diff --git a/.travis.yml b/.travis.yml index 31d10e84ee..2c5fc7e6ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,17 +3,17 @@ language: rust matrix: include: # NOTE used to build docs on successful merges to master - - env: TARGET=x86_64-unknown-linux-gnu + # - env: TARGET=x86_64-unknown-linux-gnu - - env: TARGET=thumbv6m-none-eabi - if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) + # - env: TARGET=thumbv6m-none-eabi + # if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - - env: TARGET=thumbv7m-none-eabi - if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) + # - env: TARGET=thumbv7m-none-eabi + # if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - env: TARGET=x86_64-unknown-linux-gnu rust: nightly - if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) + # if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - env: TARGET=thumbv6m-none-eabi rust: nightly diff --git a/CHANGELOG.md b/CHANGELOG.md index df4c674d32..fb1102c0ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,35 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +## v0.5.0 - 2019-??-?? (ALPHA pre-release) + +### Changed + +- [breaking-change][] [RFC 155] "explicit `Context` parameter" has been + implemented. + +[RFC 155]: https://github.com/japaric/cortex-m-rtfm/issues/155 + +- [breaking-change][] [RFC 147] "all functions must be safe" has been + implemented. + +[RFC 147]: https://github.com/japaric/cortex-m-rtfm/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. + +### 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/japaric/cortex-m-rtfm/pull/140 + ## [v0.4.3] - 2019-04-21 ### Changed diff --git a/Cargo.toml b/Cargo.toml index b102ce783a..b0df0483b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT OR Apache-2.0" name = "cortex-m-rtfm" readme = "README.md" repository = "https://github.com/japaric/cortex-m-rtfm" -version = "0.4.3" +version = "0.5.0-alpha.1" [lib] name = "rtfm" @@ -25,6 +25,12 @@ required-features = ["timer-queue"] name = "periodic" required-features = ["timer-queue"] +[[example]] +name = "pool" +# this example doesn't need this feature but only works on ARMv7-M +# specifying the feature here avoids compiling this for ARMv6-M +required-features = ["timer-queue"] + [[example]] name = "schedule" required-features = ["timer-queue"] @@ -36,12 +42,13 @@ required-features = ["timer-queue"] [dependencies] cortex-m = "0.5.8" cortex-m-rt = "0.6.7" -cortex-m-rtfm-macros = { path = "macros", version = "0.4.3" } -heapless = "0.4.1" -owned-singleton = "0.1.0" +cortex-m-rtfm-macros = { path = "macros", version = "0.5.0-alpha.1" } + +[dependencies.heapless] +features = ["smaller-atomics", "min-const-fn"] +version = "0.4.3" [dev-dependencies] -alloc-singleton = "0.1.0" cortex-m-semihosting = "0.3.2" lm3s6965 = "0.1.3" panic-halt = "0.2.0" diff --git a/README.md b/README.md index b8cbd00f7d..db23d2b4d0 100644 --- a/README.md +++ b/README.md @@ -41,13 +41,13 @@ A concurrency framework for building real time systems. ## Requirements -- Rust 1.31.0+ +- Rust 1.36.0+ - Applications must be written using the 2018 edition. -## [User documentation](https://japaric.github.io/cortex-m-rtfm/book/en) +## [User documentation](https://japaric.github.io/rtfm5/book/en) -## [API reference](https://japaric.github.io/cortex-m-rtfm/api/rtfm/index.html) +## [API reference](https://japaric.github.io/rtfm5/api/rtfm/index.html) ## Acknowledgments diff --git a/book/en/src/SUMMARY.md b/book/en/src/SUMMARY.md index 051d1acc4e..bde2b8d270 100644 --- a/book/en/src/SUMMARY.md +++ b/book/en/src/SUMMARY.md @@ -6,7 +6,6 @@ - [Resources](./by-example/resources.md) - [Tasks](./by-example/tasks.md) - [Timer queue](./by-example/timer-queue.md) - - [Singletons](./by-example/singletons.md) - [Types, Send and Sync](./by-example/types-send-sync.md) - [Starting a new project](./by-example/new.md) - [Tips & tricks](./by-example/tips.md) diff --git a/book/en/src/by-example/app.md b/book/en/src/by-example/app.md index 996b8c1672..d595570cc7 100644 --- a/book/en/src/by-example/app.md +++ b/book/en/src/by-example/app.md @@ -28,15 +28,14 @@ not required to use the [`cortex_m_rt::entry`] attribute. Within the pseudo-module the `app` attribute expects to find an initialization function marked with the `init` attribute. This function must have signature -`[unsafe] fn()`. +`fn(init::Context) [-> init::LateResources]`. This initialization function will be the first part of the application to run. The `init` function will run *with interrupts disabled* and has exclusive access to Cortex-M and device specific peripherals through the `core` and `device` -variables, which are injected in the scope of `init` by the `app` attribute. Not -all Cortex-M peripherals are available in `core` because the RTFM runtime takes -ownership of some of them -- for more details see the [`rtfm::Peripherals`] -struct. +variables fields of `init::Context`. Not all Cortex-M peripherals are available +in `core` because the RTFM runtime takes ownership of some of them -- for more +details see the [`rtfm::Peripherals`] struct. `static mut` variables declared at the beginning of `init` will be transformed into `&'static mut` references that are safe to access. @@ -61,7 +60,7 @@ $ cargo run --example init A function marked with the `idle` attribute can optionally appear in the pseudo-module. This function is used as the special *idle task* and must have -signature `[unsafe] fn() - > !`. +signature `fn(idle::Context) - > !`. When present, the runtime will execute the `idle` task after `init`. Unlike `init`, `idle` will run *with interrupts enabled* and it's not allowed to return diff --git a/book/en/src/by-example/new.md b/book/en/src/by-example/new.md index ae49ef21af..91b31bb87d 100644 --- a/book/en/src/by-example/new.md +++ b/book/en/src/by-example/new.md @@ -40,7 +40,7 @@ $ rm memory.x build.rs `timer-queue` feature. ``` console -$ cargo add cortex-m-rtfm +$ cargo add cortex-m-rtfm --allow-prerelease ``` 4. Write your RTFM application. @@ -49,7 +49,7 @@ Here I'll use the `init` example from the `cortex-m-rtfm` crate. ``` console $ curl \ - -L https://github.com/japaric/cortex-m-rtfm/raw/v0.4.0/examples/init.rs \ + -L https://github.com/japaric/cortex-m-rtfm/raw/v0.5.0-alpha.1/examples/init.rs \ > src/main.rs ``` diff --git a/book/en/src/by-example/resources.md b/book/en/src/by-example/resources.md index 17f4d139f3..06f2f06c5f 100644 --- a/book/en/src/by-example/resources.md +++ b/book/en/src/by-example/resources.md @@ -10,7 +10,7 @@ have enough information to optimize the access to the shared data. The `app` attribute has a full view of the application thus it can optimize access to `static` variables. In RTFM we refer to the `static` variables declared inside the `app` pseudo-module as *resources*. To access a resource the -context (`init`, `idle`, `interrupt` or `exception`) must first declare the +context (`init`, `idle`, `interrupt` or `exception`) one must first declare the resource in the `resources` argument of its attribute. In the example below two interrupt handlers access the same resource. No `Mutex` @@ -30,7 +30,7 @@ $ cargo run --example resource The priority of each handler can be declared in the `interrupt` and `exception` attributes. It's not possible to set the priority in any other way because the -runtime takes ownership of the `NVIC` peripheral; it's also not possible to +runtime takes ownership of the `NVIC` peripheral thus it's also not possible to change the priority of a handler / task at runtime. Thanks to this restriction the framework has knowledge about the *static* priorities of all interrupt and exception handlers. @@ -71,10 +71,10 @@ $ cargo run --example lock One more note about priorities: choosing a priority higher than what the device supports (that is `1 << NVIC_PRIO_BITS`) will result in a compile error. Due to -limitations in the language the error is currently far from helpful: it will say -something along the lines of "evaluation of constant value failed" and the span -of the error will *not* point out to the problematic interrupt value -- we are -sorry about this! +limitations in the language the error message is currently far from helpful: it +will say something along the lines of "evaluation of constant value failed" and +the span of the error will *not* point out to the problematic interrupt value -- +we are sorry about this! ## Late resources diff --git a/book/en/src/by-example/singletons.md b/book/en/src/by-example/singletons.md deleted file mode 100644 index 0823f057f5..0000000000 --- a/book/en/src/by-example/singletons.md +++ /dev/null @@ -1,26 +0,0 @@ -# Singletons - -The `app` attribute is aware of [`owned-singleton`] crate and its [`Singleton`] -attribute. When this attribute is applied to one of the resources the runtime -will perform the `unsafe` initialization of the singleton for you, ensuring that -only a single instance of the singleton is ever created. - -[`owned-singleton`]: ../../api/owned_singleton/index.html -[`Singleton`]: ../../api/owned_singleton_macros/attr.Singleton.html - -Note that when using the `Singleton` attribute you'll need to have the -`owned_singleton` in your dependencies. - -Below is an example that uses the `Singleton` attribute on a chunk of memory -and then uses the singleton instance as a fixed-size memory pool using one of -the [`alloc-singleton`] abstractions. - -[`alloc-singleton`]: https://crates.io/crates/alloc-singleton - -``` rust -{{#include ../../../../examples/singleton.rs}} -``` - -``` console -$ cargo run --example singleton -{{#include ../../../../ci/expected/singleton.run}}``` diff --git a/book/en/src/by-example/tips.md b/book/en/src/by-example/tips.md index c0bfc56e14..79b9d71e0f 100644 --- a/book/en/src/by-example/tips.md +++ b/book/en/src/by-example/tips.md @@ -24,8 +24,8 @@ of tasks. You can use conditional compilation (`#[cfg]`) on resources (`static [mut]` items) and tasks (`fn` items). The effect of using `#[cfg]` attributes is that -the resource / task will *not* be injected into the prelude of tasks that use -them (see `resources`, `spawn` and `schedule`) if the condition doesn't hold. +the resource / task will *not* be available through the corresponding `Context` +`struct` if the condition doesn't hold. The example below logs a message whenever the `foo` task is spawned, but only if the program has been compiled using the `dev` profile. @@ -37,7 +37,7 @@ the program has been compiled using the `dev` profile. ## Running tasks from RAM The main goal of moving the specification of RTFM applications to attributes in -RTFM v0.4.x was to allow inter-operation with other attributes. For example, the +RTFM v0.4.0 was to allow inter-operation with other attributes. For example, the `link_section` attribute can be applied to tasks to place them in RAM; this can improve performance in some cases. @@ -78,8 +78,6 @@ $ cargo nm --example ramfunc --release | grep ' bar::' ## `binds` -**NOTE**: Requires RTFM ~0.4.2 - You can give hardware tasks more task-like names using the `binds` argument: you name the function as you wish and specify the name of the interrupt / exception in the `binds` argument. Types like `Spawn` will be placed in a module named @@ -91,3 +89,27 @@ after the function, not the interrupt / exception. Example below: ``` console $ cargo run --example binds {{#include ../../../../ci/expected/binds.run}}``` + +## Indirection for faster message passing + +Message passing always involves copying the payload from the sender into a +static variable and then from the static variable into the receiver. Thus +sending a large buffer, like a `[u8; 128]`, as a message involves two expensive +`memcpy`s. To minimize the message passing overhead one can use indirection: +instead of sending the buffer by value, one can send an owning pointer into the +buffer. + +One can use a global allocator to achieve indirection (`alloc::Box`, +`alloc::Rc`, etc.), which requires using the nightly channel as of Rust v1.34.0, +or one can use a statically allocated memory pool like [`heapless::Pool`]. + +[`heapless::Pool`]: https://docs.rs/heapless/0.4.3/heapless/pool/index.html + +Here's an example where `heapless::Pool` is used to "box" buffers of 128 bytes. + +``` rust +{{#include ../../../../examples/pool.rs}} +``` +``` console +$ cargo run --example binds +{{#include ../../../../ci/expected/pool.run}}``` diff --git a/book/en/src/by-example/types-send-sync.md b/book/en/src/by-example/types-send-sync.md index 632946b9ac..99f9f19030 100644 --- a/book/en/src/by-example/types-send-sync.md +++ b/book/en/src/by-example/types-send-sync.md @@ -7,8 +7,7 @@ write plain functions that take them as arguments. The API reference specifies how these types are generated from the input. You can also generate documentation for you binary crate (`cargo doc --bin `); in the documentation you'll find `Context` structs (e.g. `init::Context` and -`idle::Context`) whose fields represent the variables injected into each -function. +`idle::Context`). The example below shows the different types generates by the `app` attribute. diff --git a/book/en/src/preface.md b/book/en/src/preface.md index d8f64fd473..e6a52b7bdc 100644 --- a/book/en/src/preface.md +++ b/book/en/src/preface.md @@ -11,6 +11,9 @@ There is a translation of this book in [Russian]. [Russian]: ../ru/index.html +**HEADS UP** This is an **alpha** pre-release; there may be breaking changes in +the API and semantics before a proper release is made. + {{#include ../../../README.md:5:46}} {{#include ../../../README.md:52:}} diff --git a/ci/after-success.sh b/ci/after-success.sh index 65ddb90483..6c69693795 100644 --- a/ci/after-success.sh +++ b/ci/after-success.sh @@ -23,7 +23,8 @@ main() { ./ghp-import/ghp_import.py $td set +x - git push -fq https://$GH_TOKEN@github.com/$TRAVIS_REPO_SLUG.git gh-pages && echo OK + # NOTE push documentation to a different repository + git push -fq https://$GH_TOKEN@github.com/japaric/rtfm5.git gh-pages && echo OK rm -rf $td } diff --git a/ci/expected/pool.run b/ci/expected/pool.run new file mode 100644 index 0000000000..040dcee888 --- /dev/null +++ b/ci/expected/pool.run @@ -0,0 +1,2 @@ +bar(0x2000008c) +foo(0x20000110) diff --git a/ci/script.sh b/ci/script.sh index 0e350d1ffd..b64617d000 100644 --- a/ci/script.sh +++ b/ci/script.sh @@ -104,14 +104,13 @@ main() { message capacity - singleton - types not-send not-sync shared-with-init generics + pool ramfunc ) @@ -121,6 +120,31 @@ main() { continue fi + if [ $ex = pool ]; then + if [ $TARGET != thumbv6m-none-eabi ]; then + local td=$(mktemp -d) + + local features="$nightly,timer-queue" + cargo run --example $ex --target $TARGET --features $features >\ + $td/pool.run + grep 'foo(0x2' $td/pool.run + grep 'bar(0x2' $td/pool.run + arm-none-eabi-objcopy -O ihex target/$TARGET/debug/examples/$ex \ + ci/builds/${ex}_${features/,/_}_debug_1.hex + + cargo run --example $ex --target $TARGET --features $features --release >\ + $td/pool.run + grep 'foo(0x2' $td/pool.run + grep 'bar(0x2' $td/pool.run + arm-none-eabi-objcopy -O ihex target/$TARGET/release/examples/$ex \ + ci/builds/${ex}_${features/,/_}_release_1.hex + + rm -rf $td + fi + + continue + fi + if [ $ex != types ]; then arm_example "run" $ex "debug" "$nightly" "1" arm_example "run" $ex "release" "$nightly" "1" @@ -140,13 +164,7 @@ main() { continue fi - if [ $ex = singleton ]; then - # singleton build is currently not reproducible due to - # https://github.com/japaric/owned-singleton/issues/2 - continue - fi - - if [ $ex != types ]; then + if [ $ex != types ] && [ $ex != pool ]; then arm_example "build" $ex "debug" "$nightly" "2" cmp ci/builds/${ex}_${nightly/nightly/nightly_}debug_1.hex \ ci/builds/${ex}_${nightly/nightly/nightly_}debug_2.hex diff --git a/examples/baseline.rs b/examples/baseline.rs index fdf368383d..d743107dc1 100644 --- a/examples/baseline.rs +++ b/examples/baseline.rs @@ -9,24 +9,23 @@ extern crate panic_semihosting; use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; -use rtfm::app; // NOTE: does NOT properly work on QEMU -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[init(spawn = [foo])] - fn init() { - hprintln!("init(baseline = {:?})", start).unwrap(); + fn init(c: init::Context) { + hprintln!("init(baseline = {:?})", c.start).unwrap(); // `foo` inherits the baseline of `init`: `Instant(0)` - spawn.foo().unwrap(); + c.spawn.foo().unwrap(); } #[task(schedule = [foo])] - fn foo() { + fn foo(c: foo::Context) { static mut ONCE: bool = true; - hprintln!("foo(baseline = {:?})", scheduled).unwrap(); + hprintln!("foo(baseline = {:?})", c.scheduled).unwrap(); if *ONCE { *ONCE = false; @@ -38,11 +37,11 @@ const APP: () = { } #[interrupt(spawn = [foo])] - fn UART0() { - hprintln!("UART0(baseline = {:?})", start).unwrap(); + fn UART0(c: UART0::Context) { + hprintln!("UART0(baseline = {:?})", c.start).unwrap(); // `foo` inherits the baseline of `UART0`: its `start` time - spawn.foo().unwrap(); + c.spawn.foo().unwrap(); } extern "C" { diff --git a/examples/binds.rs b/examples/binds.rs index a8b386fb7e..3d2d9b541b 100644 --- a/examples/binds.rs +++ b/examples/binds.rs @@ -9,20 +9,19 @@ extern crate panic_semihosting; use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; -use rtfm::app; // `examples/interrupt.rs` rewritten to use `binds` -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[init] - fn init() { + fn init(_: init::Context) { rtfm::pend(Interrupt::UART0); hprintln!("init").unwrap(); } #[idle] - fn idle() -> ! { + fn idle(_: idle::Context) -> ! { hprintln!("idle").unwrap(); rtfm::pend(Interrupt::UART0); @@ -33,7 +32,7 @@ const APP: () = { } #[interrupt(binds = UART0)] - fn foo() { + fn foo(_: foo::Context) { static mut TIMES: u32 = 0; *TIMES += 1; diff --git a/examples/capacity.rs b/examples/capacity.rs index a7132ba0f4..07edd9b8e1 100644 --- a/examples/capacity.rs +++ b/examples/capacity.rs @@ -9,32 +9,31 @@ extern crate panic_semihosting; use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; -use rtfm::app; -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[init] - fn init() { + fn init(_: init::Context) { rtfm::pend(Interrupt::UART0); } #[interrupt(spawn = [foo, bar])] - fn UART0() { - spawn.foo(0).unwrap(); - spawn.foo(1).unwrap(); - spawn.foo(2).unwrap(); - spawn.foo(3).unwrap(); + fn UART0(c: UART0::Context) { + c.spawn.foo(0).unwrap(); + c.spawn.foo(1).unwrap(); + c.spawn.foo(2).unwrap(); + c.spawn.foo(3).unwrap(); - spawn.bar().unwrap(); + c.spawn.bar().unwrap(); } #[task(capacity = 4)] - fn foo(x: u32) { + fn foo(_: foo::Context, x: u32) { hprintln!("foo({})", x).unwrap(); } #[task] - fn bar() { + fn bar(_: bar::Context) { hprintln!("bar").unwrap(); debug::exit(debug::EXIT_SUCCESS); diff --git a/examples/cfg.rs b/examples/cfg.rs index 3f4ca90431..03f9dbdcb2 100644 --- a/examples/cfg.rs +++ b/examples/cfg.rs @@ -9,25 +9,24 @@ extern crate panic_semihosting; #[cfg(debug_assertions)] use cortex_m_semihosting::hprintln; -use rtfm::app; -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[cfg(debug_assertions)] // <- `true` when using the `dev` profile static mut COUNT: u32 = 0; #[init] - fn init() { + fn init(_: init::Context) { // .. } #[task(priority = 3, resources = [COUNT], spawn = [log])] - fn foo() { + fn foo(c: foo::Context) { #[cfg(debug_assertions)] { - *resources.COUNT += 1; + *c.resources.COUNT += 1; - spawn.log(*resources.COUNT).ok(); + c.spawn.log(*c.resources.COUNT).ok(); } // this wouldn't compile in `release` mode @@ -38,7 +37,7 @@ const APP: () = { #[cfg(debug_assertions)] #[task] - fn log(n: u32) { + fn log(_: log::Context, n: u32) { hprintln!( "foo has been called {} time{}", n, diff --git a/examples/generics.rs b/examples/generics.rs index c8ce839351..e624da39cf 100644 --- a/examples/generics.rs +++ b/examples/generics.rs @@ -9,25 +9,25 @@ extern crate panic_semihosting; use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; -use rtfm::{app, Mutex}; +use rtfm::Mutex; -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { static mut SHARED: u32 = 0; #[init] - fn init() { + fn init(_: init::Context) { rtfm::pend(Interrupt::UART0); rtfm::pend(Interrupt::UART1); } #[interrupt(resources = [SHARED])] - fn UART0() { + fn UART0(c: UART0::Context) { static mut STATE: u32 = 0; hprintln!("UART0(STATE = {})", *STATE).unwrap(); - advance(STATE, resources.SHARED); + advance(STATE, c.resources.SHARED); rtfm::pend(Interrupt::UART1); @@ -35,17 +35,17 @@ const APP: () = { } #[interrupt(priority = 2, resources = [SHARED])] - fn UART1() { + fn UART1(mut c: UART1::Context) { static mut STATE: u32 = 0; hprintln!("UART1(STATE = {})", *STATE).unwrap(); // just to show that `SHARED` can be accessed directly and .. - *resources.SHARED += 0; + *c.resources.SHARED += 0; // .. also through a (no-op) `lock` - resources.SHARED.lock(|shared| *shared += 0); + c.resources.SHARED.lock(|shared| *shared += 0); - advance(STATE, resources.SHARED); + advance(STATE, c.resources.SHARED); } }; diff --git a/examples/idle.rs b/examples/idle.rs index 1f21a37f4a..d10cc43ed0 100644 --- a/examples/idle.rs +++ b/examples/idle.rs @@ -8,17 +8,16 @@ extern crate panic_semihosting; use cortex_m_semihosting::{debug, hprintln}; -use rtfm::app; -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[init] - fn init() { + fn init(_: init::Context) { hprintln!("init").unwrap(); } #[idle] - fn idle() -> ! { + fn idle(_: idle::Context) -> ! { static mut X: u32 = 0; // Safe access to local `static mut` variable diff --git a/examples/init.rs b/examples/init.rs index be6cfe3eb3..df687794a8 100644 --- a/examples/init.rs +++ b/examples/init.rs @@ -8,19 +8,18 @@ extern crate panic_semihosting; use cortex_m_semihosting::{debug, hprintln}; -use rtfm::app; -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[init] - fn init() { + fn init(c: init::Context) { static mut X: u32 = 0; // Cortex-M peripherals - let _core: rtfm::Peripherals = core; + let _core: rtfm::Peripherals = c.core; // Device specific peripherals - let _device: lm3s6965::Peripherals = device; + let _device: lm3s6965::Peripherals = c.device; // Safe access to local `static mut` variable let _x: &'static mut u32 = X; diff --git a/examples/interrupt.rs b/examples/interrupt.rs index 3c669d9ef2..dd6efa0df5 100644 --- a/examples/interrupt.rs +++ b/examples/interrupt.rs @@ -9,12 +9,11 @@ extern crate panic_semihosting; use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; -use rtfm::app; -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[init] - fn init() { + fn init(_: init::Context) { // Pends the UART0 interrupt but its handler won't run until *after* // `init` returns because interrupts are disabled rtfm::pend(Interrupt::UART0); @@ -23,7 +22,7 @@ const APP: () = { } #[idle] - fn idle() -> ! { + fn idle(_: idle::Context) -> ! { // interrupts are enabled again; the `UART0` handler runs at this point hprintln!("idle").unwrap(); @@ -36,7 +35,7 @@ const APP: () = { } #[interrupt] - fn UART0() { + fn UART0(_: UART0::Context) { static mut TIMES: u32 = 0; // Safe access to local `static mut` variable diff --git a/examples/late.rs b/examples/late.rs index 622008a7fe..0074fb3233 100644 --- a/examples/late.rs +++ b/examples/late.rs @@ -13,16 +13,15 @@ use heapless::{ spsc::{Consumer, Producer, Queue}, }; use lm3s6965::Interrupt; -use rtfm::app; -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { // Late resources static mut P: Producer<'static, u32, U4> = (); static mut C: Consumer<'static, u32, U4> = (); #[init] - fn init() -> init::LateResources { + fn init(_: init::Context) -> init::LateResources { // NOTE: we use `Option` here to work around the lack of // a stable `const` constructor static mut Q: Option> = None; @@ -35,9 +34,9 @@ const APP: () = { } #[idle(resources = [C])] - fn idle() -> ! { + fn idle(c: idle::Context) -> ! { loop { - if let Some(byte) = resources.C.dequeue() { + if let Some(byte) = c.resources.C.dequeue() { hprintln!("received message: {}", byte).unwrap(); debug::exit(debug::EXIT_SUCCESS); @@ -48,7 +47,7 @@ const APP: () = { } #[interrupt(resources = [P])] - fn UART0() { - resources.P.enqueue(42).unwrap(); + fn UART0(c: UART0::Context) { + c.resources.P.enqueue(42).unwrap(); } }; diff --git a/examples/lock.rs b/examples/lock.rs index 4ca862e316..814c73640f 100644 --- a/examples/lock.rs +++ b/examples/lock.rs @@ -9,24 +9,23 @@ extern crate panic_semihosting; use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; -use rtfm::app; -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { static mut SHARED: u32 = 0; #[init] - fn init() { + fn init(_: init::Context) { rtfm::pend(Interrupt::GPIOA); } // when omitted priority is assumed to be `1` #[interrupt(resources = [SHARED])] - fn GPIOA() { + fn GPIOA(mut c: GPIOA::Context) { hprintln!("A").unwrap(); // the lower priority task requires a critical section to access the data - resources.SHARED.lock(|shared| { + c.resources.SHARED.lock(|shared| { // data can only be modified within this critical section (closure) *shared += 1; @@ -47,15 +46,15 @@ const APP: () = { } #[interrupt(priority = 2, resources = [SHARED])] - fn GPIOB() { + fn GPIOB(mut c: GPIOB::Context) { // the higher priority task does *not* need a critical section - *resources.SHARED += 1; + *c.resources.SHARED += 1; - hprintln!("D - SHARED = {}", *resources.SHARED).unwrap(); + hprintln!("D - SHARED = {}", *c.resources.SHARED).unwrap(); } #[interrupt(priority = 3)] - fn GPIOC() { + fn GPIOC(_: GPIOC::Context) { hprintln!("C").unwrap(); } }; diff --git a/examples/message.rs b/examples/message.rs index b5d68a607b..1fd3b9d4c5 100644 --- a/examples/message.rs +++ b/examples/message.rs @@ -8,41 +8,40 @@ extern crate panic_semihosting; use cortex_m_semihosting::{debug, hprintln}; -use rtfm::app; -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[init(spawn = [foo])] - fn init() { - spawn.foo(/* no message */).unwrap(); + fn init(c: init::Context) { + c.spawn.foo(/* no message */).unwrap(); } #[task(spawn = [bar])] - fn foo() { + fn foo(c: foo::Context) { static mut COUNT: u32 = 0; hprintln!("foo").unwrap(); - spawn.bar(*COUNT).unwrap(); + c.spawn.bar(*COUNT).unwrap(); *COUNT += 1; } #[task(spawn = [baz])] - fn bar(x: u32) { + fn bar(c: bar::Context, x: u32) { hprintln!("bar({})", x).unwrap(); - spawn.baz(x + 1, x + 2).unwrap(); + c.spawn.baz(x + 1, x + 2).unwrap(); } #[task(spawn = [foo])] - fn baz(x: u32, y: u32) { + fn baz(c: baz::Context, x: u32, y: u32) { hprintln!("baz({}, {})", x, y).unwrap(); if x + y > 4 { debug::exit(debug::EXIT_SUCCESS); } - spawn.foo().unwrap(); + c.spawn.foo().unwrap(); } extern "C" { diff --git a/examples/not-send.rs b/examples/not-send.rs index be78c332ff..c1b6bcddf8 100644 --- a/examples/not-send.rs +++ b/examples/not-send.rs @@ -21,32 +21,32 @@ const APP: () = { static mut SHARED: Option = None; #[init(spawn = [baz, quux])] - fn init() { - spawn.baz().unwrap(); - spawn.quux().unwrap(); + fn init(c: init::Context) { + c.spawn.baz().unwrap(); + c.spawn.quux().unwrap(); } #[task(spawn = [bar])] - fn foo() { + fn foo(c: foo::Context) { // scenario 1: message passed to task that runs at the same priority - spawn.bar(NotSend { _0: PhantomData }).ok(); + c.spawn.bar(NotSend { _0: PhantomData }).ok(); } #[task] - fn bar(_x: NotSend) { + fn bar(_: bar::Context, _x: NotSend) { // scenario 1 } #[task(priority = 2, resources = [SHARED])] - fn baz() { + fn baz(mut c: baz::Context) { // scenario 2: resource shared between tasks that run at the same priority - *resources.SHARED = Some(NotSend { _0: PhantomData }); + *c.resources.SHARED = Some(NotSend { _0: PhantomData }); } #[task(priority = 2, resources = [SHARED])] - fn quux() { + fn quux(mut c: quux::Context) { // scenario 2 - let _not_send = resources.SHARED.take().unwrap(); + let _not_send = c.resources.SHARED.take().unwrap(); debug::exit(debug::EXIT_SUCCESS); } diff --git a/examples/not-sync.rs b/examples/not-sync.rs index d94e0a0467..bc71406571 100644 --- a/examples/not-sync.rs +++ b/examples/not-sync.rs @@ -10,29 +10,28 @@ extern crate panic_halt; use core::marker::PhantomData; use cortex_m_semihosting::debug; -use rtfm::app; pub struct NotSync { _0: PhantomData<*const ()>, } -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { static SHARED: NotSync = NotSync { _0: PhantomData }; #[init] - fn init() { + fn init(_: init::Context) { debug::exit(debug::EXIT_SUCCESS); } #[task(resources = [SHARED])] - fn foo() { - let _: &NotSync = resources.SHARED; + fn foo(c: foo::Context) { + let _: &NotSync = c.resources.SHARED; } #[task(resources = [SHARED])] - fn bar() { - let _: &NotSync = resources.SHARED; + fn bar(c: bar::Context) { + let _: &NotSync = c.resources.SHARED; } extern "C" { diff --git a/examples/periodic.rs b/examples/periodic.rs index ba2b4933df..f784118367 100644 --- a/examples/periodic.rs +++ b/examples/periodic.rs @@ -8,24 +8,24 @@ extern crate panic_semihosting; use cortex_m_semihosting::hprintln; -use rtfm::{app, Instant}; +use rtfm::Instant; const PERIOD: u32 = 8_000_000; // NOTE: does NOT work on QEMU! -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[init(schedule = [foo])] - fn init() { - schedule.foo(Instant::now() + PERIOD.cycles()).unwrap(); + fn init(c: init::Context) { + c.schedule.foo(Instant::now() + PERIOD.cycles()).unwrap(); } #[task(schedule = [foo])] - fn foo() { + fn foo(c: foo::Context) { let now = Instant::now(); - hprintln!("foo(scheduled = {:?}, now = {:?})", scheduled, now).unwrap(); + hprintln!("foo(scheduled = {:?}, now = {:?})", c.scheduled, now).unwrap(); - schedule.foo(scheduled + PERIOD.cycles()).unwrap(); + c.schedule.foo(c.scheduled + PERIOD.cycles()).unwrap(); } extern "C" { diff --git a/examples/pool.rs b/examples/pool.rs new file mode 100644 index 0000000000..0b594b192d --- /dev/null +++ b/examples/pool.rs @@ -0,0 +1,67 @@ +//! examples/pool.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_semihosting; + +use cortex_m_semihosting::{debug, hprintln}; +use heapless::{ + pool, + pool::singleton::{Box, Pool}, +}; +use lm3s6965::Interrupt; +use rtfm::app; + +// Declare a pool of 128-byte memory blocks +pool!(P: [u8; 128]); + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init(_: init::Context) { + static mut MEMORY: [u8; 512] = [0; 512]; + + // Increase the capacity of the memory pool by ~4 + P::grow(MEMORY); + + rtfm::pend(Interrupt::I2C0); + } + + #[interrupt(priority = 2, spawn = [foo, bar])] + fn I2C0(c: I2C0::Context) { + // claim a memory block, leave it uninitialized and .. + let x = P::alloc().unwrap().freeze(); + + // .. send it to the `foo` task + c.spawn.foo(x).ok().unwrap(); + + // send another block to the task `bar` + c.spawn.bar(P::alloc().unwrap().freeze()).ok().unwrap(); + } + + #[task] + fn foo(_: foo::Context, x: Box

) { + hprintln!("foo({:?})", x.as_ptr()).unwrap(); + + // explicitly return the block to the pool + drop(x); + + debug::exit(debug::EXIT_SUCCESS); + } + + #[task(priority = 2)] + fn bar(_: bar::Context, x: Box

) { + hprintln!("bar({:?})", x.as_ptr()).unwrap(); + + // this is done automatically so we can omit the call to `drop` + // drop(x); + } + + extern "C" { + fn UART0(); + fn UART1(); + } +}; diff --git a/examples/ramfunc.rs b/examples/ramfunc.rs index 37ea82a77e..4b0d69c793 100644 --- a/examples/ramfunc.rs +++ b/examples/ramfunc.rs @@ -8,18 +8,17 @@ extern crate panic_semihosting; use cortex_m_semihosting::{debug, hprintln}; -use rtfm::app; -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[init(spawn = [bar])] - fn init() { - spawn.bar().unwrap(); + fn init(c: init::Context) { + c.spawn.bar().unwrap(); } #[inline(never)] #[task] - fn foo() { + fn foo(_: foo::Context) { hprintln!("foo").unwrap(); debug::exit(debug::EXIT_SUCCESS); @@ -29,8 +28,8 @@ const APP: () = { #[inline(never)] #[link_section = ".data.bar"] #[task(priority = 2, spawn = [foo])] - fn bar() { - spawn.foo().unwrap(); + fn bar(c: bar::Context) { + c.spawn.foo().unwrap(); } extern "C" { diff --git a/examples/resource.rs b/examples/resource.rs index 5ddab9e8da..06bdf395ae 100644 --- a/examples/resource.rs +++ b/examples/resource.rs @@ -9,21 +9,20 @@ extern crate panic_semihosting; use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; -use rtfm::app; -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { // A resource static mut SHARED: u32 = 0; #[init] - fn init() { + fn init(_: init::Context) { rtfm::pend(Interrupt::UART0); rtfm::pend(Interrupt::UART1); } #[idle] - fn idle() -> ! { + fn idle(_: idle::Context) -> ! { debug::exit(debug::EXIT_SUCCESS); // error: `SHARED` can't be accessed from this context @@ -34,17 +33,17 @@ const APP: () = { // `SHARED` can be access from this context #[interrupt(resources = [SHARED])] - fn UART0() { - *resources.SHARED += 1; + fn UART0(mut c: UART0::Context) { + *c.resources.SHARED += 1; - hprintln!("UART0: SHARED = {}", resources.SHARED).unwrap(); + hprintln!("UART0: SHARED = {}", c.resources.SHARED).unwrap(); } // `SHARED` can be access from this context #[interrupt(resources = [SHARED])] - fn UART1() { - *resources.SHARED += 1; + fn UART1(mut c: UART1::Context) { + *c.resources.SHARED += 1; - hprintln!("UART1: SHARED = {}", resources.SHARED).unwrap(); + hprintln!("UART1: SHARED = {}", c.resources.SHARED).unwrap(); } }; diff --git a/examples/schedule.rs b/examples/schedule.rs index fd63347308..eaafb4c947 100644 --- a/examples/schedule.rs +++ b/examples/schedule.rs @@ -8,31 +8,31 @@ extern crate panic_semihosting; use cortex_m_semihosting::hprintln; -use rtfm::{app, Instant}; +use rtfm::Instant; // NOTE: does NOT work on QEMU! -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[init(schedule = [foo, bar])] - fn init() { + fn init(c: init::Context) { let now = Instant::now(); hprintln!("init @ {:?}", now).unwrap(); // Schedule `foo` to run 8e6 cycles (clock cycles) in the future - schedule.foo(now + 8_000_000.cycles()).unwrap(); + c.schedule.foo(now + 8_000_000.cycles()).unwrap(); // Schedule `bar` to run 4e6 cycles in the future - schedule.bar(now + 4_000_000.cycles()).unwrap(); + c.schedule.bar(now + 4_000_000.cycles()).unwrap(); } #[task] - fn foo() { + fn foo(_: foo::Context) { hprintln!("foo @ {:?}", Instant::now()).unwrap(); } #[task] - fn bar() { + fn bar(_: bar::Context) { hprintln!("bar @ {:?}", Instant::now()).unwrap(); } diff --git a/examples/shared-with-init.rs b/examples/shared-with-init.rs index 5ddd2cc3d9..0fb9191cbd 100644 --- a/examples/shared-with-init.rs +++ b/examples/shared-with-init.rs @@ -18,17 +18,17 @@ const APP: () = { static mut SHARED: Option = None; #[init(resources = [SHARED])] - fn init() { + fn init(c: init::Context) { // this `message` will be sent to task `UART0` let message = MustBeSend; - *resources.SHARED = Some(message); + *c.resources.SHARED = Some(message); rtfm::pend(Interrupt::UART0); } #[interrupt(resources = [SHARED])] - fn UART0() { - if let Some(message) = resources.SHARED.take() { + fn UART0(c: UART0::Context) { + if let Some(message) = c.resources.SHARED.take() { // `message` has been received drop(message); diff --git a/examples/singleton.rs b/examples/singleton.rs deleted file mode 100644 index 9e48e54121..0000000000 --- a/examples/singleton.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! examples/singleton.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -extern crate panic_semihosting; - -use alloc_singleton::stable::pool::{Box, Pool}; -use cortex_m_semihosting::{debug, hprintln}; -use lm3s6965::Interrupt; -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[Singleton(Send)] - static mut M: [u32; 2] = [0; 2]; - - static mut P: Pool = (); - - #[init(resources = [M])] - fn init() -> init::LateResources { - rtfm::pend(Interrupt::I2C0); - - init::LateResources { - P: Pool::new(resources.M), - } - } - - #[interrupt( - priority = 2, - resources = [P], - spawn = [foo, bar], - )] - fn I2C0() { - spawn.foo(resources.P.alloc(1).unwrap()).unwrap(); - spawn.bar(resources.P.alloc(2).unwrap()).unwrap(); - } - - #[task(resources = [P])] - fn foo(x: Box) { - hprintln!("foo({})", x).unwrap(); - - resources.P.lock(|p| p.dealloc(x)); - - debug::exit(debug::EXIT_SUCCESS); - } - - #[task(priority = 2, resources = [P])] - fn bar(x: Box) { - hprintln!("bar({})", x).unwrap(); - - resources.P.dealloc(x); - } - - extern "C" { - fn UART0(); - fn UART1(); - } -}; diff --git a/examples/smallest.rs b/examples/smallest.rs index e4d86be9c9..c153716805 100644 --- a/examples/smallest.rs +++ b/examples/smallest.rs @@ -13,5 +13,5 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} }; diff --git a/examples/static.rs b/examples/static.rs index 0309b68163..2e3b5b4197 100644 --- a/examples/static.rs +++ b/examples/static.rs @@ -9,14 +9,13 @@ extern crate panic_semihosting; use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; -use rtfm::app; -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { static KEY: u32 = (); #[init] - fn init() -> init::LateResources { + fn init(_: init::Context) -> init::LateResources { rtfm::pend(Interrupt::UART0); rtfm::pend(Interrupt::UART1); @@ -24,14 +23,14 @@ const APP: () = { } #[interrupt(resources = [KEY])] - fn UART0() { - hprintln!("UART0(KEY = {:#x})", resources.KEY).unwrap(); + fn UART0(c: UART0::Context) { + hprintln!("UART0(KEY = {:#x})", c.resources.KEY).unwrap(); debug::exit(debug::EXIT_SUCCESS); } #[interrupt(priority = 2, resources = [KEY])] - fn UART1() { - hprintln!("UART1(KEY = {:#x})", resources.KEY).unwrap(); + fn UART1(c: UART1::Context) { + hprintln!("UART1(KEY = {:#x})", c.resources.KEY).unwrap(); } }; diff --git a/examples/task.rs b/examples/task.rs index 4f168bb814..5bb32acb73 100644 --- a/examples/task.rs +++ b/examples/task.rs @@ -8,38 +8,37 @@ extern crate panic_semihosting; use cortex_m_semihosting::{debug, hprintln}; -use rtfm::app; -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[init(spawn = [foo])] - fn init() { - spawn.foo().unwrap(); + fn init(c: init::Context) { + c.spawn.foo().unwrap(); } #[task(spawn = [bar, baz])] - fn foo() { + fn foo(c: foo::Context) { hprintln!("foo").unwrap(); // spawns `bar` onto the task scheduler // `foo` and `bar` have the same priority so `bar` will not run until // after `foo` terminates - spawn.bar().unwrap(); + c.spawn.bar().unwrap(); // spawns `baz` onto the task scheduler // `baz` has higher priority than `foo` so it immediately preempts `foo` - spawn.baz().unwrap(); + c.spawn.baz().unwrap(); } #[task] - fn bar() { + fn bar(_: bar::Context) { hprintln!("bar").unwrap(); debug::exit(debug::EXIT_SUCCESS); } #[task(priority = 2)] - fn baz() { + fn baz(_: baz::Context) { hprintln!("baz").unwrap(); } diff --git a/examples/types.rs b/examples/types.rs index c1b8cd6923..c3dd89ca4c 100644 --- a/examples/types.rs +++ b/examples/types.rs @@ -8,45 +8,45 @@ extern crate panic_semihosting; use cortex_m_semihosting::debug; -use rtfm::{app, Exclusive, Instant}; +use rtfm::{Exclusive, Instant}; -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { static mut SHARED: u32 = 0; #[init(schedule = [foo], spawn = [foo])] - fn init() { - let _: Instant = start; - let _: rtfm::Peripherals = core; - let _: lm3s6965::Peripherals = device; - let _: init::Schedule = schedule; - let _: init::Spawn = spawn; + fn init(c: init::Context) { + let _: Instant = c.start; + let _: rtfm::Peripherals = c.core; + let _: lm3s6965::Peripherals = c.device; + let _: init::Schedule = c.schedule; + let _: init::Spawn = c.spawn; debug::exit(debug::EXIT_SUCCESS); } #[exception(schedule = [foo], spawn = [foo])] - fn SVCall() { - let _: Instant = start; - let _: SVCall::Schedule = schedule; - let _: SVCall::Spawn = spawn; + fn SVCall(c: SVCall::Context) { + let _: Instant = c.start; + let _: SVCall::Schedule = c.schedule; + let _: SVCall::Spawn = c.spawn; } #[interrupt(resources = [SHARED], schedule = [foo], spawn = [foo])] - fn UART0() { - let _: Instant = start; - let _: resources::SHARED = resources.SHARED; - let _: UART0::Schedule = schedule; - let _: UART0::Spawn = spawn; + fn UART0(c: UART0::Context) { + let _: Instant = c.start; + let _: resources::SHARED = c.resources.SHARED; + let _: UART0::Schedule = c.schedule; + let _: UART0::Spawn = c.spawn; } #[task(priority = 2, resources = [SHARED], schedule = [foo], spawn = [foo])] - fn foo() { - let _: Instant = scheduled; - let _: Exclusive = resources.SHARED; - let _: foo::Resources = resources; - let _: foo::Schedule = schedule; - let _: foo::Spawn = spawn; + fn foo(c: foo::Context) { + let _: Instant = c.scheduled; + let _: Exclusive = c.resources.SHARED; + let _: foo::Resources = c.resources; + let _: foo::Schedule = c.schedule; + let _: foo::Spawn = c.spawn; } extern "C" { diff --git a/macros/Cargo.toml b/macros/Cargo.toml index d891ba9923..3771869cfa 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" name = "cortex-m-rtfm-macros" readme = "../README.md" repository = "https://github.com/japaric/cortex-m-rtfm" -version = "0.4.3" +version = "0.5.0-alpha.1" [lib] proc-macro = true @@ -22,10 +22,6 @@ proc-macro2 = "0.4.24" features = ["extra-traits", "full"] version = "0.15.23" -[dependencies.rand] -default-features = false -version = "0.5.5" - [features] timer-queue = [] nightly = [] \ No newline at end of file diff --git a/macros/src/analyze.rs b/macros/src/analyze.rs index cfd8ebc94a..a47be77972 100644 --- a/macros/src/analyze.rs +++ b/macros/src/analyze.rs @@ -190,19 +190,20 @@ pub fn app(app: &App) -> Analysis { } // Ceiling analysis of free queues (consumer end point) -- first pass - // Ceiling analysis of ready queues (producer end point) + // Ceiling analysis of ready queues (producer end point) -- first pass // Also compute more Send-ness requirements - let mut free_queues: HashMap<_, _> = app.tasks.keys().map(|task| (task.clone(), 0)).collect(); - let mut ready_queues: HashMap<_, _> = dispatchers.keys().map(|level| (*level, 0)).collect(); + let mut free_queues = HashMap::new(); + let mut ready_queues = HashMap::new(); for (priority, task) in app.spawn_calls() { if let Some(priority) = priority { - // Users of `spawn` contend for the to-be-spawned task FREE_QUEUE - let c = free_queues.get_mut(task).expect("BUG: free_queue.get_mut"); + // Users of `spawn` contend for the spawnee FREE_QUEUE + let c = free_queues.entry(task.clone()).or_default(); *c = cmp::max(*c, priority); + // Users of `spawn` contend for the spawnee's dispatcher READY_QUEUE let c = ready_queues - .get_mut(&app.tasks[task].args.priority) - .expect("BUG: ready_queues.get_mut"); + .entry(app.tasks[task].args.priority) + .or_default(); *c = cmp::max(*c, priority); // Send is required when sending messages from a task whose priority doesn't match the @@ -215,16 +216,23 @@ pub fn app(app: &App) -> Analysis { } } + // Ceiling analysis of ready queues (producer end point) -- second pass // Ceiling analysis of free queues (consumer end point) -- second pass // Ceiling analysis of the timer queue let mut tq_ceiling = tq_priority; for (priority, task) in app.schedule_calls() { + // the system timer handler contends for the spawnee's dispatcher READY_QUEUE + let c = ready_queues + .entry(app.tasks[task].args.priority) + .or_default(); + *c = cmp::max(*c, tq_priority); + if let Some(priority) = priority { - // Users of `schedule` contend for the to-be-spawned task FREE_QUEUE (consumer end point) - let c = free_queues.get_mut(task).expect("BUG: free_queue.get_mut"); + // Users of `schedule` contend for the spawnee task FREE_QUEUE + let c = free_queues.entry(task.clone()).or_default(); *c = cmp::max(*c, priority); - // Users of `schedule` contend for the timer queu + // Users of `schedule` contend for the timer queue tq_ceiling = cmp::max(tq_ceiling, priority); } else { // spawns from `init` are excluded from the ceiling analysis diff --git a/macros/src/check.rs b/macros/src/check.rs index 4adc2c1756..4471e96ab8 100644 --- a/macros/src/check.rs +++ b/macros/src/check.rs @@ -35,21 +35,12 @@ pub fn app(app: &App) -> parse::Result<()> { } } - // Check that all late resources have been initialized in `#[init]` if `init` has signature - // `fn()` - if !app.init.returns_late_resources { - for res in - app.resources - .iter() - .filter_map(|(name, res)| if res.expr.is_none() { Some(name) } else { None }) - { - if app.init.assigns.iter().all(|assign| assign.left != *res) { - return Err(parse::Error::new( - res.span(), - "late resources MUST be initialized at the end of `init`", - )); - } - } + // Check that `init` returns `LateResources` if there's any declared late resource + if !app.init.returns_late_resources && app.resources.iter().any(|(_, res)| res.expr.is_none()) { + return Err(parse::Error::new( + app.init.span, + "late resources have been specified so `init` must return `init::LateResources`", + )); } // Check that all referenced tasks have been declared @@ -128,7 +119,7 @@ pub fn app(app: &App) -> parse::Result<()> { } else if app.init.returns_late_resources { return Err(parse::Error::new( Span::call_site(), - "`init` signature must be `[unsafe] fn()` if there are no late resources", + "`init` signature must be `fn(init::Context)` if there are no late resources", )); } diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 4468216f53..3fae75b764 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -1,1454 +1,741 @@ -#![deny(warnings)] - use proc_macro::TokenStream; -use std::{ - collections::{BTreeMap, HashMap}, - time::{SystemTime, UNIX_EPOCH}, -}; +use std::collections::{BTreeMap, BTreeSet}; use proc_macro2::Span; use quote::quote; -use rand::{Rng, SeedableRng}; -use syn::{parse_quote, ArgCaptured, Attribute, Ident, IntSuffix, LitInt}; +use syn::{ArgCaptured, Attribute, Ident, IntSuffix, LitInt}; use crate::{ analyze::{Analysis, Ownership}, - syntax::{App, Idents, Static}, + syntax::{App, Static}, }; -// NOTE to avoid polluting the user namespaces we map some identifiers to pseudo-hygienic names. -// In some instances we also use the pseudo-hygienic names for safety, for example the user should -// not modify the priority field of resources. -type Aliases = BTreeMap; +pub fn app(name: &Ident, app: &App, analysis: &Analysis) -> TokenStream { + let (const_app_resources, mod_resources) = resources(app, analysis); -struct Context { - // Alias - #[cfg(feature = "timer-queue")] - baseline: Ident, - dispatchers: BTreeMap, - // Alias (`fn`) - idle: Ident, - // Alias (`fn`) - init: Ident, - // Alias - priority: Ident, - // For non-singletons this maps the resource name to its `static mut` variable name - statics: Aliases, - /// Task -> Alias (`struct`) - resources: HashMap, - // Alias (`enum`) - schedule_enum: Ident, - // Task -> Alias (`fn`) - schedule_fn: Aliases, - tasks: BTreeMap, - // Alias (`struct` / `static mut`) - timer_queue: Ident, - // Generator of Ident names or suffixes - ident_gen: IdentGenerator, -} + let ( + const_app_exceptions, + exception_mods, + exception_locals, + exception_resources, + user_exceptions, + ) = exceptions(app, analysis); -struct Dispatcher { - enum_: Ident, - ready_queue: Ident, -} + let ( + const_app_interrupts, + interrupt_mods, + interrupt_locals, + interrupt_resources, + user_interrupts, + ) = interrupts(app, analysis); -struct Task { - alias: Ident, - free_queue: Ident, - inputs: Ident, - spawn_fn: Ident, + let (const_app_tasks, task_mods, task_locals, task_resources, user_tasks) = + tasks(app, analysis); - #[cfg(feature = "timer-queue")] - scheduleds: Ident, -} + let const_app_dispatchers = dispatchers(&app, analysis); -impl Default for Context { - fn default() -> Self { - let mut ident_gen = IdentGenerator::new(); + let const_app_spawn = spawn(app, analysis); - Context { - #[cfg(feature = "timer-queue")] - baseline: ident_gen.mk_ident(None, false), - dispatchers: BTreeMap::new(), - idle: ident_gen.mk_ident(Some("idle"), false), - init: ident_gen.mk_ident(Some("init"), false), - priority: ident_gen.mk_ident(None, false), - statics: Aliases::new(), - resources: HashMap::new(), - schedule_enum: ident_gen.mk_ident(None, false), - schedule_fn: Aliases::new(), - tasks: BTreeMap::new(), - timer_queue: ident_gen.mk_ident(None, false), - ident_gen, - } - } -} + let const_app_tq = timer_queue(app, analysis); -struct Resources { - alias: Ident, - decl: proc_macro2::TokenStream, -} + let const_app_schedule = schedule(app); -pub fn app(app: &App, analysis: &Analysis) -> TokenStream { - let mut ctxt = Context::default(); + let assertion_stmts = assertions(app, analysis); - let resources = resources(&mut ctxt, &app, analysis); + let pre_init_stmts = pre_init(&app, analysis); - let tasks = tasks(&mut ctxt, &app, analysis); + let ( + const_app_init, + mod_init, + init_locals, + init_resources, + init_late_resources, + user_init, + call_init, + ) = init(app, analysis); - let (dispatchers_data, dispatchers) = dispatchers(&mut ctxt, &app, analysis); + let post_init_stmts = post_init(&app, analysis); - let (init_fn, has_late_resources) = init(&mut ctxt, &app, analysis); - let init_arg = if cfg!(feature = "timer-queue") { - quote!(rtfm::Peripherals { - CBP: p.CBP, - CPUID: p.CPUID, - DCB: &mut p.DCB, - FPB: p.FPB, - FPU: p.FPU, - ITM: p.ITM, - MPU: p.MPU, - SCB: &mut p.SCB, - TPIU: p.TPIU, - }) - } else { - quote!(rtfm::Peripherals { - CBP: p.CBP, - CPUID: p.CPUID, - DCB: p.DCB, - DWT: p.DWT, - FPB: p.FPB, - FPU: p.FPU, - ITM: p.ITM, - MPU: p.MPU, - SCB: &mut p.SCB, - SYST: p.SYST, - TPIU: p.TPIU, - }) - }; + let (const_app_idle, mod_idle, idle_locals, idle_resources, user_idle, call_idle) = + idle(app, analysis); - let init = &ctxt.init; - let init_phase = if has_late_resources { - let assigns = app - .resources - .iter() - .filter_map(|(name, res)| { - if res.expr.is_none() { - let alias = &ctxt.statics[name]; - - Some(quote!(#alias.write(res.#name);)) - } else { - None - } - }) - .collect::>(); - - quote!( - let res = #init(#init_arg); - #(#assigns)* - ) - } else { - quote!(#init(#init_arg);) - }; - - let post_init = post_init(&ctxt, &app, analysis); - - let (idle_fn, idle_expr) = idle(&mut ctxt, &app, analysis); - - let exceptions = exceptions(&mut ctxt, app, analysis); - - let (root_interrupts, scoped_interrupts) = interrupts(&mut ctxt, app, analysis); - - let spawn = spawn(&mut ctxt, app, analysis); - - let schedule = match () { - #[cfg(feature = "timer-queue")] - () => schedule(&ctxt, app), - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let timer_queue = timer_queue(&mut ctxt, app, analysis); - - let pre_init = pre_init(&ctxt, &app, analysis); - - let assertions = assertions(app, analysis); - - let main = ctxt.ident_gen.mk_ident(None, false); + let device = &app.args.device; quote!( - #resources + #user_init - #spawn + #user_idle - #timer_queue + #(#user_exceptions)* - #schedule + #(#user_interrupts)* - #dispatchers_data + #(#user_tasks)* - #(#exceptions)* + #mod_resources - #root_interrupts + #init_locals - const APP: () = { - #scoped_interrupts + #init_resources - #(#dispatchers)* + #init_late_resources + + #mod_init + + #idle_locals + + #idle_resources + + #mod_idle + + #(#exception_locals)* + + #(#exception_resources)* + + #(#exception_mods)* + + #(#interrupt_locals)* + + #(#interrupt_resources)* + + #(#interrupt_mods)* + + #(#task_locals)* + + #(#task_resources)* + + #(#task_mods)* + + /// Implementation details + const #name: () = { + // always include the device crate, which contains the vector table + use #device as _; + + #(#const_app_resources)* + + #const_app_init + + #const_app_idle + + #(#const_app_exceptions)* + + #(#const_app_interrupts)* + + #(#const_app_dispatchers)* + + #(#const_app_tasks)* + + #(#const_app_spawn)* + + #(#const_app_tq)* + + #(#const_app_schedule)* + + #[no_mangle] + unsafe fn main() -> ! { + #(#assertion_stmts)* + + #(#pre_init_stmts)* + + #call_init + + #(#post_init_stmts)* + + #call_idle + } }; - - #(#tasks)* - - #init_fn - - #idle_fn - - #[export_name = "main"] - #[allow(unsafe_code)] - #[doc(hidden)] - unsafe fn #main() -> ! { - #assertions - - rtfm::export::interrupt::disable(); - - #pre_init - - #init_phase - - #post_init - - rtfm::export::interrupt::enable(); - - #idle_expr - } ) .into() } -fn resources(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { - let mut items = vec![]; - let mut module = vec![]; +/* Main functions */ +/// In this pass we generate a static variable and a resource proxy for each resource +/// +/// If the user specified a resource like this: +/// +/// ``` +/// #[rtfm::app(device = ..)] +/// const APP: () = { +/// static mut X: UserDefinedStruct = (); +/// static mut Y: u64 = 0; +/// static mut Z: u32 = 0; +/// } +/// ``` +/// +/// We'll generate code like this: +/// +/// - `const_app` +/// +/// ``` +/// const APP: () = { +/// static mut X: MaybeUninit = MaybeUninit::uninit(); +/// static mut Y: u64 = 0; +/// static mut Z: u32 = 0; +/// +/// impl<'a> Mutex for resources::X<'a> { .. } +/// +/// impl<'a> Mutex for resources::Y<'a> { .. } +/// +/// // but not for `Z` because it's not shared and thus requires no proxy +/// }; +/// ``` +/// +/// - `mod_resources` +/// +/// ``` +/// mod resources { +/// pub struct X<'a> { +/// priority: &'a Priority, +/// } +/// +/// impl<'a> X<'a> { +/// pub unsafe fn new(priority: &'a Priority) -> Self { +/// X { priority } +/// } +/// +/// pub unsafe fn priority(&self) -> &Priority { +/// self.priority +/// } +/// } +/// +/// // same thing for `Y` +/// +/// // but not for `Z` +/// } +/// ``` +fn resources( + app: &App, + analysis: &Analysis, +) -> ( + // const_app + Vec, + // mod_resources + proc_macro2::TokenStream, +) { + let mut const_app = vec![]; + let mut mod_resources = vec![]; + for (name, res) in &app.resources { let cfgs = &res.cfgs; let attrs = &res.attrs; - let mut_ = &res.mutability; let ty = &res.ty; - let expr = &res.expr; - if res.singleton { - items.push(quote!( + if let Some(expr) = res.expr.as_ref() { + const_app.push(quote!( #(#attrs)* - pub static #mut_ #name: #ty = #expr; + #(#cfgs)* + static mut #name: #ty = #expr; )); - - let alias = ctxt.ident_gen.mk_ident(None, true); // XXX is randomness required? - if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { - items.push(mk_resource( - ctxt, - cfgs, - name, - quote!(#name), - *ceiling, - quote!(&mut <#name as owned_singleton::Singleton>::new()), - app, - Some(&mut module), - )) - } - - ctxt.statics.insert(name.clone(), alias); } else { - let alias = ctxt.ident_gen.mk_ident(None, false); - let symbol = format!("{}::{}", name, alias); - - items.push( - expr.as_ref() - .map(|expr| { - quote!( - #(#attrs)* - #(#cfgs)* - #[doc = #symbol] - static mut #alias: #ty = #expr; - ) - }) - .unwrap_or_else(|| { - quote!( - #(#attrs)* - #(#cfgs)* - #[doc = #symbol] - static mut #alias: rtfm::export::MaybeUninit<#ty> = - rtfm::export::MaybeUninit::uninit(); - ) - }), - ); - - if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { - if res.mutability.is_some() { - let ptr = if res.expr.is_none() { - quote!(unsafe { &mut *#alias.as_mut_ptr() }) - } else { - quote!(unsafe { &mut #alias }) - }; - - items.push(mk_resource( - ctxt, - cfgs, - name, - quote!(#ty), - *ceiling, - ptr, - app, - Some(&mut module), - )); - } - } - - ctxt.statics.insert(name.clone(), alias); + const_app.push(quote!( + #(#attrs)* + #(#cfgs)* + static mut #name: rtfm::export::MaybeUninit<#ty> = + rtfm::export::MaybeUninit::uninit(); + )); } - } - if !module.is_empty() { - items.push(quote!( - /// Resource proxies - pub mod resources { - #(#module)* - } - )); - } - - quote!(#(#items)*) -} - -fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> (proc_macro2::TokenStream, bool) { - let attrs = &app.init.attrs; - let locals = mk_locals(&app.init.statics, true); - let stmts = &app.init.stmts; - // TODO remove in v0.5.x - let assigns = app - .init - .assigns - .iter() - .map(|assign| { - let attrs = &assign.attrs; - if app - .resources - .get(&assign.left) - .map(|r| r.expr.is_none()) - .unwrap_or(false) - { - let alias = &ctxt.statics[&assign.left]; - let expr = &assign.right; - quote!( - #(#attrs)* - unsafe { #alias.write(#expr); } - ) + if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { + let ptr = if res.expr.is_none() { + quote!(#name.as_mut_ptr()) } else { - let left = &assign.left; - let right = &assign.right; - quote!( - #(#attrs)* - #left = #right; - ) - } - }) - .collect::>(); - - let prelude = prelude( - ctxt, - Kind::Init, - &app.init.args.resources, - &app.init.args.spawn, - &app.init.args.schedule, - app, - 255, - analysis, - ); - - let (late_resources, late_resources_ident, ret) = if app.init.returns_late_resources { - // create `LateResources` struct in the root of the crate - let ident = ctxt.ident_gen.mk_ident(None, false); - - let fields = app - .resources - .iter() - .filter_map(|(name, res)| { - if res.expr.is_none() { - let ty = &res.ty; - Some(quote!(pub #name: #ty)) - } else { - None - } - }) - .collect::>(); - - let late_resources = quote!( - #[allow(non_snake_case)] - pub struct #ident { - #(#fields),* - } - ); - - ( - Some(late_resources), - Some(ident), - Some(quote!(-> init::LateResources)), - ) - } else { - (None, None, None) - }; - let has_late_resources = late_resources.is_some(); - - let module = module( - ctxt, - Kind::Init, - !app.init.args.schedule.is_empty(), - !app.init.args.spawn.is_empty(), - app, - late_resources_ident, - ); - - #[cfg(feature = "timer-queue")] - let baseline = &ctxt.baseline; - let baseline_let = match () { - #[cfg(feature = "timer-queue")] - () => quote!(let ref #baseline = rtfm::Instant::artificial(0);), - - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let start_let = match () { - #[cfg(feature = "timer-queue")] - () => quote!( - #[allow(unused_variables)] - let start = *#baseline; - ), - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let unsafety = &app.init.unsafety; - let device = &app.args.device; - let init = &ctxt.init; - ( - quote!( - #late_resources - - #module - - // unsafe trampoline to deter end-users from calling this non-reentrant function - #(#attrs)* - unsafe fn #init(core: rtfm::Peripherals) #ret { - #[inline(always)] - #unsafety fn init(mut core: rtfm::Peripherals) #ret { - #(#locals)* - - #baseline_let - - #prelude - - let mut device = unsafe { #device::Peripherals::steal() }; - - #start_let - - #(#stmts)* - - #(#assigns)* - } - - init(core) - } - ), - has_late_resources, - ) -} - -fn post_init(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { - let mut exprs = vec![]; - - let device = &app.args.device; - let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); - for (handler, exception) in &app.exceptions { - let name = exception.args.binds(handler); - let priority = exception.args.priority; - - // compile time assert that the priority is supported by the device - exprs.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); - - exprs.push(quote!(p.SCB.set_priority( - rtfm::export::SystemHandler::#name, - ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), - ))); - } - - if !analysis.timer_queue.tasks.is_empty() { - let priority = analysis.timer_queue.priority; - - // compile time assert that the priority is supported by the device - exprs.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); - - exprs.push(quote!(p.SCB.set_priority( - rtfm::export::SystemHandler::SysTick, - ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), - ))); - } - - if app.idle.is_none() { - // Set SLEEPONEXIT bit to enter sleep mode when returning from ISR - exprs.push(quote!(p.SCB.scr.modify(|r| r | 1 << 1))); - } - - // Enable and start the system timer - if !analysis.timer_queue.tasks.is_empty() { - let tq = &ctxt.timer_queue; - exprs.push( - quote!((*#tq.as_mut_ptr()).syst.set_clock_source(rtfm::export::SystClkSource::Core)), - ); - exprs.push(quote!((*#tq.as_mut_ptr()).syst.enable_counter())); - } - - // Enable cycle counter - if cfg!(feature = "timer-queue") { - exprs.push(quote!(p.DCB.enable_trace())); - exprs.push(quote!(p.DWT.enable_cycle_counter())); - } - - quote!(#(#exprs;)*) -} - -/// This function creates creates a module for `init` / `idle` / a `task` (see `kind` argument) -fn module( - ctxt: &mut Context, - kind: Kind, - schedule: bool, - spawn: bool, - app: &App, - late_resources: Option, -) -> proc_macro2::TokenStream { - let mut items = vec![]; - let mut fields = vec![]; - - let name = kind.ident(); - let priority = &ctxt.priority; - let device = &app.args.device; - - let mut lt = None; - match kind { - Kind::Init => { - if cfg!(feature = "timer-queue") { - fields.push(quote!( - /// System start time = `Instant(0 /* cycles */)` - pub start: rtfm::Instant, - )); - } - - fields.push(quote!( - /// Core (Cortex-M) peripherals - pub core: rtfm::Peripherals<'a>, - /// Device specific peripherals - pub device: #device::Peripherals, - )); - lt = Some(quote!('a)); - } - Kind::Idle => {} - Kind::Exception(_) | Kind::Interrupt(_) => { - if cfg!(feature = "timer-queue") { - fields.push(quote!( - /// Time at which this handler started executing - pub start: rtfm::Instant, - )); - } - } - Kind::Task(_) => { - if cfg!(feature = "timer-queue") { - fields.push(quote!( - /// The time at which this task was scheduled to run - pub scheduled: rtfm::Instant, - )); - } - } - } - - if schedule { - lt = Some(quote!('a)); - - fields.push(quote!( - /// Tasks that can be scheduled from this context - pub schedule: Schedule<'a>, - )); - - items.push(quote!( - /// Tasks that can be scheduled from this context - #[derive(Clone, Copy)] - pub struct Schedule<'a> { - #[doc(hidden)] - pub #priority: &'a rtfm::export::Priority, - } - )); - } - - if spawn { - lt = Some(quote!('a)); - - fields.push(quote!( - /// Tasks that can be spawned from this context - pub spawn: Spawn<'a>, - )); - - if kind.is_idle() { - items.push(quote!( - /// Tasks that can be spawned from this context - #[derive(Clone, Copy)] - pub struct Spawn<'a> { - #[doc(hidden)] - pub #priority: &'a rtfm::export::Priority, - } - )); - } else { - let baseline_field = match () { - #[cfg(feature = "timer-queue")] - () => { - let baseline = &ctxt.baseline; - quote!( - // NOTE this field is visible so we use a shared reference to make it - // immutable - #[doc(hidden)] - pub #baseline: &'a rtfm::Instant, - ) - } - #[cfg(not(feature = "timer-queue"))] - () => quote!(), + quote!(&mut #name) }; - items.push(quote!( - /// Tasks that can be spawned from this context - #[derive(Clone, Copy)] - pub struct Spawn<'a> { - #baseline_field - #[doc(hidden)] - pub #priority: &'a rtfm::export::Priority, + mod_resources.push(quote!( + pub struct #name<'a> { + priority: &'a Priority, } + + impl<'a> #name<'a> { + #[inline(always)] + pub unsafe fn new(priority: &'a Priority) -> Self { + #name { priority } + } + + #[inline(always)] + pub unsafe fn priority(&self) -> &Priority { + self.priority + } + } + )); + + const_app.push(impl_mutex( + app, + cfgs, + true, + name, + quote!(#ty), + *ceiling, + ptr, )); } } - let mut root = None; - if let Some(resources) = ctxt.resources.get(&kind) { - lt = Some(quote!('a)); - - root = Some(resources.decl.clone()); - - let alias = &resources.alias; - items.push(quote!( - #[doc(inline)] - pub use super::#alias as Resources; - )); - - fields.push(quote!( - /// Resources available in this context - pub resources: Resources<'a>, - )); - }; - - let doc = match kind { - Kind::Exception(_) => "Exception handler", - Kind::Idle => "Idle loop", - Kind::Init => "Initialization function", - Kind::Interrupt(_) => "Interrupt handler", - Kind::Task(_) => "Software task", - }; - - if let Some(late_resources) = late_resources { - items.push(quote!( - pub use super::#late_resources as LateResources; - )); - } - - quote!( - #root - - #[doc = #doc] - #[allow(non_snake_case)] - pub mod #name { - /// Variables injected into this context by the `app` attribute - pub struct Context<#lt> { - #(#fields)* - } - - #(#items)* - } - ) -} - -/// The prelude injects `resources`, `spawn`, `schedule` and `start` / `scheduled` (all values) into -/// a function scope -fn prelude( - ctxt: &mut Context, - kind: Kind, - resources: &Idents, - spawn: &Idents, - schedule: &Idents, - app: &App, - logical_prio: u8, - analysis: &Analysis, -) -> proc_macro2::TokenStream { - let mut items = vec![]; - - let lt = if kind.runs_once() { - quote!('static) - } else { - quote!('a) - }; - - let module = kind.ident(); - - let priority = &ctxt.priority; - if !resources.is_empty() { - let mut defs = vec![]; - let mut exprs = vec![]; - - // NOTE This field is just to avoid unused type parameter errors around `'a` - defs.push(quote!(#[allow(dead_code)] pub #priority: &'a rtfm::export::Priority)); - exprs.push(parse_quote!(#priority)); - - let mut may_call_lock = false; - let mut needs_unsafe = false; - for name in resources { - let res = &app.resources[name]; - let cfgs = &res.cfgs; - - let initialized = res.expr.is_some(); - let singleton = res.singleton; - let mut_ = res.mutability; - let ty = &res.ty; - - if kind.is_init() { - let mut force_mut = false; - if !analysis.ownerships.contains_key(name) { - // owned by Init - if singleton { - needs_unsafe = true; - defs.push(quote!( - #(#cfgs)* - pub #name: #name - )); - exprs.push(quote!( - #(#cfgs)* - #name: <#name as owned_singleton::Singleton>::new() - )); - continue; - } else { - defs.push(quote!( - #(#cfgs)* - pub #name: &'static #mut_ #ty - )); - } - } else { - // owned by someone else - if singleton { - needs_unsafe = true; - defs.push(quote!( - #(#cfgs)* - pub #name: &'a mut #name - )); - exprs.push(quote!( - #(#cfgs)* - #name: &mut <#name as owned_singleton::Singleton>::new() - )); - continue; - } else { - force_mut = true; - defs.push(quote!( - #(#cfgs)* - pub #name: &'a mut #ty - )); - } - } - - let alias = &ctxt.statics[name]; - // Resources assigned to init are always const initialized - needs_unsafe = true; - if force_mut { - exprs.push(quote!( - #(#cfgs)* - #name: &mut #alias - )); - } else { - exprs.push(quote!( - #(#cfgs)* - #name: &#mut_ #alias - )); - } - } else { - let ownership = &analysis.ownerships[name]; - let mut exclusive = false; - - if ownership.needs_lock(logical_prio) { - may_call_lock = true; - if singleton { - if mut_.is_none() { - needs_unsafe = true; - defs.push(quote!( - #(#cfgs)* - pub #name: &'a #name - )); - exprs.push(quote!( - #(#cfgs)* - #name: &<#name as owned_singleton::Singleton>::new() - )); - continue; - } else { - // Generate a resource proxy - defs.push(quote!( - #(#cfgs)* - pub #name: resources::#name<'a> - )); - exprs.push(quote!( - #(#cfgs)* - #name: resources::#name { #priority } - )); - continue; - } - } else { - if mut_.is_none() { - defs.push(quote!( - #(#cfgs)* - pub #name: &'a #ty - )); - } else { - // Generate a resource proxy - defs.push(quote!( - #(#cfgs)* - pub #name: resources::#name<'a> - )); - exprs.push(quote!( - #(#cfgs)* - #name: resources::#name { #priority } - )); - continue; - } - } - } else { - if singleton { - if kind.runs_once() { - needs_unsafe = true; - defs.push(quote!( - #(#cfgs)* - pub #name: #name - )); - exprs.push(quote!( - #(#cfgs)* - #name: <#name as owned_singleton::Singleton>::new() - )); - } else { - needs_unsafe = true; - if ownership.is_owned() || mut_.is_none() { - defs.push(quote!( - #(#cfgs)* - pub #name: &'a #mut_ #name - )); - // XXX is randomness required? - let alias = ctxt.ident_gen.mk_ident(None, true); - items.push(quote!( - #(#cfgs)* - let #mut_ #alias = unsafe { - <#name as owned_singleton::Singleton>::new() - }; - )); - exprs.push(quote!( - #(#cfgs)* - #name: &#mut_ #alias - )); - } else { - may_call_lock = true; - defs.push(quote!( - #(#cfgs)* - pub #name: rtfm::Exclusive<'a, #name> - )); - // XXX is randomness required? - let alias = ctxt.ident_gen.mk_ident(None, true); - items.push(quote!( - #(#cfgs)* - let #mut_ #alias = unsafe { - <#name as owned_singleton::Singleton>::new() - }; - )); - exprs.push(quote!( - #(#cfgs)* - #name: rtfm::Exclusive(&mut #alias) - )); - } - } - continue; - } else { - if ownership.is_owned() || mut_.is_none() { - defs.push(quote!( - #(#cfgs)* - pub #name: &#lt #mut_ #ty - )); - } else { - exclusive = true; - may_call_lock = true; - defs.push(quote!( - #(#cfgs)* - pub #name: rtfm::Exclusive<#lt, #ty> - )); - } - } - } - - let alias = &ctxt.statics[name]; - needs_unsafe = true; - if initialized { - if exclusive { - exprs.push(quote!( - #(#cfgs)* - #name: rtfm::Exclusive(&mut #alias) - )); - } else { - exprs.push(quote!( - #(#cfgs)* - #name: &#mut_ #alias - )); - } - } else { - let expr = if mut_.is_some() { - quote!(&mut *#alias.as_mut_ptr()) - } else { - quote!(&*#alias.as_ptr()) - }; - - if exclusive { - exprs.push(quote!( - #(#cfgs)* - #name: rtfm::Exclusive(#expr) - )); - } else { - exprs.push(quote!( - #(#cfgs)* - #name: #expr - )); - } - } - } - } - - let alias = ctxt.ident_gen.mk_ident(None, false); - let unsafety = if needs_unsafe { - Some(quote!(unsafe)) - } else { - None - }; - - let defs = &defs; - let doc = format!("`{}::Resources`", kind.ident().to_string()); - let decl = quote!( - #[doc = #doc] - #[allow(non_snake_case)] - pub struct #alias<'a> { #(#defs,)* } - ); - items.push(quote!( - #[allow(unused_variables)] - #[allow(unsafe_code)] - #[allow(unused_mut)] - let mut resources = #unsafety { #alias { #(#exprs,)* } }; - )); - - ctxt.resources - .insert(kind.clone(), Resources { alias, decl }); - - if may_call_lock { - items.push(quote!( - use rtfm::Mutex; - )); - } - } - - if !spawn.is_empty() { - if kind.is_idle() { - items.push(quote!( - #[allow(unused_variables)] - let spawn = #module::Spawn { #priority }; - )); - } else { - let baseline_expr = match () { - #[cfg(feature = "timer-queue")] - () => { - let baseline = &ctxt.baseline; - quote!(#baseline) - } - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - items.push(quote!( - #[allow(unused_variables)] - let spawn = #module::Spawn { #priority, #baseline_expr }; - )); - } - } - - if !schedule.is_empty() { - // Populate `schedule_fn` - for task in schedule { - if ctxt.schedule_fn.contains_key(task) { - continue; - } - - ctxt.schedule_fn - .insert(task.clone(), ctxt.ident_gen.mk_ident(None, false)); - } - - items.push(quote!( - #[allow(unused_imports)] - use rtfm::U32Ext; - - #[allow(unused_variables)] - let schedule = #module::Schedule { #priority }; - )); - } - - if items.is_empty() { + let mod_resources = if mod_resources.is_empty() { quote!() } else { - quote!( - let ref #priority = unsafe { rtfm::export::Priority::new(#logical_prio) }; + quote!(mod resources { + use rtfm::export::Priority; - #(#items)* - ) - } + #(#mod_resources)* + }) + }; + + (const_app, mod_resources) } -fn idle( - ctxt: &mut Context, +// For each exception we'll generate: +// +// - at the root of the crate: +// - a ${name}Resources struct (maybe) +// - a ${name}Locals struct +// +// - a module named after the exception, see the `module` function for more details +// +// - hidden in `const APP` +// - the ${name}Resources constructor +// +// - the exception handler specified by the user +fn exceptions( app: &App, analysis: &Analysis, -) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { - if let Some(idle) = app.idle.as_ref() { - let attrs = &idle.attrs; - let locals = mk_locals(&idle.statics, true); - let stmts = &idle.stmts; +) -> ( + // const_app + Vec, + // exception_mods + Vec, + // exception_locals + Vec, + // exception_resources + Vec, + // user_exceptions + Vec, +) { + let mut const_app = vec![]; + let mut mods = vec![]; + let mut locals_structs = vec![]; + let mut resources_structs = vec![]; + let mut user_code = vec![]; - let prelude = prelude( - ctxt, - Kind::Idle, - &idle.args.resources, - &idle.args.spawn, - &idle.args.schedule, - app, - 0, - analysis, - ); + for (name, exception) in &app.exceptions { + let (let_instant, instant) = if cfg!(feature = "timer-queue") { + ( + Some(quote!(let instant = rtfm::Instant::now();)), + Some(quote!(, instant)), + ) + } else { + (None, None) + }; + let priority = &exception.args.priority; + let symbol = exception.args.binds(name); + const_app.push(quote!( + #[allow(non_snake_case)] + #[no_mangle] + unsafe fn #symbol() { + const PRIORITY: u8 = #priority; - let module = module( - ctxt, - Kind::Idle, - !idle.args.schedule.is_empty(), - !idle.args.spawn.is_empty(), - app, - None, - ); + #let_instant - let unsafety = &idle.unsafety; - let idle = &ctxt.idle; + rtfm::export::run(PRIORITY, || { + crate::#name( + #name::Locals::new(), + #name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant) + ) + }); + } + )); - ( - quote!( - #module - - // unsafe trampoline to deter end-users from calling this non-reentrant function - #(#attrs)* - unsafe fn #idle() -> ! { - #[inline(always)] - #unsafety fn idle() -> ! { - #(#locals)* - - #prelude - - #(#stmts)* - } - - idle() - } - ), - quote!(#idle()), - ) - } else { - ( - quote!(), - quote!(loop { - rtfm::export::wfi(); - }), - ) - } -} - -fn exceptions(ctxt: &mut Context, app: &App, analysis: &Analysis) -> Vec { - app.exceptions - .iter() - .map(|(ident, exception)| { - let attrs = &exception.attrs; - let stmts = &exception.stmts; - - let kind = Kind::Exception(ident.clone()); - let prelude = prelude( - ctxt, - kind.clone(), - &exception.args.resources, - &exception.args.spawn, - &exception.args.schedule, - app, + let mut needs_lt = false; + if !exception.args.resources.is_empty() { + let (item, constructor) = resources_struct( + Kind::Exception(name.clone()), exception.args.priority, + &mut needs_lt, + app, analysis, ); - let module = module( - ctxt, - kind, - !exception.args.schedule.is_empty(), - !exception.args.spawn.is_empty(), - app, - None, - ); + resources_structs.push(item); - #[cfg(feature = "timer-queue")] - let baseline = &ctxt.baseline; - let baseline_let = match () { - #[cfg(feature = "timer-queue")] - () => quote!(let ref #baseline = rtfm::Instant::now();), - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; + const_app.push(constructor); + } - let start_let = match () { - #[cfg(feature = "timer-queue")] - () => quote!( - #[allow(unused_variables)] - let start = *#baseline; - ), - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let locals = mk_locals(&exception.statics, false); - let symbol = exception.args.binds(ident).to_string(); - let alias = ctxt.ident_gen.mk_ident(None, false); - let unsafety = &exception.unsafety; - quote!( - #module - - // unsafe trampoline to deter end-users from calling this non-reentrant function - #[export_name = #symbol] - #(#attrs)* - unsafe fn #alias() { - #[inline(always)] - #unsafety fn exception() { - #(#locals)* - - #baseline_let - - #prelude - - #start_let - - rtfm::export::run(move || { - #(#stmts)* - }) - } - - exception() - } - ) - }) - .collect() -} - -fn interrupts( - ctxt: &mut Context, - app: &App, - analysis: &Analysis, -) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { - let mut root = vec![]; - let mut scoped = vec![]; - - for (ident, interrupt) in &app.interrupts { - let attrs = &interrupt.attrs; - let stmts = &interrupt.stmts; - - let kind = Kind::Interrupt(ident.clone()); - let prelude = prelude( - ctxt, - kind.clone(), - &interrupt.args.resources, - &interrupt.args.spawn, - &interrupt.args.schedule, + mods.push(module( + Kind::Exception(name.clone()), + (!exception.args.resources.is_empty(), needs_lt), + !exception.args.schedule.is_empty(), + !exception.args.spawn.is_empty(), + false, app, - interrupt.args.priority, - analysis, - ); - - root.push(module( - ctxt, - kind, - !interrupt.args.schedule.is_empty(), - !interrupt.args.spawn.is_empty(), - app, - None, )); - #[cfg(feature = "timer-queue")] - let baseline = &ctxt.baseline; - let baseline_let = match () { - #[cfg(feature = "timer-queue")] - () => quote!(let ref #baseline = rtfm::Instant::now();), - #[cfg(not(feature = "timer-queue"))] - () => quote!(), + let attrs = &exception.attrs; + let context = &exception.context; + let (locals, lets) = locals(Kind::Exception(name.clone()), &exception.statics); + locals_structs.push(locals); + let use_u32ext = if cfg!(feature = "timer-queue") { + Some(quote!( + use rtfm::U32Ext as _; + )) + } else { + None }; - - let start_let = match () { - #[cfg(feature = "timer-queue")] - () => quote!( - #[allow(unused_variables)] - let start = *#baseline; - ), - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let locals = mk_locals(&interrupt.statics, false); - let alias = ctxt.ident_gen.mk_ident(None, false); - let symbol = interrupt.args.binds(ident).to_string(); - let unsafety = &interrupt.unsafety; - scoped.push(quote!( - // unsafe trampoline to deter end-users from calling this non-reentrant function + let stmts = &exception.stmts; + user_code.push(quote!( #(#attrs)* - #[export_name = #symbol] - unsafe fn #alias() { - #[inline(always)] - #unsafety fn interrupt() { - #(#locals)* + #[allow(non_snake_case)] + fn #name(__locals: #name::Locals, #context: #name::Context) { + #use_u32ext + use rtfm::Mutex as _; - #baseline_let + #(#lets;)* - #prelude - - #start_let - - rtfm::export::run(move || { - #(#stmts)* - }) - } - - interrupt() + #(#stmts)* } )); } - (quote!(#(#root)*), quote!(#(#scoped)*)) + ( + const_app, + mods, + locals_structs, + resources_structs, + user_code, + ) } -fn tasks(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { - let mut items = vec![]; +// For each interrupt we'll generate: +// +// - at the root of the crate: +// - a ${name}Resources struct (maybe) +// - a ${name}Locals struct +// +// - a module named after the exception, see the `module` function for more details +// +// - hidden in `const APP` +// - the ${name}Resources constructor +// +// - the interrupt handler specified by the user +fn interrupts( + app: &App, + analysis: &Analysis, +) -> ( + // const_app + Vec, + // interrupt_mods + Vec, + // interrupt_locals + Vec, + // interrupt_resources + Vec, + // user_exceptions + Vec, +) { + let mut const_app = vec![]; + let mut mods = vec![]; + let mut locals_structs = vec![]; + let mut resources_structs = vec![]; + let mut user_code = vec![]; - // first pass to generate buffers (statics and resources) and spawn aliases - for (name, task) in &app.tasks { - #[cfg(feature = "timer-queue")] - let scheduleds_alias = ctxt.ident_gen.mk_ident(None, false); - let free_alias = ctxt.ident_gen.mk_ident(None, false); - let inputs_alias = ctxt.ident_gen.mk_ident(None, false); - let task_alias = ctxt.ident_gen.mk_ident(Some(&name.to_string()), false); - - let inputs = &task.inputs; - - let ty = tuple_ty(inputs); - - let capacity = analysis.capacities[name]; - let capacity_lit = mk_capacity_literal(capacity); - let capacity_ty = mk_typenum_capacity(capacity, true); - - let resource = mk_resource( - ctxt, - &[], - &free_alias, - quote!(rtfm::export::FreeQueue<#capacity_ty>), - *analysis.free_queues.get(name).unwrap_or(&0), - if cfg!(feature = "nightly") { - quote!(&mut #free_alias) - } else { - quote!(#free_alias.get_mut()) - }, - app, - None, - ); - - let scheduleds_static = match () { - #[cfg(feature = "timer-queue")] - () => { - let scheduleds_symbol = format!("{}::SCHEDULED_TIMES::{}", name, scheduleds_alias); - - if cfg!(feature = "nightly") { - let inits = - (0..capacity).map(|_| quote!(rtfm::export::MaybeUninit::uninit())); - - quote!( - #[doc = #scheduleds_symbol] - static mut #scheduleds_alias: - [rtfm::export::MaybeUninit; #capacity_lit] = - [#(#inits),*]; - ) - } else { - quote!( - #[doc = #scheduleds_symbol] - static mut #scheduleds_alias: - rtfm::export::MaybeUninit<[rtfm::Instant; #capacity_lit]> = - rtfm::export::MaybeUninit::uninit(); - ) - } - } - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let inputs_symbol = format!("{}::INPUTS::{}", name, inputs_alias); - let free_symbol = format!("{}::FREE_QUEUE::{}", name, free_alias); - if cfg!(feature = "nightly") { - let inits = (0..capacity).map(|_| quote!(rtfm::export::MaybeUninit::uninit())); - - items.push(quote!( - #[doc = #free_symbol] - static mut #free_alias: rtfm::export::FreeQueue<#capacity_ty> = unsafe { - rtfm::export::FreeQueue::new_sc() - }; - - #[doc = #inputs_symbol] - static mut #inputs_alias: [rtfm::export::MaybeUninit<#ty>; #capacity_lit] = - [#(#inits),*]; - )); + let device = &app.args.device; + for (name, interrupt) in &app.interrupts { + let (let_instant, instant) = if cfg!(feature = "timer-queue") { + ( + Some(quote!(let instant = rtfm::Instant::now();)), + Some(quote!(, instant)), + ) } else { - items.push(quote!( - #[doc = #free_symbol] - static mut #free_alias: rtfm::export::MaybeUninit< - rtfm::export::FreeQueue<#capacity_ty> - > = rtfm::export::MaybeUninit::uninit(); + (None, None) + }; + let priority = &interrupt.args.priority; + let symbol = interrupt.args.binds(name); + const_app.push(quote!( + #[allow(non_snake_case)] + #[no_mangle] + unsafe fn #symbol() { + const PRIORITY: u8 = #priority; - #[doc = #inputs_symbol] - static mut #inputs_alias: rtfm::export::MaybeUninit<[#ty; #capacity_lit]> = - rtfm::export::MaybeUninit::uninit(); + #let_instant + // check that this interrupt exists + let _ = #device::Interrupt::#symbol; + + rtfm::export::run(PRIORITY, || { + crate::#name( + #name::Locals::new(), + #name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant) + ) + }); + } + )); + + let mut needs_lt = false; + if !interrupt.args.resources.is_empty() { + let (item, constructor) = resources_struct( + Kind::Interrupt(name.clone()), + interrupt.args.priority, + &mut needs_lt, + app, + analysis, + ); + + resources_structs.push(item); + + const_app.push(constructor); + } + + mods.push(module( + Kind::Interrupt(name.clone()), + (!interrupt.args.resources.is_empty(), needs_lt), + !interrupt.args.schedule.is_empty(), + !interrupt.args.spawn.is_empty(), + false, + app, + )); + + let attrs = &interrupt.attrs; + let context = &interrupt.context; + let use_u32ext = if cfg!(feature = "timer-queue") { + Some(quote!( + use rtfm::U32Ext as _; + )) + } else { + None + }; + let (locals, lets) = locals(Kind::Interrupt(name.clone()), &interrupt.statics); + locals_structs.push(locals); + let stmts = &interrupt.stmts; + user_code.push(quote!( + #(#attrs)* + #[allow(non_snake_case)] + fn #name(__locals: #name::Locals, #context: #name::Context) { + #use_u32ext + use rtfm::Mutex as _; + + #(#lets;)* + + #(#stmts)* + } + )); + } + + ( + const_app, + mods, + locals_structs, + resources_structs, + user_code, + ) +} + +// For each task we'll generate: +// +// - at the root of the crate: +// - a ${name}Resources struct (maybe) +// - a ${name}Locals struct +// +// - a module named after the task, see the `module` function for more details +// +// - hidden in `const APP` +// - the ${name}Resources constructor +// - an INPUTS buffer +// - a free queue and a corresponding resource +// - an INSTANTS buffer (if `timer-queue` is enabled) +// +// - the task handler specified by the user +fn tasks( + app: &App, + analysis: &Analysis, +) -> ( + // const_app + Vec, + // task_mods + Vec, + // task_locals + Vec, + // task_resources + Vec, + // user_tasks + Vec, +) { + let mut const_app = vec![]; + let mut mods = vec![]; + let mut locals_structs = vec![]; + let mut resources_structs = vec![]; + let mut user_code = vec![]; + + for (name, task) in &app.tasks { + let inputs = &task.inputs; + let (_, _, _, ty) = regroup_inputs(inputs); + + let cap = analysis.capacities[name]; + let cap_lit = mk_capacity_literal(cap); + let cap_ty = mk_typenum_capacity(cap, true); + + let task_inputs = mk_inputs_ident(name); + let task_instants = mk_instants_ident(name); + let task_fq = mk_fq_ident(name); + + let elems = (0..cap) + .map(|_| quote!(rtfm::export::MaybeUninit::uninit())) + .collect::>(); + + if cfg!(feature = "timer-queue") { + let elems = elems.clone(); + const_app.push(quote!( + /// Buffer that holds the instants associated to the inputs of a task + static mut #task_instants: [rtfm::export::MaybeUninit; #cap_lit] = + [#(#elems,)*]; )); } - items.push(quote!( - #resource - - #scheduleds_static + const_app.push(quote!( + /// Buffer that holds the inputs of a task + static mut #task_inputs: [rtfm::export::MaybeUninit<#ty>; #cap_lit] = + [#(#elems,)*]; )); - ctxt.tasks.insert( - name.clone(), - Task { - alias: task_alias, - free_queue: free_alias, - inputs: inputs_alias, - spawn_fn: ctxt.ident_gen.mk_ident(None, false), + let doc = "Queue version of a free-list that keeps track of empty slots in the previous buffer(s)"; + let fq_ty = quote!(rtfm::export::FreeQueue<#cap_ty>); + let ptr = if cfg!(feature = "nightly") { + const_app.push(quote!( + #[doc = #doc] + static mut #task_fq: #fq_ty = unsafe { + rtfm::export::FreeQueue::u8_sc() + }; + )); - #[cfg(feature = "timer-queue")] - scheduleds: scheduleds_alias, - }, - ); - } + quote!(&mut #task_fq) + } else { + const_app.push(quote!( + #[doc = #doc] + static mut #task_fq: rtfm::export::MaybeUninit<#fq_ty> = + rtfm::export::MaybeUninit::uninit(); + )); - // second pass to generate the actual task function - for (name, task) in &app.tasks { - let inputs = &task.inputs; - let locals = mk_locals(&task.statics, false); - let stmts = &task.stmts; - let unsafety = &task.unsafety; - - let scheduled_let = match () { - #[cfg(feature = "timer-queue")] - () => { - let baseline = &ctxt.baseline; - quote!(let scheduled = *#baseline;) - } - #[cfg(not(feature = "timer-queue"))] - () => quote!(), + quote!(#task_fq.as_mut_ptr()) }; - let prelude = prelude( - ctxt, - Kind::Task(name.clone()), - &task.args.resources, - &task.args.spawn, - &task.args.schedule, - app, - task.args.priority, - analysis, - ); + if let Some(ceiling) = analysis.free_queues.get(name) { + const_app.push(quote!(struct #task_fq<'a> { + priority: &'a rtfm::export::Priority, + })); - items.push(module( - ctxt, + const_app.push(impl_mutex(app, &[], false, &task_fq, fq_ty, *ceiling, ptr)); + } + + let mut needs_lt = false; + if !task.args.resources.is_empty() { + let (item, constructor) = resources_struct( + Kind::Task(name.clone()), + task.args.priority, + &mut needs_lt, + app, + analysis, + ); + + resources_structs.push(item); + + const_app.push(constructor); + } + + mods.push(module( Kind::Task(name.clone()), + (!task.args.resources.is_empty(), needs_lt), !task.args.schedule.is_empty(), !task.args.spawn.is_empty(), + false, app, - None, )); let attrs = &task.attrs; - let cfgs = &task.cfgs; - let task_alias = &ctxt.tasks[name].alias; - let (baseline, baseline_arg) = match () { - #[cfg(feature = "timer-queue")] - () => { - let baseline = &ctxt.baseline; - (quote!(#baseline,), quote!(#baseline: &rtfm::Instant,)) - } - #[cfg(not(feature = "timer-queue"))] - () => (quote!(), quote!()), + let use_u32ext = if cfg!(feature = "timer-queue") { + Some(quote!( + use rtfm::U32Ext as _; + )) + } else { + None }; - let pats = tuple_pat(inputs); - items.push(quote!( - // unsafe trampoline to deter end-users from calling this non-reentrant function + let context = &task.context; + let stmts = &task.stmts; + let (locals_struct, lets) = locals(Kind::Task(name.clone()), &task.statics); + locals_structs.push(locals_struct); + user_code.push(quote!( #(#attrs)* - #(#cfgs)* - unsafe fn #task_alias(#baseline_arg #(#inputs,)*) { - #[inline(always)] - #unsafety fn task(#baseline_arg #(#inputs,)*) { - #(#locals)* + #[allow(non_snake_case)] + fn #name(__locals: #name::Locals, #context: #name::Context #(,#inputs)*) { + use rtfm::Mutex as _; + #use_u32ext - #prelude + #(#lets;)* - #scheduled_let - - #(#stmts)* - } - - task(#baseline #pats) + #(#stmts)* } )); } - quote!(#(#items)*) + ( + const_app, + mods, + locals_structs, + resources_structs, + user_code, + ) } -fn dispatchers( - ctxt: &mut Context, - app: &App, - analysis: &Analysis, -) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { - let mut data = vec![]; - let mut dispatchers = vec![]; +/// For each task dispatcher we'll generate +/// +/// - A static variable that hold the ready queue (`RQ${priority}`) and a resource proxy for it +/// - An enumeration of all the tasks dispatched by this dispatcher `T${priority}` +/// - An interrupt handler that dispatches the tasks +fn dispatchers(app: &App, analysis: &Analysis) -> Vec { + let mut items = vec![]; let device = &app.args.device; for (level, dispatcher) in &analysis.dispatchers { - let ready_alias = ctxt.ident_gen.mk_ident(None, false); - let enum_alias = ctxt.ident_gen.mk_ident(None, false); - let capacity = mk_typenum_capacity(dispatcher.capacity, true); + let rq = mk_rq_ident(*level); + let t = mk_t_ident(*level); + let cap = mk_typenum_capacity(dispatcher.capacity, true); + + let doc = format!( + "Queue of tasks ready to be dispatched at priority level {}", + level + ); + let rq_ty = quote!(rtfm::export::ReadyQueue<#t, #cap>); + let ptr = if cfg!(feature = "nightly") { + items.push(quote!( + #[doc = #doc] + static mut #rq: #rq_ty = unsafe { + rtfm::export::ReadyQueue::u8_sc() + }; + )); + + quote!(&mut #rq) + } else { + items.push(quote!( + #[doc = #doc] + static mut #rq: rtfm::export::MaybeUninit<#rq_ty> = + rtfm::export::MaybeUninit::uninit(); + )); + + quote!(#rq.as_mut_ptr()) + }; + + if let Some(ceiling) = analysis.ready_queues.get(&level) { + items.push(quote!( + struct #rq<'a> { + priority: &'a rtfm::export::Priority, + } + )); + + items.push(impl_mutex(app, &[], false, &rq, rq_ty, *ceiling, ptr)); + } let variants = dispatcher .tasks .iter() .map(|task| { - let task_ = &app.tasks[task]; - let cfgs = &task_.cfgs; + let cfgs = &app.tasks[task].cfgs; quote!( #(#cfgs)* @@ -1456,126 +743,100 @@ fn dispatchers( ) }) .collect::>(); - let symbol = format!("P{}::READY_QUEUE::{}", level, ready_alias); - let e = quote!(rtfm::export); - let ty = quote!(#e::ReadyQueue<#enum_alias, #capacity>); - let ceiling = *analysis.ready_queues.get(&level).unwrap_or(&0); - let resource = mk_resource( - ctxt, - &[], - &ready_alias, - ty.clone(), - ceiling, - if cfg!(feature = "nightly") { - quote!(&mut #ready_alias) - } else { - quote!(#ready_alias.get_mut()) - }, - app, - None, + + let doc = format!( + "Software tasks to be dispatched at priority level {}", + level ); - - if cfg!(feature = "nightly") { - data.push(quote!( - #[doc = #symbol] - static mut #ready_alias: #ty = unsafe { #e::ReadyQueue::new_sc() }; - )); - } else { - data.push(quote!( - #[doc = #symbol] - static mut #ready_alias: #e::MaybeUninit<#ty> = #e::MaybeUninit::uninit(); - )); - } - data.push(quote!( - #[allow(dead_code)] + items.push(quote!( #[allow(non_camel_case_types)] - enum #enum_alias { #(#variants,)* } - - #resource + #[derive(Clone, Copy)] + #[doc = #doc] + enum #t { + #(#variants,)* + } )); let arms = dispatcher .tasks .iter() - .map(|task| { - let task_ = &ctxt.tasks[task]; - let inputs = &task_.inputs; - let free = &task_.free_queue; - let alias = &task_.alias; + .map(|name| { + let task = &app.tasks[name]; + let cfgs = &task.cfgs; + let (_, tupled, pats, _) = regroup_inputs(&task.inputs); - let task__ = &app.tasks[task]; - let pats = tuple_pat(&task__.inputs); - let cfgs = &task__.cfgs; + let inputs = mk_inputs_ident(name); + let fq = mk_fq_ident(name); - let baseline_let; - let call; - match () { - #[cfg(feature = "timer-queue")] - () => { - let scheduleds = &task_.scheduleds; - let scheduled = if cfg!(feature = "nightly") { - quote!(#scheduleds.get_unchecked(usize::from(index)).as_ptr()) - } else { - quote!(#scheduleds.get_ref().get_unchecked(usize::from(index))) - }; - - baseline_let = quote!( - let baseline = ptr::read(#scheduled); - ); - call = quote!(#alias(&baseline, #pats)); - } - #[cfg(not(feature = "timer-queue"))] - () => { - baseline_let = quote!(); - call = quote!(#alias(#pats)); - } + let input = quote!(#inputs.get_unchecked(usize::from(index)).read()); + let fq = if cfg!(feature = "nightly") { + quote!(#fq) + } else { + quote!((*#fq.as_mut_ptr())) }; - let (free_, input) = if cfg!(feature = "nightly") { + let (let_instant, _instant) = if cfg!(feature = "timer-queue") { + let instants = mk_instants_ident(name); + let instant = quote!(#instants.get_unchecked(usize::from(index)).read()); + ( - quote!(#free), - quote!(#inputs.get_unchecked(usize::from(index)).as_ptr()), + Some(quote!(let instant = #instant;)), + Some(quote!(, instant)), ) } else { - ( - quote!(#free.get_mut()), - quote!(#inputs.get_ref().get_unchecked(usize::from(index))), + (None, None) + }; + + let call = { + let pats = pats.clone(); + + quote!( + #name( + #name::Locals::new(), + #name::Context::new(priority #_instant) + #(,#pats)* + ) ) }; quote!( #(#cfgs)* - #enum_alias::#task => { - #baseline_let - let input = ptr::read(#input); - #free_.split().0.enqueue_unchecked(index); - let (#pats) = input; + #t::#name => { + let #tupled = #input; + #let_instant + #fq.split().0.enqueue_unchecked(index); + let priority = &rtfm::export::Priority::new(PRIORITY); #call } ) }) .collect::>(); + let doc = format!( + "interrupt handler used to dispatch tasks at priority {}", + level + ); let attrs = &dispatcher.attrs; let interrupt = &dispatcher.interrupt; - let symbol = interrupt.to_string(); - let alias = ctxt.ident_gen.mk_ident(None, false); - let ready_alias_ = if cfg!(feature = "nightly") { - quote!(#ready_alias) + let rq = if cfg!(feature = "nightly") { + quote!((&mut #rq)) } else { - quote!(#ready_alias.get_mut()) + quote!((*#rq.as_mut_ptr())) }; - dispatchers.push(quote!( + items.push(quote!( + #[doc = #doc] #(#attrs)* - #[export_name = #symbol] - unsafe fn #alias() { - use core::ptr; + #[no_mangle] + #[allow(non_snake_case)] + unsafe fn #interrupt() { + /// The priority of this interrupt handler + const PRIORITY: u8 = #level; // check that this interrupt exists - let _ = #device::interrupt::#interrupt; + let _ = #device::Interrupt::#interrupt; - rtfm::export::run(|| { - while let Some((task, index)) = #ready_alias_.split().1.dequeue() { + rtfm::export::run(PRIORITY, || { + while let Some((task, index)) = #rq.split().1.dequeue() { match task { #(#arms)* } @@ -1583,269 +844,127 @@ fn dispatchers( }); } )); - - ctxt.dispatchers.insert( - *level, - Dispatcher { - ready_queue: ready_alias, - enum_: enum_alias, - }, - ); } - (quote!(#(#data)*), quote!(#(#dispatchers)*)) + items } -fn spawn(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { +/// Generates all the `Spawn.$task` related code +fn spawn(app: &App, analysis: &Analysis) -> Vec { let mut items = vec![]; - // Generate `spawn` functions - let device = &app.args.device; - let priority = &ctxt.priority; - #[cfg(feature = "timer-queue")] - let baseline = &ctxt.baseline; - for (name, task) in &ctxt.tasks { - let alias = &task.spawn_fn; - let task_ = &app.tasks[name]; - let cfgs = &task_.cfgs; - let free = &task.free_queue; - let level = task_.args.priority; - let dispatcher = &ctxt.dispatchers[&level]; - let ready = &dispatcher.ready_queue; - let enum_ = &dispatcher.enum_; - let dispatcher = &analysis.dispatchers[&level].interrupt; - let inputs = &task.inputs; - let args = &task_.inputs; - let ty = tuple_ty(args); - let pats = tuple_pat(args); - - let scheduleds_write = match () { - #[cfg(feature = "timer-queue")] - () => { - let scheduleds = &ctxt.tasks[name].scheduleds; - if cfg!(feature = "nightly") { - quote!( - ptr::write( - #scheduleds.get_unchecked_mut(usize::from(index)).as_mut_ptr(), - #baseline, - ); - ) - } else { - quote!( - ptr::write( - #scheduleds.get_mut().get_unchecked_mut(usize::from(index)), - #baseline, - ); - ) - } - } - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let baseline_arg = match () { - #[cfg(feature = "timer-queue")] - () => quote!(#baseline: rtfm::Instant,), - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let input = if cfg!(feature = "nightly") { - quote!(#inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr()) - } else { - quote!(#inputs.get_mut().get_unchecked_mut(usize::from(index))) - }; - items.push(quote!( - #[inline(always)] - #(#cfgs)* - unsafe fn #alias( - #baseline_arg - #priority: &rtfm::export::Priority, - #(#args,)* - ) -> Result<(), #ty> { - use core::ptr; - - use rtfm::Mutex; - - if let Some(index) = (#free { #priority }).lock(|f| f.split().1.dequeue()) { - ptr::write(#input, (#pats)); - #scheduleds_write - - #ready { #priority }.lock(|rq| { - rq.split().0.enqueue_unchecked((#enum_::#name, index)) - }); - - rtfm::pend(#device::Interrupt::#dispatcher); - - Ok(()) - } else { - Err((#pats)) - } - } - )) - } - - // Generate `spawn` structs; these call the `spawn` functions generated above - for (name, spawn) in app.spawn_callers() { - if spawn.is_empty() { + let mut seen = BTreeSet::new(); + for (spawner, spawnees) in app.spawn_callers() { + if spawnees.is_empty() { continue; } - #[cfg(feature = "timer-queue")] - let is_idle = name.to_string() == "idle"; - let mut methods = vec![]; - for task in spawn { - let task_ = &app.tasks[task]; - let alias = &ctxt.tasks[task].spawn_fn; - let inputs = &task_.inputs; - let cfgs = &task_.cfgs; - let ty = tuple_ty(inputs); - let pats = tuple_pat(inputs); - let instant = match () { - #[cfg(feature = "timer-queue")] - () => { - if is_idle { - quote!(rtfm::Instant::now(),) - } else { - quote!(*self.#baseline,) + let spawner_is_init = spawner == "init"; + let spawner_is_idle = spawner == "idle"; + for name in spawnees { + let spawnee = &app.tasks[name]; + let cfgs = &spawnee.cfgs; + let (args, _, untupled, ty) = regroup_inputs(&spawnee.inputs); + + if spawner_is_init { + // `init` uses a special spawn implementation; it doesn't use the `spawn_${name}` + // functions which are shared by other contexts + + let body = mk_spawn_body(&spawner, &name, app, analysis); + + let let_instant = if cfg!(feature = "timer-queue") { + Some(quote!(let instant = unsafe { rtfm::Instant::artificial(0) };)) + } else { + None + }; + methods.push(quote!( + #(#cfgs)* + fn #name(&self #(,#args)*) -> Result<(), #ty> { + #let_instant + #body } + )); + } else { + let spawn = mk_spawn_ident(name); + + if !seen.contains(name) { + // generate a `spawn_${name}` function + seen.insert(name); + + let instant = if cfg!(feature = "timer-queue") { + Some(quote!(, instant: rtfm::Instant)) + } else { + None + }; + let body = mk_spawn_body(&spawner, &name, app, analysis); + let args = args.clone(); + items.push(quote!( + #(#cfgs)* + unsafe fn #spawn( + priority: &rtfm::export::Priority + #instant + #(,#args)* + ) -> Result<(), #ty> { + #body + } + )); } - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - methods.push(quote!( - #[allow(unsafe_code)] - #[inline] - #(#cfgs)* - pub fn #task(&self, #(#inputs,)*) -> Result<(), #ty> { - unsafe { #alias(#instant &self.#priority, #pats) } - } - )); + + let (let_instant, instant) = if cfg!(feature = "timer-queue") { + ( + Some(if spawner_is_idle { + quote!(let instant = rtfm::Instant::now();) + } else { + quote!(let instant = self.instant();) + }), + Some(quote!(, instant)), + ) + } else { + (None, None) + }; + methods.push(quote!( + #(#cfgs)* + #[inline(always)] + fn #name(&self #(,#args)*) -> Result<(), #ty> { + unsafe { + #let_instant + #spawn(self.priority() #instant #(,#untupled)*) + } + } + )); + } } + let lt = if spawner_is_init { + None + } else { + Some(quote!('a)) + }; items.push(quote!( - impl<'a> #name::Spawn<'a> { + impl<#lt> #spawner::Spawn<#lt> { #(#methods)* } )); } - quote!(#(#items)*) + items } -#[cfg(feature = "timer-queue")] -fn schedule(ctxt: &Context, app: &App) -> proc_macro2::TokenStream { +/// Generates code related to the timer queue, namely +/// +/// - A static variable that holds the timer queue and a resource proxy for it +/// - The system timer exception, which moves tasks from the timer queue into the ready queues +fn timer_queue(app: &App, analysis: &Analysis) -> Vec { let mut items = vec![]; - // Generate `schedule` functions - let priority = &ctxt.priority; - let timer_queue = &ctxt.timer_queue; - for (task, alias) in &ctxt.schedule_fn { - let task_ = &ctxt.tasks[task]; - let free = &task_.free_queue; - let enum_ = &ctxt.schedule_enum; - let inputs = &task_.inputs; - let scheduleds = &task_.scheduleds; - let task__ = &app.tasks[task]; - let args = &task__.inputs; - let cfgs = &task__.cfgs; - let ty = tuple_ty(args); - let pats = tuple_pat(args); - - let input = if cfg!(feature = "nightly") { - quote!(#inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr()) - } else { - quote!(#inputs.get_mut().get_unchecked_mut(usize::from(index))) - }; - - let scheduled = if cfg!(feature = "nightly") { - quote!(#scheduleds.get_unchecked_mut(usize::from(index)).as_mut_ptr()) - } else { - quote!(#scheduleds.get_mut().get_unchecked_mut(usize::from(index))) - }; - items.push(quote!( - #[inline(always)] - #(#cfgs)* - unsafe fn #alias( - #priority: &rtfm::export::Priority, - instant: rtfm::Instant, - #(#args,)* - ) -> Result<(), #ty> { - use core::ptr; - - use rtfm::Mutex; - - if let Some(index) = (#free { #priority }).lock(|f| f.split().1.dequeue()) { - ptr::write(#input, (#pats)); - ptr::write(#scheduled, instant); - - let nr = rtfm::export::NotReady { - instant, - index, - task: #enum_::#task, - }; - - ({#timer_queue { #priority }}).lock(|tq| tq.enqueue_unchecked(nr)); - - Ok(()) - } else { - Err((#pats)) - } - } - )) - } - - // Generate `Schedule` structs; these call the `schedule` functions generated above - for (name, schedule) in app.schedule_callers() { - if schedule.is_empty() { - continue; - } - - debug_assert!(!schedule.is_empty()); - - let mut methods = vec![]; - for task in schedule { - let alias = &ctxt.schedule_fn[task]; - let task_ = &app.tasks[task]; - let inputs = &task_.inputs; - let cfgs = &task_.cfgs; - let ty = tuple_ty(inputs); - let pats = tuple_pat(inputs); - - methods.push(quote!( - #[inline] - #(#cfgs)* - pub fn #task( - &self, - instant: rtfm::Instant, - #(#inputs,)* - ) -> Result<(), #ty> { - unsafe { #alias(&self.#priority, instant, #pats) } - } - )); - } - - items.push(quote!( - impl<'a> #name::Schedule<'a> { - #(#methods)* - } - )); - } - - quote!(#(#items)*) -} - -fn timer_queue(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { let tasks = &analysis.timer_queue.tasks; if tasks.is_empty() { - return quote!(); + return items; } - let mut items = vec![]; - let variants = tasks .iter() .map(|task| { @@ -1856,273 +975,1392 @@ fn timer_queue(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro ) }) .collect::>(); - let enum_ = &ctxt.schedule_enum; + items.push(quote!( - #[allow(dead_code)] + /// `schedule`-dable tasks #[allow(non_camel_case_types)] #[derive(Clone, Copy)] - enum #enum_ { #(#variants,)* } + enum T { + #(#variants,)* + } )); let cap = mk_typenum_capacity(analysis.timer_queue.capacity, false); - let tq = &ctxt.timer_queue; - let symbol = format!("TIMER_QUEUE::{}", tq); - if cfg!(feature = "nightly") { - items.push(quote!( - #[doc = #symbol] - static mut #tq: rtfm::export::MaybeUninit> = - rtfm::export::MaybeUninit::uninit(); - )); - } else { - items.push(quote!( - #[doc = #symbol] - static mut #tq: - rtfm::export::MaybeUninit> = - rtfm::export::MaybeUninit::uninit(); - )); - } - - items.push(mk_resource( - ctxt, - &[], - tq, - quote!(rtfm::export::TimerQueue<#enum_, #cap>), - analysis.timer_queue.ceiling, - quote!(&mut *#tq.as_mut_ptr()), - app, - None, + let ty = quote!(rtfm::export::TimerQueue); + items.push(quote!( + /// The timer queue + static mut TQ: rtfm::export::MaybeUninit<#ty> = rtfm::export::MaybeUninit::uninit(); + )); + + items.push(quote!( + struct TQ<'a> { + priority: &'a rtfm::export::Priority, + } + )); + + items.push(impl_mutex( + app, + &[], + false, + &Ident::new("TQ", Span::call_site()), + ty, + analysis.timer_queue.ceiling, + quote!(TQ.as_mut_ptr()), )); - let priority = &ctxt.priority; let device = &app.args.device; let arms = tasks .iter() - .map(|task| { - let task_ = &app.tasks[task]; - let level = task_.args.priority; - let cfgs = &task_.cfgs; - let dispatcher_ = &ctxt.dispatchers[&level]; - let tenum = &dispatcher_.enum_; - let ready = &dispatcher_.ready_queue; - let dispatcher = &analysis.dispatchers[&level].interrupt; + .map(|name| { + let task = &app.tasks[name]; + let cfgs = &task.cfgs; + let priority = task.args.priority; + let rq = mk_rq_ident(priority); + let t = mk_t_ident(priority); + let dispatcher = &analysis.dispatchers[&priority].interrupt; quote!( #(#cfgs)* - #enum_::#task => { - (#ready { #priority }).lock(|rq| { - rq.split().0.enqueue_unchecked((#tenum::#task, index)) + T::#name => { + let priority = &rtfm::export::Priority::new(PRIORITY); + (#rq { priority }).lock(|rq| { + rq.split().0.enqueue_unchecked((#t::#name, index)) }); - rtfm::pend(#device::Interrupt::#dispatcher); + rtfm::pend(#device::Interrupt::#dispatcher) } ) }) .collect::>(); - let logical_prio = analysis.timer_queue.priority; - let alias = ctxt.ident_gen.mk_ident(None, false); + let priority = analysis.timer_queue.priority; items.push(quote!( - #[export_name = "SysTick"] - #[doc(hidden)] - unsafe fn #alias() { - use rtfm::Mutex; + /// The system timer + #[no_mangle] + unsafe fn SysTick() { + use rtfm::Mutex as _; - let ref #priority = rtfm::export::Priority::new(#logical_prio); + /// System timer priority + const PRIORITY: u8 = #priority; - rtfm::export::run(|| { - rtfm::export::sys_tick(#tq { #priority }, |task, index| { + rtfm::export::run(PRIORITY, || { + while let Some((task, index)) = (TQ { + // NOTE dynamic priority is always the static priority at this point + priority: &rtfm::export::Priority::new(PRIORITY), + }) + // NOTE `inline(always)` produces faster and smaller code + .lock(#[inline(always)] + |tq| tq.dequeue()) + { match task { #(#arms)* } - }); - }) + } + }); } )); - quote!(#(#items)*) + items } -fn pre_init(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { - let mut exprs = vec![]; +/// Generates all the `Schedule.$task` related code +fn schedule(app: &App) -> Vec { + let mut items = vec![]; + if !cfg!(feature = "timer-queue") { + return items; + } + let mut seen = BTreeSet::new(); + for (scheduler, schedulees) in app.schedule_callers() { + if schedulees.is_empty() { + continue; + } + + let mut methods = vec![]; + + let scheduler_is_init = scheduler == "init"; + for name in schedulees { + let schedulee = &app.tasks[name]; + + let (args, _, untupled, ty) = regroup_inputs(&schedulee.inputs); + + let cfgs = &schedulee.cfgs; + + let schedule = mk_schedule_ident(name); + if scheduler_is_init { + let body = mk_schedule_body(&scheduler, name, app); + + let args = args.clone(); + methods.push(quote!( + #(#cfgs)* + fn #name(&self, instant: rtfm::Instant #(,#args)*) -> Result<(), #ty> { + #body + } + )); + } else { + if !seen.contains(name) { + seen.insert(name); + + let body = mk_schedule_body(&scheduler, name, app); + let args = args.clone(); + + items.push(quote!( + #(#cfgs)* + fn #schedule( + priority: &rtfm::export::Priority, + instant: rtfm::Instant + #(,#args)* + ) -> Result<(), #ty> { + #body + } + )); + } + + methods.push(quote!( + #(#cfgs)* + #[inline(always)] + fn #name(&self, instant: rtfm::Instant #(,#args)*) -> Result<(), #ty> { + let priority = unsafe { self.priority() }; + + #schedule(priority, instant #(,#untupled)*) + } + )); + } + } + + let lt = if scheduler_is_init { + None + } else { + Some(quote!('a)) + }; + items.push(quote!( + impl<#lt> #scheduler::Schedule<#lt> { + #(#methods)* + } + )); + } + + items +} + +/// Generates `Send` / `Sync` compile time checks +fn assertions(app: &App, analysis: &Analysis) -> Vec { + let mut stmts = vec![]; + + for ty in &analysis.assert_sync { + stmts.push(quote!(rtfm::export::assert_sync::<#ty>();)); + } + + for task in &analysis.tasks_assert_send { + let (_, _, _, ty) = regroup_inputs(&app.tasks[task].inputs); + stmts.push(quote!(rtfm::export::assert_send::<#ty>();)); + } + + // all late resources need to be `Send` + for ty in &analysis.resources_assert_send { + stmts.push(quote!(rtfm::export::assert_send::<#ty>();)); + } + + stmts +} + +/// Generates code that we must run before `init` runs. See comments inside +fn pre_init(app: &App, analysis: &Analysis) -> Vec { + let mut stmts = vec![]; + + stmts.push(quote!(rtfm::export::interrupt::disable();)); + + // these won't be required once we have better `const fn` on stable (or const generics) if !cfg!(feature = "nightly") { - // these are `MaybeUninit` arrays - for task in ctxt.tasks.values() { - let inputs = &task.inputs; - exprs.push(quote!(#inputs.write(core::mem::uninitialized());)) + // initialize `MaybeUninit` `ReadyQueue`s + for level in analysis.dispatchers.keys() { + let rq = mk_rq_ident(*level); + stmts.push(quote!(#rq.write(rtfm::export::ReadyQueue::u8_sc());)) } - #[cfg(feature = "timer-queue")] - for task in ctxt.tasks.values() { - let scheduleds = &task.scheduleds; - exprs.push(quote!(#scheduleds.write(core::mem::uninitialized());)) - } + // initialize `MaybeUninit` `FreeQueue`s + for name in app.tasks.keys() { + let fq = mk_fq_ident(name); - // these are `MaybeUninit` `ReadyQueue`s - for dispatcher in ctxt.dispatchers.values() { - let rq = &dispatcher.ready_queue; - exprs.push(quote!(#rq.write(rtfm::export::ReadyQueue::new_sc());)) - } + stmts.push(quote!( + let fq = #fq.write(rtfm::export::FreeQueue::u8_sc()); + )); - // these are `MaybeUninit` `FreeQueue`s - for task in ctxt.tasks.values() { - let fq = &task.free_queue; - exprs.push(quote!(#fq.write(rtfm::export::FreeQueue::new_sc());)) + // populate the `FreeQueue`s + let cap = analysis.capacities[name]; + stmts.push(quote!( + for i in 0..#cap { + fq.enqueue_unchecked(i); + } + )); + } + } else { + // populate the `FreeQueue`s + for name in app.tasks.keys() { + let fq = mk_fq_ident(name); + let cap = analysis.capacities[name]; + + stmts.push(quote!( + for i in 0..#cap { + #fq.enqueue_unchecked(i); + } + )); } } + stmts.push(quote!( + let mut core = rtfm::export::Peripherals::steal(); + )); + // Initialize the timer queue if !analysis.timer_queue.tasks.is_empty() { - let tq = &ctxt.timer_queue; - exprs.push(quote!(#tq.write(rtfm::export::TimerQueue::new(p.SYST));)); - } - - // Populate the `FreeQueue`s - for (name, task) in &ctxt.tasks { - let fq = &task.free_queue; - let fq_ = if cfg!(feature = "nightly") { - quote!(#fq) - } else { - quote!(#fq.get_mut()) - }; - let capacity = analysis.capacities[name]; - exprs.push(quote!( - for i in 0..#capacity { - #fq_.enqueue_unchecked(i); - } - )) + stmts.push(quote!(TQ.write(rtfm::export::TimerQueue::new(core.SYST));)); } + // set interrupts priorities let device = &app.args.device; let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); for (handler, interrupt) in &app.interrupts { let name = interrupt.args.binds(handler); let priority = interrupt.args.priority; - exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name);)); - // compile time assert that the priority is supported by the device - exprs.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + stmts.push(quote!(core.NVIC.enable(#device::Interrupt::#name);)); - exprs.push(quote!(p.NVIC.set_priority( - #device::Interrupt::#name, - ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), - );)); + // compile time assert that this priority is supported by the device + stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + + stmts.push(quote!( + core.NVIC.set_priority( + #device::Interrupt::#name, + rtfm::export::logical2hw(#priority, #nvic_prio_bits), + ); + )); } + // set task dispatcher priorities for (priority, dispatcher) in &analysis.dispatchers { let name = &dispatcher.interrupt; - exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name);)); - // compile time assert that the priority is supported by the device - exprs.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + stmts.push(quote!(core.NVIC.enable(#device::Interrupt::#name);)); - exprs.push(quote!(p.NVIC.set_priority( - #device::Interrupt::#name, - ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), - );)); + // compile time assert that this priority is supported by the device + stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + + stmts.push(quote!( + core.NVIC.set_priority( + #device::Interrupt::#name, + rtfm::export::logical2hw(#priority, #nvic_prio_bits), + ); + )); } // Set the cycle count to 0 and disable it while `init` executes if cfg!(feature = "timer-queue") { - exprs.push(quote!(p.DWT.ctrl.modify(|r| r & !1);)); - exprs.push(quote!(p.DWT.cyccnt.write(0);)); + stmts.push(quote!(core.DWT.ctrl.modify(|r| r & !1);)); + stmts.push(quote!(core.DWT.cyccnt.write(0);)); } - quote!( - let mut p = rtfm::export::Peripherals::steal(); - #(#exprs)* + stmts +} + +// This generates +// +// - at the root of the crate +// - a initResources struct (maybe) +// - a initLateResources struct (maybe) +// - a initLocals struct +// +// - an `init` module that contains +// - the `Context` struct +// - a re-export of the initResources struct +// - a re-export of the initLateResources struct +// - a re-export of the initLocals struct +// - the Spawn struct (maybe) +// - the Schedule struct (maybe, if `timer-queue` is enabled) +// +// - hidden in `const APP` +// - the initResources constructor +// +// - the user specified `init` function +// +// - a call to the user specified `init` function +fn init( + app: &App, + analysis: &Analysis, +) -> ( + // const_app + Option, + // mod_init + proc_macro2::TokenStream, + // init_locals + proc_macro2::TokenStream, + // init_resources + Option, + // init_late_resources + Option, + // user_init + proc_macro2::TokenStream, + // call_init + proc_macro2::TokenStream, +) { + let mut needs_lt = false; + let mut const_app = None; + let mut init_resources = None; + if !app.init.args.resources.is_empty() { + let (item, constructor) = resources_struct(Kind::Init, 0, &mut needs_lt, app, analysis); + + init_resources = Some(item); + const_app = Some(constructor); + } + + let core = if cfg!(feature = "timer-queue") { + quote!(rtfm::Peripherals { + CBP: core.CBP, + CPUID: core.CPUID, + DCB: &mut core.DCB, + FPB: core.FPB, + FPU: core.FPU, + ITM: core.ITM, + MPU: core.MPU, + SCB: &mut core.SCB, + TPIU: core.TPIU, + }) + } else { + quote!(rtfm::Peripherals { + CBP: core.CBP, + CPUID: core.CPUID, + DCB: core.DCB, + DWT: core.DWT, + FPB: core.FPB, + FPU: core.FPU, + ITM: core.ITM, + MPU: core.MPU, + SCB: &mut core.SCB, + SYST: core.SYST, + TPIU: core.TPIU, + }) + }; + + let call_init = quote!(let late = init(init::Locals::new(), init::Context::new(#core));); + + let late_fields = app + .resources + .iter() + .filter_map(|(name, res)| { + if res.expr.is_none() { + let ty = &res.ty; + + Some(quote!(pub #name: #ty)) + } else { + None + } + }) + .collect::>(); + + let attrs = &app.init.attrs; + let has_late_resources = !late_fields.is_empty(); + let (ret, init_late_resources) = if has_late_resources { + ( + Some(quote!(-> init::LateResources)), + Some(quote!( + /// Resources initialized at runtime + #[allow(non_snake_case)] + pub struct initLateResources { + #(#late_fields),* + } + )), + ) + } else { + (None, None) + }; + let context = &app.init.context; + let use_u32ext = if cfg!(feature = "timer-queue") { + Some(quote!( + use rtfm::U32Ext as _; + )) + } else { + None + }; + let (locals_struct, lets) = locals(Kind::Init, &app.init.statics); + let stmts = &app.init.stmts; + let user_init = quote!( + #(#attrs)* + #[allow(non_snake_case)] + fn init(__locals: init::Locals, #context: init::Context) #ret { + #use_u32ext + + #(#lets;)* + + #(#stmts)* + } + ); + + let mod_init = module( + Kind::Init, + (!app.init.args.resources.is_empty(), needs_lt), + !app.init.args.schedule.is_empty(), + !app.init.args.spawn.is_empty(), + has_late_resources, + app, + ); + + ( + const_app, + mod_init, + locals_struct, + init_resources, + init_late_resources, + user_init, + call_init, ) } -fn assertions(app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { - let mut items = vec![]; +/// Generates code that we must run after `init` returns. See comments inside +fn post_init(app: &App, analysis: &Analysis) -> Vec { + let mut stmts = vec![]; - for ty in &analysis.assert_sync { - items.push(quote!(rtfm::export::assert_sync::<#ty>())); + let device = &app.args.device; + let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); + + // initialize late resources + for (name, res) in &app.resources { + if res.expr.is_some() { + continue; + } + + stmts.push(quote!(#name.write(late.#name);)); } - for task in &analysis.tasks_assert_send { - let ty = tuple_ty(&app.tasks[task].inputs); - items.push(quote!(rtfm::export::assert_send::<#ty>())); + // set exception priorities + for (handler, exception) in &app.exceptions { + let name = exception.args.binds(handler); + let priority = exception.args.priority; + + // compile time assert that this priority is supported by the device + stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + + stmts.push(quote!(core.SCB.set_priority( + rtfm::export::SystemHandler::#name, + rtfm::export::logical2hw(#priority, #nvic_prio_bits), + );)); } - // all late resources need to be `Send` - for ty in &analysis.resources_assert_send { - items.push(quote!(rtfm::export::assert_send::<#ty>())); + // set the system timer priority + if !analysis.timer_queue.tasks.is_empty() { + let priority = analysis.timer_queue.priority; + + // compile time assert that this priority is supported by the device + stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + + stmts.push(quote!(core.SCB.set_priority( + rtfm::export::SystemHandler::SysTick, + rtfm::export::logical2hw(#priority, #nvic_prio_bits), + );)); } - quote!(#(#items;)*) + if app.idle.is_none() { + // Set SLEEPONEXIT bit to enter sleep mode when returning from ISR + stmts.push(quote!(core.SCB.scr.modify(|r| r | 1 << 1);)); + } + + // enable and start the system timer + if !analysis.timer_queue.tasks.is_empty() { + stmts.push(quote!((*TQ.as_mut_ptr()) + .syst + .set_clock_source(rtfm::export::SystClkSource::Core);)); + stmts.push(quote!((*TQ.as_mut_ptr()).syst.enable_counter();)); + } + + // enable the cycle counter + if cfg!(feature = "timer-queue") { + stmts.push(quote!(core.DCB.enable_trace();)); + stmts.push(quote!(core.DWT.enable_cycle_counter();)); + } + + stmts.push(quote!(rtfm::export::interrupt::enable();)); + + stmts } -fn mk_resource( - ctxt: &Context, +// If the user specified `idle` this generates +// +// - at the root of the crate +// - an idleResources struct (maybe) +// - an idleLocals struct +// +// - an `init` module that contains +// - the `Context` struct +// - a re-export of the idleResources struct +// - a re-export of the idleLocals struct +// - the Spawn struct (maybe) +// - the Schedule struct (maybe, if `timer-queue` is enabled) +// +// - hidden in `const APP` +// - the idleResources constructor +// +// - the user specified `idle` function +// +// - a call to the user specified `idle` function +// +// Otherwise it uses `loop { WFI }` as `idle` +fn idle( + app: &App, + analysis: &Analysis, +) -> ( + // const_app_idle + Option, + // mod_idle + Option, + // idle_locals + Option, + // idle_resources + Option, + // user_idle + Option, + // call_idle + proc_macro2::TokenStream, +) { + if let Some(idle) = app.idle.as_ref() { + let mut needs_lt = false; + let mut const_app = None; + let mut idle_resources = None; + + if !idle.args.resources.is_empty() { + let (item, constructor) = resources_struct(Kind::Idle, 0, &mut needs_lt, app, analysis); + + idle_resources = Some(item); + const_app = Some(constructor); + } + + let call_idle = quote!(idle( + idle::Locals::new(), + idle::Context::new(&rtfm::export::Priority::new(0)) + )); + + let attrs = &idle.attrs; + let context = &idle.context; + let use_u32ext = if cfg!(feature = "timer-queue") { + Some(quote!( + use rtfm::U32Ext as _; + )) + } else { + None + }; + let (idle_locals, lets) = locals(Kind::Idle, &idle.statics); + let stmts = &idle.stmts; + let user_idle = quote!( + #(#attrs)* + #[allow(non_snake_case)] + fn idle(__locals: idle::Locals, #context: idle::Context) -> ! { + #use_u32ext + use rtfm::Mutex as _; + + #(#lets;)* + + #(#stmts)* + } + ); + + let mod_idle = module( + Kind::Idle, + (!idle.args.resources.is_empty(), needs_lt), + !idle.args.schedule.is_empty(), + !idle.args.spawn.is_empty(), + false, + app, + ); + + ( + const_app, + Some(mod_idle), + Some(idle_locals), + idle_resources, + Some(user_idle), + call_idle, + ) + } else { + ( + None, + None, + None, + None, + None, + quote!(loop { + rtfm::export::wfi() + }), + ) + } +} + +/* Support functions */ +/// This function creates the `Resources` struct +/// +/// It's a bit unfortunate but this struct has to be created in the root because it refers to types +/// which may have been imported into the root. +fn resources_struct( + kind: Kind, + priority: u8, + needs_lt: &mut bool, + app: &App, + analysis: &Analysis, +) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { + let mut lt = None; + + let resources = match &kind { + Kind::Init => &app.init.args.resources, + Kind::Idle => &app.idle.as_ref().expect("UNREACHABLE").args.resources, + Kind::Interrupt(name) => &app.interrupts[name].args.resources, + Kind::Exception(name) => &app.exceptions[name].args.resources, + Kind::Task(name) => &app.tasks[name].args.resources, + }; + + let mut fields = vec![]; + let mut values = vec![]; + for name in resources { + let res = &app.resources[name]; + + let cfgs = &res.cfgs; + let mut_ = res.mutability; + let ty = &res.ty; + + if kind.is_init() { + if !analysis.ownerships.contains_key(name) { + // owned by `init` + fields.push(quote!( + #(#cfgs)* + pub #name: &'static #mut_ #ty + )); + + values.push(quote!( + #(#cfgs)* + #name: &#mut_ #name + )); + } else { + // owned by someone else + lt = Some(quote!('a)); + + fields.push(quote!( + #(#cfgs)* + pub #name: &'a mut #ty + )); + + values.push(quote!( + #(#cfgs)* + #name: &mut #name + )); + } + } else { + let ownership = &analysis.ownerships[name]; + + let mut exclusive = false; + if ownership.needs_lock(priority) { + if mut_.is_none() { + 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: resources::#name<'a> + )); + + values.push(quote!( + #(#cfgs)* + #name: resources::#name::new(priority) + )); + + continue; + } + } else { + let lt = if kind.runs_once() { + quote!('static) + } else { + lt = Some(quote!('a)); + quote!('a) + }; + + if ownership.is_owned() || mut_.is_none() { + fields.push(quote!( + #(#cfgs)* + pub #name: &#lt #mut_ #ty + )); + } else { + exclusive = true; + + fields.push(quote!( + #(#cfgs)* + pub #name: rtfm::Exclusive<#lt, #ty> + )); + } + } + + let is_late = res.expr.is_none(); + if is_late { + let expr = if mut_.is_some() { + quote!(&mut *#name.as_mut_ptr()) + } else { + quote!(&*#name.as_ptr()) + }; + + if exclusive { + values.push(quote!( + #(#cfgs)* + #name: rtfm::Exclusive(#expr) + )); + } else { + values.push(quote!( + #(#cfgs)* + #name: #expr + )); + } + } else { + if exclusive { + values.push(quote!( + #(#cfgs)* + #name: rtfm::Exclusive(&mut #name) + )); + } else { + values.push(quote!( + #(#cfgs)* + #name: &#mut_ #name + )); + } + } + } + } + + if lt.is_some() { + *needs_lt = true; + + // the struct could end up empty due to `cfg` leading to an error due to `'a` being unused + fields.push(quote!( + #[doc(hidden)] + pub __marker__: core::marker::PhantomData<&'a ()> + )); + + values.push(quote!(__marker__: core::marker::PhantomData)) + } + + let ident = kind.resources_ident(); + let doc = format!("Resources {} has access to", ident); + let item = quote!( + #[allow(non_snake_case)] + #[doc = #doc] + pub struct #ident<#lt> { + #(#fields,)* + } + ); + let arg = if kind.is_init() { + None + } else { + Some(quote!(priority: &#lt rtfm::export::Priority)) + }; + let constructor = quote!( + impl<#lt> #ident<#lt> { + #[inline(always)] + unsafe fn new(#arg) -> Self { + #ident { + #(#values,)* + } + } + } + ); + (item, constructor) +} + +/// Creates a `Mutex` implementation +fn impl_mutex( + app: &App, cfgs: &[Attribute], - struct_: &Ident, + resources_prefix: bool, + name: &Ident, ty: proc_macro2::TokenStream, ceiling: u8, ptr: proc_macro2::TokenStream, - app: &App, - module: Option<&mut Vec>, ) -> proc_macro2::TokenStream { - let priority = &ctxt.priority; - let device = &app.args.device; - - let mut items = vec![]; - - let path = if let Some(module) = module { - let doc = format!("`{}`", ty); - module.push(quote!( - #[allow(non_camel_case_types)] - #[doc = #doc] - #(#cfgs)* - pub struct #struct_<'a> { - #[doc(hidden)] - pub #priority: &'a rtfm::export::Priority, - } - )); - - quote!(resources::#struct_) + let path = if resources_prefix { + quote!(resources::#name) } else { - items.push(quote!( - #(#cfgs)* - struct #struct_<'a> { - #priority: &'a rtfm::export::Priority, - } - )); - - quote!(#struct_) + quote!(#name) }; - items.push(quote!( + let priority = if resources_prefix { + quote!(self.priority()) + } else { + quote!(self.priority) + }; + + let device = &app.args.device; + quote!( #(#cfgs)* impl<'a> rtfm::Mutex for #path<'a> { type T = #ty; - #[inline] - fn lock(&mut self, f: F) -> R - where - F: FnOnce(&mut Self::T) -> R, - { + #[inline(always)] + fn lock(&mut self, f: impl FnOnce(&mut #ty) -> R) -> R { + /// Priority ceiling + const CEILING: u8 = #ceiling; + unsafe { - rtfm::export::claim( + rtfm::export::lock( #ptr, - &self.#priority, - #ceiling, + #priority, + CEILING, #device::NVIC_PRIO_BITS, f, ) } } } - )); - - quote!(#(#items)*) + ) } +/// Creates a `Locals` struct and related code. This returns +/// +/// - `locals` +/// +/// ``` +/// pub struct Locals<'a> { +/// #[cfg(never)] +/// pub X: &'a mut X, +/// __marker__: PhantomData<&'a mut ()>, +/// } +/// ``` +/// +/// - `lt` +/// +/// ``` +/// 'a +/// ``` +/// +/// - `lets` +/// +/// ``` +/// #[cfg(never)] +/// let X = __locals.X +/// ``` +fn locals( + kind: Kind, + statics: &BTreeMap, +) -> ( + // locals + proc_macro2::TokenStream, + // lets + Vec, +) { + let runs_once = kind.runs_once(); + let ident = kind.locals_ident(); + + let mut lt = None; + let mut fields = vec![]; + let mut lets = vec![]; + let mut items = vec![]; + let mut values = vec![]; + for (name, static_) in statics { + let lt = if runs_once { + quote!('static) + } else { + lt = Some(quote!('a)); + quote!('a) + }; + + let cfgs = &static_.cfgs; + let expr = &static_.expr; + let ty = &static_.ty; + fields.push(quote!( + #(#cfgs)* + #name: &#lt mut #ty + )); + items.push(quote!( + #(#cfgs)* + static mut #name: #ty = #expr + )); + values.push(quote!( + #(#cfgs)* + #name: &mut #name + )); + lets.push(quote!( + #(#cfgs)* + let #name = __locals.#name + )); + } + + if lt.is_some() { + fields.push(quote!(__marker__: core::marker::PhantomData<&'a mut ()>)); + values.push(quote!(__marker__: core::marker::PhantomData)); + } + + let locals = quote!( + #[allow(non_snake_case)] + #[doc(hidden)] + pub struct #ident<#lt> { + #(#fields),* + } + + impl<#lt> #ident<#lt> { + #[inline(always)] + unsafe fn new() -> Self { + #(#items;)* + + #ident { + #(#values),* + } + } + } + ); + + (locals, lets) +} + +/// This function creates a module that contains +// +// - the Context struct +// - a re-export of the ${name}Resources struct (maybe) +// - a re-export of the ${name}LateResources struct (maybe) +// - a re-export of the ${name}Locals struct +// - the Spawn struct (maybe) +// - the Schedule struct (maybe, if `timer-queue` is enabled) +fn module( + kind: Kind, + resources: (/* has */ bool, /* 'a */ bool), + schedule: bool, + spawn: bool, + late_resources: bool, + app: &App, +) -> proc_macro2::TokenStream { + let mut items = vec![]; + let mut fields = vec![]; + let mut values = vec![]; + + let name = kind.ident(); + + let mut needs_instant = false; + let mut lt = None; + match kind { + Kind::Init => { + if cfg!(feature = "timer-queue") { + fields.push(quote!( + /// System start time = `Instant(0 /* cycles */)` + pub start: rtfm::Instant + )); + + values.push(quote!(start: rtfm::Instant::artificial(0))); + } + + let device = &app.args.device; + fields.push(quote!( + /// Core (Cortex-M) peripherals + pub core: rtfm::Peripherals<'a> + )); + fields.push(quote!( + /// Device specific peripherals + pub device: #device::Peripherals + )); + + values.push(quote!(core)); + values.push(quote!(device: #device::Peripherals::steal())); + lt = Some(quote!('a)); + } + + Kind::Idle => {} + + Kind::Exception(_) | Kind::Interrupt(_) => { + if cfg!(feature = "timer-queue") { + fields.push(quote!( + /// Time at which this handler started executing + pub start: rtfm::Instant + )); + + values.push(quote!(start: instant)); + + needs_instant = true; + } + } + + Kind::Task(_) => { + if cfg!(feature = "timer-queue") { + fields.push(quote!( + /// The time at which this task was scheduled to run + pub scheduled: rtfm::Instant + )); + + values.push(quote!(scheduled: instant)); + + needs_instant = true; + } + } + } + + let ident = kind.locals_ident(); + items.push(quote!( + #[doc(inline)] + pub use super::#ident as Locals; + )); + + if resources.0 { + let ident = kind.resources_ident(); + let lt = if resources.1 { + lt = Some(quote!('a)); + Some(quote!('a)) + } else { + None + }; + + items.push(quote!( + #[doc(inline)] + pub use super::#ident as Resources; + )); + + fields.push(quote!( + /// Resources this task has access to + pub resources: Resources<#lt> + )); + + let priority = if kind.is_init() { + None + } else { + Some(quote!(priority)) + }; + values.push(quote!(resources: Resources::new(#priority))); + } + + if schedule { + let doc = "Tasks that can be `schedule`-d from this context"; + if kind.is_init() { + items.push(quote!( + #[doc = #doc] + #[derive(Clone, Copy)] + pub struct Schedule { + _not_send: core::marker::PhantomData<*mut ()>, + } + )); + + fields.push(quote!( + #[doc = #doc] + pub schedule: Schedule + )); + + values.push(quote!( + schedule: Schedule { _not_send: core::marker::PhantomData } + )); + } else { + lt = Some(quote!('a)); + + items.push(quote!( + #[doc = #doc] + #[derive(Clone, Copy)] + pub struct Schedule<'a> { + priority: &'a rtfm::export::Priority, + } + + impl<'a> Schedule<'a> { + #[doc(hidden)] + #[inline(always)] + pub unsafe fn priority(&self) -> &rtfm::export::Priority { + &self.priority + } + } + )); + + fields.push(quote!( + #[doc = #doc] + pub schedule: Schedule<'a> + )); + + values.push(quote!( + schedule: Schedule { priority } + )); + } + } + + if spawn { + let doc = "Tasks that can be `spawn`-ed from this context"; + if kind.is_init() { + fields.push(quote!( + #[doc = #doc] + pub spawn: Spawn + )); + + items.push(quote!( + #[doc = #doc] + #[derive(Clone, Copy)] + pub struct Spawn { + _not_send: core::marker::PhantomData<*mut ()>, + } + )); + + values.push(quote!(spawn: Spawn { _not_send: core::marker::PhantomData })); + } else { + lt = Some(quote!('a)); + + fields.push(quote!( + #[doc = #doc] + pub spawn: Spawn<'a> + )); + + let mut instant_method = None; + if kind.is_idle() { + items.push(quote!( + #[doc = #doc] + #[derive(Clone, Copy)] + pub struct Spawn<'a> { + priority: &'a rtfm::export::Priority, + } + )); + + values.push(quote!(spawn: Spawn { priority })); + } else { + let instant_field = if cfg!(feature = "timer-queue") { + needs_instant = true; + instant_method = Some(quote!( + pub unsafe fn instant(&self) -> rtfm::Instant { + self.instant + } + )); + Some(quote!(instant: rtfm::Instant,)) + } else { + None + }; + + items.push(quote!( + /// Tasks that can be spawned from this context + #[derive(Clone, Copy)] + pub struct Spawn<'a> { + #instant_field + priority: &'a rtfm::export::Priority, + } + )); + + let _instant = if needs_instant { + Some(quote!(, instant)) + } else { + None + }; + values.push(quote!( + spawn: Spawn { priority #_instant } + )); + } + + items.push(quote!( + impl<'a> Spawn<'a> { + #[doc(hidden)] + #[inline(always)] + pub unsafe fn priority(&self) -> &rtfm::export::Priority { + self.priority + } + + #instant_method + } + )); + } + } + + if late_resources { + items.push(quote!( + #[doc(inline)] + pub use super::initLateResources as LateResources; + )); + } + + let doc = match kind { + Kind::Exception(_) => "Hardware task (exception)", + Kind::Idle => "Idle loop", + Kind::Init => "Initialization function", + Kind::Interrupt(_) => "Hardware task (interrupt)", + Kind::Task(_) => "Software task", + }; + + let core = if kind.is_init() { + lt = Some(quote!('a)); + Some(quote!(core: rtfm::Peripherals<'a>,)) + } else { + None + }; + + let priority = if kind.is_init() { + None + } else { + Some(quote!(priority: &#lt rtfm::export::Priority)) + }; + + let instant = if needs_instant { + Some(quote!(, instant: rtfm::Instant)) + } else { + None + }; + items.push(quote!( + /// Execution context + pub struct Context<#lt> { + #(#fields,)* + } + + impl<#lt> Context<#lt> { + #[inline(always)] + pub unsafe fn new(#core #priority #instant) -> Self { + Context { + #(#values,)* + } + } + } + )); + + if !items.is_empty() { + quote!( + #[allow(non_snake_case)] + #[doc = #doc] + pub mod #name { + #(#items)* + } + ) + } else { + quote!() + } +} + +/// Creates the body of `spawn_${name}` +fn mk_spawn_body<'a>( + spawner: &Ident, + name: &Ident, + app: &'a App, + analysis: &Analysis, +) -> proc_macro2::TokenStream { + let spawner_is_init = spawner == "init"; + let device = &app.args.device; + + let spawnee = &app.tasks[name]; + let priority = spawnee.args.priority; + let dispatcher = &analysis.dispatchers[&priority].interrupt; + + let (_, tupled, _, _) = regroup_inputs(&spawnee.inputs); + + let inputs = mk_inputs_ident(name); + let fq = mk_fq_ident(name); + + let rq = mk_rq_ident(priority); + let t = mk_t_ident(priority); + + let write_instant = if cfg!(feature = "timer-queue") { + let instants = mk_instants_ident(name); + + Some(quote!( + #instants.get_unchecked_mut(usize::from(index)).write(instant); + )) + } else { + None + }; + + let (dequeue, enqueue) = if spawner_is_init { + // `init` has exclusive access to these queues so we can bypass the resources AND + // the consumer / producer split + if cfg!(feature = "nightly") { + ( + quote!(#fq.dequeue()), + quote!(#rq.enqueue_unchecked((#t::#name, index));), + ) + } else { + ( + quote!((*#fq.as_mut_ptr()).dequeue()), + quote!((*#rq.as_mut_ptr()).enqueue_unchecked((#t::#name, index));), + ) + } + } else { + ( + quote!((#fq { priority }).lock(|fq| fq.split().1.dequeue())), + quote!((#rq { priority }).lock(|rq| { + rq.split().0.enqueue_unchecked((#t::#name, index)) + });), + ) + }; + + quote!( + unsafe { + use rtfm::Mutex as _; + + let input = #tupled; + if let Some(index) = #dequeue { + #inputs.get_unchecked_mut(usize::from(index)).write(input); + + #write_instant + + #enqueue + + rtfm::pend(#device::Interrupt::#dispatcher); + + Ok(()) + } else { + Err(input) + } + } + ) +} + +/// Creates the body of `schedule_${name}` +fn mk_schedule_body<'a>(scheduler: &Ident, name: &Ident, app: &'a App) -> proc_macro2::TokenStream { + let scheduler_is_init = scheduler == "init"; + + let schedulee = &app.tasks[name]; + + let (_, tupled, _, _) = regroup_inputs(&schedulee.inputs); + + let fq = mk_fq_ident(name); + let inputs = mk_inputs_ident(name); + let instants = mk_instants_ident(name); + + let (dequeue, enqueue) = if scheduler_is_init { + // `init` has exclusive access to these queues so we can bypass the resources AND + // the consumer / producer split + let dequeue = if cfg!(feature = "nightly") { + quote!(#fq.dequeue()) + } else { + quote!((*#fq.as_mut_ptr()).dequeue()) + }; + + (dequeue, quote!((*TQ.as_mut_ptr()).enqueue_unchecked(nr);)) + } else { + ( + quote!((#fq { priority }).lock(|fq| fq.split().1.dequeue())), + quote!((TQ { priority }).lock(|tq| tq.enqueue_unchecked(nr));), + ) + }; + + quote!( + unsafe { + use rtfm::Mutex as _; + + let input = #tupled; + if let Some(index) = #dequeue { + #instants.get_unchecked_mut(usize::from(index)).write(instant); + + #inputs.get_unchecked_mut(usize::from(index)).write(input); + + let nr = rtfm::export::NotReady { + instant, + index, + task: T::#name, + }; + + #enqueue + + Ok(()) + } else { + Err(input) + } + } + ) +} + +/// `u8` -> (unsuffixed) `LitInt` fn mk_capacity_literal(capacity: u8) -> LitInt { LitInt::new(u64::from(capacity), IntSuffix::None, Span::call_site()) } +/// e.g. `4u8` -> `U4` fn mk_typenum_capacity(capacity: u8, power_of_two: bool) -> proc_macro2::TokenStream { let capacity = if power_of_two { capacity @@ -2137,108 +2375,85 @@ fn mk_typenum_capacity(capacity: u8, power_of_two: bool) -> proc_macro2::TokenSt quote!(rtfm::export::consts::#ident) } -struct IdentGenerator { - call_count: u32, - rng: rand::rngs::SmallRng, +/// e.g. `foo` -> `foo_INPUTS` +fn mk_inputs_ident(base: &Ident) -> Ident { + Ident::new(&format!("{}_INPUTS", base), Span::call_site()) } -impl IdentGenerator { - fn new() -> IdentGenerator { - let elapsed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - - let secs = elapsed.as_secs(); - let nanos = elapsed.subsec_nanos(); - - let mut seed: [u8; 16] = [0; 16]; - - for (i, v) in seed.iter_mut().take(8).enumerate() { - *v = ((secs >> (i * 8)) & 0xFF) as u8 - } - - for (i, v) in seed.iter_mut().skip(8).take(4).enumerate() { - *v = ((nanos >> (i * 8)) & 0xFF) as u8 - } - - let rng = rand::rngs::SmallRng::from_seed(seed); - - IdentGenerator { call_count: 0, rng } - } - - fn mk_ident(&mut self, name: Option<&str>, random: bool) -> Ident { - let s = if let Some(name) = name { - format!("{}_", name) - } else { - "__rtfm_internal_".to_string() - }; - - let mut s = format!("{}{}", s, self.call_count); - self.call_count += 1; - - if random { - s.push('_'); - - for i in 0..4 { - if i == 0 || self.rng.gen() { - s.push(('a' as u8 + self.rng.gen::() % 25) as char) - } else { - s.push(('0' as u8 + self.rng.gen::() % 10) as char) - } - } - } - - Ident::new(&s, Span::call_site()) - } +/// e.g. `foo` -> `foo_INSTANTS` +fn mk_instants_ident(base: &Ident) -> Ident { + Ident::new(&format!("{}_INSTANTS", base), Span::call_site()) } -// `once = true` means that these locals will be called from a function that will run *once* -fn mk_locals(locals: &BTreeMap, once: bool) -> proc_macro2::TokenStream { - let lt = if once { Some(quote!('static)) } else { None }; - - let locals = locals - .iter() - .map(|(name, static_)| { - let attrs = &static_.attrs; - let cfgs = &static_.cfgs; - let expr = &static_.expr; - let ident = name; - let ty = &static_.ty; - - quote!( - #[allow(non_snake_case)] - #(#cfgs)* - let #ident: &#lt mut #ty = { - #(#attrs)* - #(#cfgs)* - static mut #ident: #ty = #expr; - - unsafe { &mut #ident } - }; - ) - }) - .collect::>(); - - quote!(#(#locals)*) +/// e.g. `foo` -> `foo_FQ` +fn mk_fq_ident(base: &Ident) -> Ident { + Ident::new(&format!("{}_FQ", base), Span::call_site()) } -fn tuple_pat(inputs: &[ArgCaptured]) -> proc_macro2::TokenStream { - if inputs.len() == 1 { - let pat = &inputs[0].pat; - quote!(#pat) - } else { - let pats = inputs.iter().map(|i| &i.pat).collect::>(); - - quote!(#(#pats,)*) - } +/// e.g. `3` -> `RQ3` +fn mk_rq_ident(level: u8) -> Ident { + Ident::new(&format!("RQ{}", level), Span::call_site()) } -fn tuple_ty(inputs: &[ArgCaptured]) -> proc_macro2::TokenStream { +/// e.g. `3` -> `T3` +fn mk_t_ident(level: u8) -> Ident { + Ident::new(&format!("T{}", level), Span::call_site()) +} + +fn mk_spawn_ident(task: &Ident) -> Ident { + Ident::new(&format!("spawn_{}", task), Span::call_site()) +} + +fn mk_schedule_ident(task: &Ident) -> Ident { + Ident::new(&format!("schedule_{}", task), Span::call_site()) +} + +// Regroups a task inputs +// +// e.g. &[`input: Foo`], &[`mut x: i32`, `ref y: i64`] +fn regroup_inputs( + inputs: &[ArgCaptured], +) -> ( + // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`] + Vec, + // tupled e.g. `_0`, `(_0, _1)` + proc_macro2::TokenStream, + // untupled e.g. &[`_0`], &[`_0`, `_1`] + Vec, + // ty e.g. `Foo`, `(i32, i64)` + proc_macro2::TokenStream, +) { if inputs.len() == 1 { let ty = &inputs[0].ty; - quote!(#ty) - } else { - let tys = inputs.iter().map(|i| &i.ty).collect::>(); - quote!((#(#tys,)*)) + ( + 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) } } @@ -2253,13 +2468,22 @@ enum Kind { impl Kind { fn ident(&self) -> Ident { + let span = Span::call_site(); match self { - Kind::Init => Ident::new("init", Span::call_site()), - Kind::Idle => Ident::new("idle", Span::call_site()), + Kind::Init => Ident::new("init", span), + Kind::Idle => Ident::new("idle", span), Kind::Task(name) | Kind::Interrupt(name) | Kind::Exception(name) => name.clone(), } } + fn locals_ident(&self) -> Ident { + Ident::new(&format!("{}Locals", self.ident()), Span::call_site()) + } + + fn resources_ident(&self) -> Ident { + Ident::new(&format!("{}Resources", self.ident()), Span::call_site()) + } + fn is_idle(&self) -> bool { *self == Kind::Idle } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index c8d9fee190..441d6b5ee9 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -288,9 +288,9 @@ mod syntax; pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { // Parse let args = parse_macro_input!(args as syntax::AppArgs); - let items = parse_macro_input!(input as syntax::Input).items; + let input = parse_macro_input!(input as syntax::Input); - let app = match syntax::App::parse(items, args) { + let app = match syntax::App::parse(input.items, args) { Err(e) => return e.to_compile_error().into(), Ok(app) => app, }; @@ -304,5 +304,5 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { let analysis = analyze::app(&app); // Code generation - codegen::app(&app, &analysis).into() + codegen::app(&input.ident, &app, &analysis).into() } diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs index 228d9588e0..c6814d5f25 100644 --- a/macros/src/syntax.rs +++ b/macros/src/syntax.rs @@ -11,8 +11,8 @@ use syn::{ spanned::Spanned, token::Brace, ArgCaptured, AttrStyle, Attribute, Expr, FnArg, ForeignItem, Ident, IntSuffix, Item, ItemFn, - ItemForeignMod, ItemStatic, LitInt, Path, PathArguments, PathSegment, ReturnType, Stmt, Token, - Type, TypeTuple, Visibility, + ItemForeignMod, ItemStatic, LitInt, Pat, Path, PathArguments, ReturnType, Stmt, Token, Type, + TypeTuple, Visibility, }; pub struct AppArgs { @@ -70,7 +70,7 @@ impl Parse for AppArgs { pub struct Input { _const_token: Token![const], - _ident: Ident, + pub ident: Ident, _colon_token: Token![:], _ty: TypeTuple, _eq_token: Token![=], @@ -94,7 +94,7 @@ impl Parse for Input { let content; Ok(Input { _const_token: input.parse()?, - _ident: input.parse()?, + ident: input.parse()?, _colon_token: input.parse()?, _ty: input.parse()?, _eq_token: input.parse()?, @@ -435,7 +435,7 @@ pub type FreeInterrupts = BTreeMap; pub struct Idle { pub args: IdleArgs, pub attrs: Vec, - pub unsafety: Option, + pub context: Pat, pub statics: BTreeMap, pub stmts: Vec, } @@ -444,34 +444,29 @@ pub type IdleArgs = InitArgs; impl Idle { fn check(args: IdleArgs, item: ItemFn) -> parse::Result { - let valid_signature = item.vis == Visibility::Inherited - && item.constness.is_none() - && item.asyncness.is_none() - && item.abi.is_none() - && item.decl.generics.params.is_empty() - && item.decl.generics.where_clause.is_none() - && item.decl.inputs.is_empty() - && item.decl.variadic.is_none() - && is_bottom(&item.decl.output); + let valid_signature = + check_signature(&item) && item.decl.inputs.len() == 1 && is_bottom(&item.decl.output); let span = item.span(); - if !valid_signature { - return Err(parse::Error::new( - span, - "`idle` must have type signature `[unsafe] fn() -> !`", - )); + if valid_signature { + if let Some((context, _)) = check_inputs(item.decl.inputs, "idle") { + let (statics, stmts) = extract_statics(item.block.stmts); + + return Ok(Idle { + args, + attrs: item.attrs, + context, + statics: Static::parse(statics)?, + stmts, + }); + } } - let (statics, stmts) = extract_statics(item.block.stmts); - - Ok(Idle { - args, - attrs: item.attrs, - unsafety: item.unsafety, - statics: Static::parse(statics)?, - stmts, - }) + Err(parse::Error::new( + span, + "`idle` must have type signature `fn(idle::Context) -> !`", + )) } } @@ -596,34 +591,21 @@ impl Parse for InitArgs { } } -// TODO remove in v0.5.x -pub struct Assign { - pub attrs: Vec, - pub left: Ident, - pub right: Box, -} - pub struct Init { pub args: InitArgs, pub attrs: Vec, - pub unsafety: Option, pub statics: BTreeMap, + pub context: Pat, pub stmts: Vec, - // TODO remove in v0.5.x - pub assigns: Vec, pub returns_late_resources: bool, + pub span: Span, } impl Init { fn check(args: InitArgs, item: ItemFn) -> parse::Result { - let mut valid_signature = item.vis == Visibility::Inherited - && item.constness.is_none() - && item.asyncness.is_none() - && item.abi.is_none() - && item.decl.generics.params.is_empty() - && item.decl.generics.where_clause.is_none() - && item.decl.inputs.is_empty() - && item.decl.variadic.is_none(); + let mut valid_signature = check_signature(&item) && item.decl.inputs.len() == 1; + + const DONT_CARE: bool = false; let returns_late_resources = match &item.decl.output { ReturnType::Default => false, @@ -636,36 +618,25 @@ impl Init { } else { valid_signature = false; - false // don't care + DONT_CARE } } - Type::Path(p) => { - let mut segments = p.path.segments.iter(); - if p.qself.is_none() - && p.path.leading_colon.is_none() - && p.path.segments.len() == 2 - && segments.next().map(|s| { - s.arguments == PathArguments::None && s.ident.to_string() == "init" - }) == Some(true) - && segments.next().map(|s| { - s.arguments == PathArguments::None - && s.ident.to_string() == "LateResources" - }) == Some(true) - { + Type::Path(_) => { + if is_path(ty, &["init", "LateResources"]) { // -> init::LateResources true } else { valid_signature = false; - false // don't care + DONT_CARE } } _ => { valid_signature = false; - false // don't care + DONT_CARE } } } @@ -673,29 +644,26 @@ impl Init { let span = item.span(); - if !valid_signature { - return Err(parse::Error::new( - span, - "`init` must have type signature `[unsafe] fn() [-> init::LateResources]`", - )); + if valid_signature { + if let Some((context, _)) = check_inputs(item.decl.inputs, "init") { + let (statics, stmts) = extract_statics(item.block.stmts); + + return Ok(Init { + args, + attrs: item.attrs, + statics: Static::parse(statics)?, + context, + stmts, + returns_late_resources, + span, + }); + } } - let (statics, stmts) = extract_statics(item.block.stmts); - let (stmts, assigns) = if returns_late_resources { - (stmts, vec![]) - } else { - extract_assignments(stmts) - }; - - Ok(Init { - args, - attrs: item.attrs, - unsafety: item.unsafety, - statics: Static::parse(statics)?, - stmts, - assigns, - returns_late_resources, - }) + Err(parse::Error::new( + span, + "`init` must have type signature `fn(init::Context) [-> init::LateResources]`", + )) } } @@ -725,8 +693,8 @@ impl Default for Args { pub struct Exception { pub args: ExceptionArgs, pub attrs: Vec, - pub unsafety: Option, pub statics: BTreeMap, + pub context: Pat, pub stmts: Vec, } @@ -770,61 +738,67 @@ impl Parse for ExceptionArgs { impl Exception { fn check(args: ExceptionArgs, item: ItemFn) -> parse::Result { - let valid_signature = item.vis == Visibility::Inherited - && item.constness.is_none() - && item.asyncness.is_none() - && item.abi.is_none() - && item.decl.generics.params.is_empty() - && item.decl.generics.where_clause.is_none() - && item.decl.inputs.is_empty() - && item.decl.variadic.is_none() - && is_unit(&item.decl.output); + let valid_signature = + check_signature(&item) && item.decl.inputs.len() == 1 && is_unit(&item.decl.output); - if !valid_signature { - return Err(parse::Error::new( - item.span(), - "`exception` handlers must have type signature `[unsafe] fn()`", - )); - } + let span = item.span(); - let span = item.ident.span(); - match &*args.binds.as_ref().unwrap_or(&item.ident).to_string() { - "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" - | "DebugMonitor" | "PendSV" => {} // OK - "SysTick" => { - if cfg!(feature = "timer-queue") { - return Err(parse::Error::new( + let name = item.ident.to_string(); + if valid_signature { + if let Some((context, _)) = check_inputs(item.decl.inputs, &name) { + let span = item.ident.span(); + match &*args + .binds + .as_ref() + .map(|ident| ident.to_string()) + .unwrap_or(name) + { + "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" + | "DebugMonitor" | "PendSV" => {} // OK + "SysTick" => { + if cfg!(feature = "timer-queue") { + return Err(parse::Error::new( + span, + "the `SysTick` exception can't be used because it's used by \ + the runtime when the `timer-queue` feature is enabled", + )); + } + } + _ => { + return Err(parse::Error::new( span, - "the `SysTick` exception can't be used because it's used by \ - the runtime when the `timer-queue` feature is enabled", + "only exceptions with configurable priority can be used as hardware tasks", )); + } } - } - _ => { - return Err(parse::Error::new( - span, - "only exceptions with configurable priority can be used as hardware tasks", - )); + + let (statics, stmts) = extract_statics(item.block.stmts); + + return Ok(Exception { + args, + attrs: item.attrs, + statics: Static::parse(statics)?, + context, + stmts, + }); } } - let (statics, stmts) = extract_statics(item.block.stmts); - - Ok(Exception { - args, - attrs: item.attrs, - unsafety: item.unsafety, - statics: Static::parse(statics)?, - stmts, - }) + Err(parse::Error::new( + span, + &format!( + "this `exception` handler must have type signature `fn({}::Context)`", + name + ), + )) } } pub struct Interrupt { pub args: InterruptArgs, pub attrs: Vec, - pub unsafety: Option, pub statics: BTreeMap, + pub context: Pat, pub stmts: Vec, } @@ -832,49 +806,47 @@ pub type InterruptArgs = ExceptionArgs; impl Interrupt { fn check(args: InterruptArgs, item: ItemFn) -> parse::Result { - let valid_signature = item.vis == Visibility::Inherited - && item.constness.is_none() - && item.asyncness.is_none() - && item.abi.is_none() - && item.decl.generics.params.is_empty() - && item.decl.generics.where_clause.is_none() - && item.decl.inputs.is_empty() - && item.decl.variadic.is_none() - && is_unit(&item.decl.output); + let valid_signature = + check_signature(&item) && item.decl.inputs.len() == 1 && is_unit(&item.decl.output); let span = item.span(); - if !valid_signature { - return Err(parse::Error::new( - span, - "`interrupt` handlers must have type signature `[unsafe] fn()`", - )); - } + let name = item.ident.to_string(); + if valid_signature { + if let Some((context, _)) = check_inputs(item.decl.inputs, &name) { + match &*name { + "init" | "idle" | "resources" => { + return Err(parse::Error::new( + span, + "`interrupt` handlers can NOT be named `idle`, `init` or `resources`", + )); + } + _ => {} + } - match &*item.ident.to_string() { - "init" | "idle" | "resources" => { - return Err(parse::Error::new( - span, - "`interrupt` handlers can NOT be named `idle`, `init` or `resources`", - )); + let (statics, stmts) = extract_statics(item.block.stmts); + + return Ok(Interrupt { + args, + attrs: item.attrs, + statics: Static::parse(statics)?, + context, + stmts, + }); } - _ => {} } - let (statics, stmts) = extract_statics(item.block.stmts); - - Ok(Interrupt { - args, - attrs: item.attrs, - unsafety: item.unsafety, - statics: Static::parse(statics)?, - stmts, - }) + Err(parse::Error::new( + span, + format!( + "this `interrupt` handler must have type signature `fn({}::Context)`", + name + ), + )) } } pub struct Resource { - pub singleton: bool, pub cfgs: Vec, pub attrs: Vec, pub mutability: Option, @@ -883,7 +855,7 @@ pub struct Resource { } impl Resource { - fn check(mut item: ItemStatic) -> parse::Result { + fn check(item: ItemStatic) -> parse::Result { if item.vis != Visibility::Inherited { return Err(parse::Error::new( item.span(), @@ -896,19 +868,9 @@ impl Resource { _ => false, }; - let pos = item.attrs.iter().position(|attr| eq(attr, "Singleton")); - - if let Some(pos) = pos { - item.attrs[pos].path.segments.insert( - 0, - PathSegment::from(Ident::new("owned_singleton", Span::call_site())), - ); - } - let (cfgs, attrs) = extract_cfgs(item.attrs); Ok(Resource { - singleton: pos.is_some(), cfgs, attrs, mutability: item.mutability, @@ -1177,66 +1139,61 @@ pub struct Task { pub args: TaskArgs, pub cfgs: Vec, pub attrs: Vec, - pub unsafety: Option, pub inputs: Vec, + pub context: Pat, pub statics: BTreeMap, pub stmts: Vec, } impl Task { fn check(args: TaskArgs, item: ItemFn) -> parse::Result { - let valid_signature = item.vis == Visibility::Inherited - && item.constness.is_none() - && item.asyncness.is_none() - && item.abi.is_none() - && item.decl.generics.params.is_empty() - && item.decl.generics.where_clause.is_none() - && item.decl.variadic.is_none() - && is_unit(&item.decl.output); + let valid_signature = + check_signature(&item) && !item.decl.inputs.is_empty() && is_unit(&item.decl.output); let span = item.span(); - if !valid_signature { - return Err(parse::Error::new( - span, - "`task` handlers must have type signature `[unsafe] fn(..)`", - )); - } + let name = item.ident.to_string(); + if valid_signature { + if let Some((context, rest)) = check_inputs(item.decl.inputs, &name) { + let (statics, stmts) = extract_statics(item.block.stmts); - let (statics, stmts) = extract_statics(item.block.stmts); + let inputs = rest.map_err(|arg| { + parse::Error::new( + arg.span(), + "inputs must be named arguments (e.f. `foo: u32`) and not include `self`", + ) + })?; - let mut inputs = vec![]; - for input in item.decl.inputs { - if let FnArg::Captured(capture) = input { - inputs.push(capture); - } else { - return Err(parse::Error::new( - span, - "inputs must be named arguments (e.f. `foo: u32`) and not include `self`", - )); + match &*name { + "init" | "idle" | "resources" => { + return Err(parse::Error::new( + span, + "`task` handlers can NOT be named `idle`, `init` or `resources`", + )); + } + _ => {} + } + + let (cfgs, attrs) = extract_cfgs(item.attrs); + return Ok(Task { + args, + cfgs, + attrs, + inputs, + context, + statics: Static::parse(statics)?, + stmts, + }); } } - match &*item.ident.to_string() { - "init" | "idle" | "resources" => { - return Err(parse::Error::new( - span, - "`task` handlers can NOT be named `idle`, `init` or `resources`", - )); - } - _ => {} - } - - let (cfgs, attrs) = extract_cfgs(item.attrs); - Ok(Task { - args, - cfgs, - attrs, - unsafety: item.unsafety, - inputs, - statics: Static::parse(statics)?, - stmts, - }) + Err(parse::Error::new( + span, + &format!( + "this `task` handler must have type signature `fn({}::Context, ..)`", + name + ), + )) } } @@ -1335,38 +1292,69 @@ fn extract_statics(stmts: Vec) -> (Statics, Vec) { (statics, stmts) } -// TODO remove in v0.5.x -fn extract_assignments(stmts: Vec) -> (Vec, Vec) { - let mut istmts = stmts.into_iter().rev(); +// checks that the list of arguments has the form `#pat: #name::Context, (..)` +// +// if the check succeeds it returns `#pat` plus the remaining arguments +fn check_inputs( + inputs: Punctuated, + name: &str, +) -> Option<(Pat, Result, FnArg>)> { + let mut inputs = inputs.into_iter(); - let mut assigns = vec![]; - let mut stmts = vec![]; - while let Some(stmt) = istmts.next() { - match stmt { - Stmt::Semi(Expr::Assign(assign), semi) => { - if let Expr::Path(ref expr) = *assign.left { - if expr.path.segments.len() == 1 { - assigns.push(Assign { - attrs: assign.attrs, - left: expr.path.segments[0].ident.clone(), - right: assign.right, - }); - continue; - } - } + match inputs.next() { + Some(FnArg::Captured(first)) => { + if is_path(&first.ty, &[name, "Context"]) { + let rest = inputs + .map(|arg| match arg { + FnArg::Captured(arg) => Ok(arg), + _ => Err(arg), + }) + .collect::, _>>(); - stmts.push(Stmt::Semi(Expr::Assign(assign), semi)); - } - _ => { - stmts.push(stmt); - break; + Some((first.pat, rest)) + } else { + None } } + + _ => None, } +} - stmts.extend(istmts); +/// 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 parametrs) +/// - is not variadic +/// - uses the Rust ABI (and not e.g. "C") +fn check_signature(item: &ItemFn) -> bool { + item.vis == Visibility::Inherited + && item.constness.is_none() + && item.asyncness.is_none() + && item.abi.is_none() + && item.unsafety.is_none() + && item.decl.generics.params.is_empty() + && item.decl.generics.where_clause.is_none() + && item.decl.variadic.is_none() +} - (stmts.into_iter().rev().collect(), assigns) +fn 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, + } } fn is_bottom(ty: &ReturnType) -> bool { diff --git a/src/export.rs b/src/export.rs index cf7293b620..93a92fcf2a 100644 --- a/src/export.rs +++ b/src/export.rs @@ -1,7 +1,5 @@ //! IMPLEMENTATION DETAILS. DO NOT USE ANYTHING IN THIS MODULE -#[cfg(not(feature = "nightly"))] -use core::ptr; use core::{cell::Cell, u8}; #[cfg(armv7m)] @@ -14,25 +12,31 @@ pub use heapless::consts; use heapless::spsc::{Queue, SingleCore}; #[cfg(feature = "timer-queue")] -pub use crate::tq::{isr as sys_tick, NotReady, TimerQueue}; +pub use crate::tq::{NotReady, TimerQueue}; -pub type FreeQueue = Queue; -pub type ReadyQueue = Queue<(T, u8), N, usize, SingleCore>; +pub type FreeQueue = Queue; +pub type ReadyQueue = Queue<(T, u8), N, u8, SingleCore>; #[cfg(armv7m)] #[inline(always)] -pub fn run(f: F) +pub fn run(priority: u8, f: F) where F: FnOnce(), { - let initial = basepri::read(); - f(); - unsafe { basepri::write(initial) } + 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(armv7m))] #[inline(always)] -pub fn run(f: F) +pub fn run(_priority: u8, f: F) where F: FnOnce(), { @@ -52,7 +56,7 @@ impl Priority { } } - // these two methods are used by claim (see below) but can't be used from the RTFM application + // these two methods are used by `lock` (see below) but can't be used from the RTFM application #[inline(always)] fn set(&self, value: u8) { self.inner.set(value) @@ -64,13 +68,12 @@ impl Priority { } } -#[cfg(feature = "nightly")] +// We newtype `core::mem::MaybeUninit` so the end-user doesn't need `#![feature(maybe_uninit)]` in +// their code pub struct MaybeUninit { - // we newtype so the end-user doesn't need `#![feature(maybe_uninit)]` in their code inner: core::mem::MaybeUninit, } -#[cfg(feature = "nightly")] impl MaybeUninit { pub const fn uninit() -> Self { MaybeUninit { @@ -86,64 +89,15 @@ impl MaybeUninit { self.inner.as_mut_ptr() } + pub unsafe fn read(&self) -> T { + self.inner.read() + } + pub fn write(&mut self, value: T) -> &mut T { self.inner.write(value) } } -#[cfg(not(feature = "nightly"))] -pub struct MaybeUninit { - value: Option, -} - -#[cfg(not(feature = "nightly"))] -const MSG: &str = - "you have hit a bug (UB) in RTFM implementation; try enabling this crate 'nightly' feature"; - -#[cfg(not(feature = "nightly"))] -impl MaybeUninit { - pub const fn uninit() -> Self { - MaybeUninit { value: None } - } - - pub fn as_ptr(&self) -> *const T { - if let Some(x) = self.value.as_ref() { - x - } else { - unreachable!(MSG) - } - } - - pub fn as_mut_ptr(&mut self) -> *mut T { - if let Some(x) = self.value.as_mut() { - x - } else { - unreachable!(MSG) - } - } - - pub unsafe fn get_ref(&self) -> &T { - if let Some(x) = self.value.as_ref() { - x - } else { - unreachable!(MSG) - } - } - - pub unsafe fn get_mut(&mut self) -> &mut T { - if let Some(x) = self.value.as_mut() { - x - } else { - unreachable!(MSG) - } - } - - pub fn write(&mut self, val: T) { - // NOTE(volatile) we have observed UB when this uses a plain `ptr::write` - unsafe { ptr::write_volatile(&mut self.value, Some(val)) } - } -} - #[inline(always)] pub fn assert_send() where @@ -160,19 +114,16 @@ where #[cfg(armv7m)] #[inline(always)] -pub unsafe fn claim( +pub unsafe fn lock( ptr: *mut T, priority: &Priority, ceiling: u8, nvic_prio_bits: u8, - f: F, -) -> R -where - F: FnOnce(&mut T) -> R, -{ + f: impl FnOnce(&mut T) -> R, +) -> R { let current = priority.get(); - if priority.get() < ceiling { + if current < ceiling { if ceiling == (1 << nvic_prio_bits) { priority.set(u8::MAX); let r = interrupt::free(|_| f(&mut *ptr)); @@ -193,19 +144,16 @@ where #[cfg(not(armv7m))] #[inline(always)] -pub unsafe fn claim( +pub unsafe fn lock( ptr: *mut T, priority: &Priority, ceiling: u8, _nvic_prio_bits: u8, - f: F, -) -> R -where - F: FnOnce(&mut T) -> R, -{ + f: impl FnOnce(&mut T) -> R, +) -> R { let current = priority.get(); - if priority.get() < ceiling { + if current < ceiling { priority.set(u8::MAX); let r = interrupt::free(|_| f(&mut *ptr)); priority.set(current); @@ -215,8 +163,7 @@ where } } -#[cfg(armv7m)] #[inline] -fn logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { +pub fn logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits) } diff --git a/src/lib.rs b/src/lib.rs index b0bf6689e3..acd8d433fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,8 @@ //! Real Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollers //! +//! **HEADS UP** This is an **alpha** pre-release; there may be breaking changes in the API and +//! semantics before a proper release is made. +//! //! **IMPORTANT**: This crate is published as [`cortex-m-rtfm`] on crates.io but the name of the //! library is `rtfm`. //! @@ -7,7 +10,7 @@ //! //! The user level documentation can be found [here]. //! -//! [here]: https://japaric.github.io/cortex-m-rtfm/book/en/ +//! [here]: https://japaric.github.io/rtfm5/book/en/ //! //! Don't forget to check the documentation of the [`#[app]`] attribute, which is the main component //! of the framework. @@ -16,7 +19,7 @@ //! //! # Minimum Supported Rust Version (MSRV) //! -//! This crate is guaranteed to compile on stable Rust 1.31 (2018 edition) and up. It *might* +//! This crate is guaranteed to compile on stable Rust 1.36 (2018 edition) and up. It *might* //! compile on older versions but that may change in any new patch release. //! //! # Semantic Versioning @@ -36,12 +39,11 @@ //! [`Instant`]: struct.Instant.html //! [`Duration`]: struct.Duration.html //! -//! - `nightly`. Enabling this opt-in feature makes RTFM internally use the unstable -//! `core::mem::MaybeUninit` API and unstable `const_fn` language feature to reduce static memory -//! usage, runtime overhead and initialization overhead. This feature requires a nightly compiler -//! and may stop working at any time! +//! - `nightly`. Enabling this opt-in feature makes RTFM internally use the unstable `const_fn` +//! language feature to reduce static memory usage, runtime overhead and initialization overhead. +//! This feature requires a nightly compiler and may stop working at any time! -#![cfg_attr(feature = "nightly", feature(maybe_uninit))] +#![feature(maybe_uninit)] #![deny(missing_docs)] #![deny(warnings)] #![no_std] @@ -132,7 +134,7 @@ pub struct Instant(i32); impl Instant { /// IMPLEMENTATION DETAIL. DO NOT USE #[doc(hidden)] - pub fn artificial(timestamp: i32) -> Self { + pub unsafe fn artificial(timestamp: i32) -> Self { Instant(timestamp) } @@ -290,9 +292,7 @@ pub trait Mutex { type T; /// Creates a critical section and grants temporary access to the protected data - fn lock(&mut self, f: F) -> R - where - F: FnOnce(&mut Self::T) -> R; + fn lock(&mut self, f: impl FnOnce(&mut Self::T) -> R) -> R; } impl<'a, M> Mutex for &'a mut M @@ -301,10 +301,7 @@ where { type T = M::T; - fn lock(&mut self, f: F) -> R - where - F: FnOnce(&mut Self::T) -> R, - { + fn lock(&mut self, f: impl FnOnce(&mut M::T) -> R) -> R { (**self).lock(f) } } @@ -317,10 +314,7 @@ pub struct Exclusive<'a, T>(pub &'a mut T); impl<'a, T> Mutex for Exclusive<'a, T> { type T = T; - fn lock(&mut self, f: F) -> R - where - F: FnOnce(&mut Self::T) -> R, - { + fn lock(&mut self, f: impl FnOnce(&mut T) -> R) -> R { f(self.0) } } diff --git a/src/tq.rs b/src/tq.rs index 8d52051855..8ca1bd3f8e 100644 --- a/src/tq.rs +++ b/src/tq.rs @@ -3,7 +3,7 @@ use core::cmp::{self, Ordering}; use cortex_m::peripheral::{SCB, SYST}; use heapless::{binary_heap::Min, ArrayLength, BinaryHeap}; -use crate::{Instant, Mutex}; +use crate::Instant; pub struct TimerQueue where @@ -43,11 +43,39 @@ where } // set SysTick pending - (*SCB::ptr()).icsr.write(1 << 26); + SCB::set_pendst(); } self.queue.push_unchecked(nr); } + + #[inline] + pub fn dequeue(&mut self) -> Option<(T, u8)> { + if let Some(instant) = self.queue.peek().map(|p| p.instant) { + let diff = instant.0.wrapping_sub(Instant::now().0); + + if diff < 0 { + // task became ready + let nr = unsafe { self.queue.pop_unchecked() }; + + Some((nr.task, nr.index)) + } else { + // set a new timeout + const MAX: u32 = 0x00ffffff; + + self.syst.set_reload(cmp::min(MAX, diff as u32)); + + // start counting down from the new reload + self.syst.clear_current(); + + None + } + } else { + // the queue is empty + self.syst.disable_interrupt(); + None + } + } } pub struct NotReady @@ -87,49 +115,3 @@ where Some(self.cmp(&other)) } } - -#[inline(always)] -pub fn isr(mut tq: TQ, mut f: F) -where - TQ: Mutex>, - T: Copy + Send, - N: ArrayLength>, - F: FnMut(T, u8), -{ - loop { - // XXX does `#[inline(always)]` improve performance or not? - let next = tq.lock(#[inline(always)] - |tq| { - if let Some(instant) = tq.queue.peek().map(|p| p.instant) { - let diff = instant.0.wrapping_sub(Instant::now().0); - - if diff < 0 { - // task became ready - let m = unsafe { tq.queue.pop_unchecked() }; - - Some((m.task, m.index)) - } else { - // set a new timeout - const MAX: u32 = 0x00ffffff; - - tq.syst.set_reload(cmp::min(MAX, diff as u32)); - - // start counting down from the new reload - tq.syst.clear_current(); - - None - } - } else { - // the queue is empty - tq.syst.disable_interrupt(); - None - } - }); - - if let Some((task, index)) = next { - f(task, index) - } else { - return; - } - } -} diff --git a/tests/cfail/cfg-resources.rs b/tests/cfail/cfg-resources.rs index dee1485b8e..5e20c4de6c 100644 --- a/tests/cfail/cfg-resources.rs +++ b/tests/cfail/cfg-resources.rs @@ -30,35 +30,35 @@ const APP: () = { static S3: u32 = 0; #[init(resources = [O1, O4, O5, O6, S3])] - fn init() { - resources.O1; //~ ERROR no field `O1` - resources.O4; //~ ERROR no field `O4` - resources.O5; //~ ERROR no field `O5` - resources.O6; //~ ERROR no field `O6` - resources.S3; //~ ERROR no field `S3` + fn init(c: init::Context) { + c.resources.O1; //~ ERROR no field `O1` + c.resources.O4; //~ ERROR no field `O4` + c.resources.O5; //~ ERROR no field `O5` + c.resources.O6; //~ ERROR no field `O6` + c.resources.S3; //~ ERROR no field `S3` } #[idle(resources = [O2, O4, S1, S3])] - fn idle() -> ! { - resources.O2; //~ ERROR no field `O2` - resources.O4; //~ ERROR no field `O4` - resources.S1; //~ ERROR no field `S1` - resources.S3; //~ ERROR no field `S3` + fn idle(c: idle::Context) -> ! { + c.resources.O2; //~ ERROR no field `O2` + c.resources.O4; //~ ERROR no field `O4` + c.resources.S1; //~ ERROR no field `S1` + c.resources.S3; //~ ERROR no field `S3` loop {} } #[interrupt(resources = [O3, S1, S2, S3])] - fn UART0() { - resources.O3; //~ ERROR no field `O3` - resources.S1; //~ ERROR no field `S1` - resources.S2; //~ ERROR no field `S2` - resources.S3; //~ ERROR no field `S3` + fn UART0(c: UART0::Context) { + c.resources.O3; //~ ERROR no field `O3` + c.resources.S1; //~ ERROR no field `S1` + c.resources.S2; //~ ERROR no field `S2` + c.resources.S3; //~ ERROR no field `S3` } #[interrupt(resources = [S2, O5])] - fn UART1() { - resources.S2; //~ ERROR no field `S2` - resources.O5; //~ ERROR no field `O5` + fn UART1(c: UART1::Context) { + c.resources.S2; //~ ERROR no field `S2` + c.resources.O5; //~ ERROR no field `O5` } }; diff --git a/tests/cfail/cfg-static.rs b/tests/cfail/cfg-static.rs index 0d27e53398..91465a1e7a 100644 --- a/tests/cfail/cfg-static.rs +++ b/tests/cfail/cfg-static.rs @@ -10,7 +10,7 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() { + fn init(_: init::Context) { #[cfg(never)] static mut FOO: u32 = 0; @@ -18,7 +18,7 @@ const APP: () = { } #[idle] - fn idle() -> ! { + fn idle(_: idle::Context) -> ! { #[cfg(never)] static mut FOO: u32 = 0; @@ -28,7 +28,7 @@ const APP: () = { } #[exception] - fn SVCall() { + fn SVCall(_: SVCall::Context) { #[cfg(never)] static mut FOO: u32 = 0; @@ -36,7 +36,7 @@ const APP: () = { } #[interrupt] - fn UART0() { + fn UART0(_: UART0::Context) { #[cfg(never)] static mut FOO: u32 = 0; @@ -44,7 +44,7 @@ const APP: () = { } #[task] - fn foo() { + fn foo(_: foo::Context) { #[cfg(never)] static mut FOO: u32 = 0; diff --git a/tests/cfail/duplicate-args-2.rs b/tests/cfail/duplicate-args-2.rs index 1a196e99f8..5bef79b5f7 100644 --- a/tests/cfail/duplicate-args-2.rs +++ b/tests/cfail/duplicate-args-2.rs @@ -10,13 +10,13 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} #[task( priority = 1, priority = 2, //~ ERROR argument appears more than once )] - fn foo() {} + fn foo(_: foo::Context) {} extern "C" { fn UART0(); diff --git a/tests/cfail/duplicate-args.rs b/tests/cfail/duplicate-args.rs index a946bae223..6938cd0d72 100644 --- a/tests/cfail/duplicate-args.rs +++ b/tests/cfail/duplicate-args.rs @@ -10,13 +10,13 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} #[task( capacity = 1, capacity = 2, //~ ERROR argument appears more than once )] - fn foo() {} + fn foo(_: foo::Context) {} extern "C" { fn UART0(); diff --git a/tests/cfail/early-return-2.rs b/tests/cfail/early-return-2.rs deleted file mode 100644 index bf867e0720..0000000000 --- a/tests/cfail/early-return-2.rs +++ /dev/null @@ -1,29 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - static mut UNINITIALIZED: bool = (); - - #[init] - fn init() { - if false { - return; //~ ERROR `init` is *not* allowed to early return - } - - UNINITIALIZED = true; - } - - #[interrupt(resources = [UNINITIALIZED])] - fn UART0() { - if resources.UNINITIALIZED { - // UB - } - } -}; diff --git a/tests/cfail/early-return.rs b/tests/cfail/early-return.rs deleted file mode 100644 index fb695aac97..0000000000 --- a/tests/cfail/early-return.rs +++ /dev/null @@ -1,32 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - static mut UNINITIALIZED: bool = (); - - #[init] - fn init() { - let x = || { - // this is OK - return 0; - }; - - return; //~ ERROR `init` is *not* allowed to early return - - UNINITIALIZED = true; - } - - #[interrupt(resources = [UNINITIALIZED])] - fn UART0() { - if resources.UNINITIALIZED { - // UB - } - } -}; diff --git a/tests/cfail/exception-divergent.rs b/tests/cfail/exception-divergent.rs index 692a57c796..3fe9a36528 100644 --- a/tests/cfail/exception-divergent.rs +++ b/tests/cfail/exception-divergent.rs @@ -10,11 +10,11 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} #[exception] - fn SVCall() -> ! { - //~^ ERROR `exception` handlers must have type signature `[unsafe] fn()` + fn SVCall(_: SVCall::Context) -> ! { + //~^ ERROR this `exception` handler must have type signature `fn(SVCall::Context)` loop {} } }; diff --git a/tests/cfail/exception-input.rs b/tests/cfail/exception-input.rs index cb0711ced0..d1363fe58d 100644 --- a/tests/cfail/exception-input.rs +++ b/tests/cfail/exception-input.rs @@ -10,10 +10,10 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} #[exception] - fn SVCall(undef: u32) { - //~^ ERROR `exception` handlers must have type signature `[unsafe] fn()` + fn SVCall(_: SVCall::Context, undef: u32) { + //~^ ERROR this `exception` handler must have type signature `fn(SVCall::Context)` } }; diff --git a/tests/cfail/exception-invalid.rs b/tests/cfail/exception-invalid.rs index 0a7fb520a2..4bb8f1ec08 100644 --- a/tests/cfail/exception-invalid.rs +++ b/tests/cfail/exception-invalid.rs @@ -10,10 +10,10 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} #[exception] - fn NonMaskableInt() { + fn NonMaskableInt(_: NonMaskableInt::Context) { //~^ ERROR only exceptions with configurable priority can be used as hardware tasks } }; diff --git a/tests/cfail/exception-output.rs b/tests/cfail/exception-output.rs index 758dbdd772..8f6729857e 100644 --- a/tests/cfail/exception-output.rs +++ b/tests/cfail/exception-output.rs @@ -10,11 +10,11 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} #[exception] - fn SVCall() -> u32 { - //~^ ERROR `exception` handlers must have type signature `[unsafe] fn()` + fn SVCall(_: SVCall::Context) -> u32 { + //~^ ERROR this `exception` handler must have type signature `fn(SVCall::Context)` 0 } }; diff --git a/tests/cfail/exception-sys-tick.rs b/tests/cfail/exception-sys-tick.rs index 69d73dbc43..d5eae20baa 100644 --- a/tests/cfail/exception-sys-tick.rs +++ b/tests/cfail/exception-sys-tick.rs @@ -10,10 +10,10 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} #[exception] - fn SysTick() { + fn SysTick(_: SysTick::Context) { //~^ ERROR the `SysTick` exception can't be used because it's used by the runtime } }; diff --git a/tests/cfail/idle-input.rs b/tests/cfail/idle-input.rs index 5095977e03..feb83e8b72 100644 --- a/tests/cfail/idle-input.rs +++ b/tests/cfail/idle-input.rs @@ -10,10 +10,10 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} #[idle] - fn idle(undef: u32) { - //~^ ERROR `idle` must have type signature `[unsafe] fn() -> !` + fn idle(_: idle::Context, undef: u32) { + //~^ ERROR `idle` must have type signature `fn(idle::Context) -> !` } }; diff --git a/tests/cfail/idle-not-divergent.rs b/tests/cfail/idle-not-divergent.rs index e90eff08aa..505fba1455 100644 --- a/tests/cfail/idle-not-divergent.rs +++ b/tests/cfail/idle-not-divergent.rs @@ -10,10 +10,10 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} #[idle] - fn idle() { - //~^ ERROR `idle` must have type signature `[unsafe] fn() -> !` + fn idle(_: idle::Context) { + //~^ ERROR `idle` must have type signature `fn(idle::Context) -> !` } }; diff --git a/tests/cfail/init-divergent.rs b/tests/cfail/init-divergent.rs index 54813d479f..0e779ffcee 100644 --- a/tests/cfail/init-divergent.rs +++ b/tests/cfail/init-divergent.rs @@ -10,8 +10,8 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() -> ! { - //~^ ERROR `init` must have type signature `[unsafe] fn() [-> init::LateResources]` + fn init(_: init::Context) -> ! { + //~^ ERROR `init` must have type signature `fn(init::Context) [-> init::LateResources]` loop {} } }; diff --git a/tests/cfail/init-input.rs b/tests/cfail/init-input.rs index 3bf0cadf13..9063efe355 100644 --- a/tests/cfail/init-input.rs +++ b/tests/cfail/init-input.rs @@ -10,7 +10,7 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init(undef: u32) { - //~^ ERROR `init` must have type signature `[unsafe] fn() [-> init::LateResources]` + fn init(_: init::Context, undef: u32) { + //~^ ERROR `init` must have type signature `fn(init::Context) [-> init::LateResources]` } }; diff --git a/tests/cfail/init-not-send.rs b/tests/cfail/init-not-send.rs index 3ac495f533..5a33fac764 100644 --- a/tests/cfail/init-not-send.rs +++ b/tests/cfail/init-not-send.rs @@ -1,6 +1,5 @@ //! This is equivalent to the `late-not-send` cfail test -#![feature(extern_crate_item_prelude)] // ??? #![no_main] #![no_std] @@ -21,10 +20,10 @@ const APP: () = { static mut X: Option = None; #[init(resources = [X])] - fn init() { - *resources.X = Some(NotSend { _0: PhantomData }) + fn init(c: init::Context) { + *c.resources.X = Some(NotSend { _0: PhantomData }) } #[interrupt(resources = [X])] - fn UART0() {} + fn UART0(_: UART0::Context) {} }; diff --git a/tests/cfail/init-output.rs b/tests/cfail/init-output.rs index 414a35a83a..f88d5340c0 100644 --- a/tests/cfail/init-output.rs +++ b/tests/cfail/init-output.rs @@ -10,8 +10,8 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() -> u32 { - //~^ ERROR `init` must have type signature `[unsafe] fn() [-> init::LateResources]` + fn init(_: init::Context) -> u32 { + //~^ ERROR `init` must have type signature `fn(init::Context) [-> init::LateResources]` 0 } }; diff --git a/tests/cfail/insufficient-free-interrupts.rs b/tests/cfail/insufficient-free-interrupts.rs index baa2582bc7..7148fbf34f 100644 --- a/tests/cfail/insufficient-free-interrupts.rs +++ b/tests/cfail/insufficient-free-interrupts.rs @@ -10,8 +10,8 @@ use rtfm::app; #[app(device = lm3s6965)] //~ ERROR 1 free interrupt (`extern { .. }`) is required const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} #[task] - fn foo() {} + fn foo(_: foo::Context) {} }; diff --git a/tests/cfail/interrupt-divergent.rs b/tests/cfail/interrupt-divergent.rs index 4a01533077..b67601eea5 100644 --- a/tests/cfail/interrupt-divergent.rs +++ b/tests/cfail/interrupt-divergent.rs @@ -10,11 +10,11 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} #[interrupt] - fn UART0() -> ! { - //~^ ERROR `interrupt` handlers must have type signature `[unsafe] fn()` + fn UART0(_: UART0::Context) -> ! { + //~^ ERROR this `interrupt` handler must have type signature `fn(UART0::Context)` loop {} } }; diff --git a/tests/cfail/interrupt-input.rs b/tests/cfail/interrupt-input.rs index d0240f4e6e..f11b2d3960 100644 --- a/tests/cfail/interrupt-input.rs +++ b/tests/cfail/interrupt-input.rs @@ -10,10 +10,10 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} #[interrupt] - fn UART0(undef: u32) { - //~^ ERROR `interrupt` handlers must have type signature `[unsafe] fn()` + fn UART0(_: UART0::Context, undef: u32) { + //~^ ERROR this `interrupt` handler must have type signature `fn(UART0::Context)` } }; diff --git a/tests/cfail/interrupt-output.rs b/tests/cfail/interrupt-output.rs index 37cd7c211e..69e4957fd8 100644 --- a/tests/cfail/interrupt-output.rs +++ b/tests/cfail/interrupt-output.rs @@ -10,11 +10,11 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} #[interrupt] - fn UART0() -> u32 { - //~^ ERROR `interrupt` handlers must have type signature `[unsafe] fn()` + fn UART0(_: UART0::Context) -> u32 { + //~^ ERROR this `interrupt` handler must have type signature `fn(UART0::Context)` 0 } }; diff --git a/tests/cfail/late-assigned-to-init.rs b/tests/cfail/late-assigned-to-init.rs index 70a361c1da..00d6c8ceed 100644 --- a/tests/cfail/late-assigned-to-init.rs +++ b/tests/cfail/late-assigned-to-init.rs @@ -12,5 +12,5 @@ const APP: () = { static mut X: u32 = (); #[init(resources = [X])] //~ ERROR late resources can NOT be assigned to `init` - fn init() {} + fn init(_: init::Context) {} }; diff --git a/tests/cfail/late-not-send.rs b/tests/cfail/late-not-send.rs index eb3048d90f..04a4af158b 100644 --- a/tests/cfail/late-not-send.rs +++ b/tests/cfail/late-not-send.rs @@ -1,7 +1,6 @@ //! `init` has a static priority of `0`. Initializing resources from it is equivalent to sending a //! message to the task that will own the resource -#![feature(extern_crate_item_prelude)] // ??? #![no_main] #![no_std] @@ -22,12 +21,12 @@ const APP: () = { static mut X: NotSend = (); #[init] - fn init() -> init::LateResources { + fn init(_: init::Context) -> init::LateResources { init::LateResources { X: NotSend { _0: PhantomData }, } } #[interrupt(resources = [X])] - fn UART0() {} + fn UART0(_: UART0::Context) {} }; diff --git a/tests/cfail/needs-send.rs b/tests/cfail/needs-send.rs index 7e3ca3062b..8dc9707fc9 100644 --- a/tests/cfail/needs-send.rs +++ b/tests/cfail/needs-send.rs @@ -1,4 +1,3 @@ -#![feature(extern_crate_item_prelude)] // ??? #![no_main] #![no_std] @@ -19,10 +18,10 @@ unsafe impl Sync for NotSend {} #[app(device = lm3s6965)] //~ ERROR cannot be sent between threads safely const APP: () = { #[init(spawn = [foo])] - fn init() {} + fn init(_: init::Context) {} #[task] - fn foo(_x: NotSend) {} + fn foo(_: foo::Context, _x: NotSend) {} extern "C" { fn UART0(); diff --git a/tests/cfail/needs-sync.rs b/tests/cfail/needs-sync.rs index f25f91a2f6..6025e7d56a 100644 --- a/tests/cfail/needs-sync.rs +++ b/tests/cfail/needs-sync.rs @@ -1,4 +1,3 @@ -#![feature(extern_crate_item_prelude)] // ??? #![no_main] #![no_std] @@ -21,13 +20,13 @@ const APP: () = { static X: NotSync = NotSync { _0: PhantomData }; #[init(spawn = [foo])] - fn init() {} + fn init(_: init::Context) {} #[task(priority = 1, resources = [X])] - fn foo() {} + fn foo(_: foo::Context) {} #[task(priority = 2, resources = [X])] - fn bar() {} + fn bar(_: bar::Context) {} extern "C" { fn UART0(); diff --git a/tests/cfail/priority-too-high.rs b/tests/cfail/priority-too-high.rs index ec324014b3..817462a350 100644 --- a/tests/cfail/priority-too-high.rs +++ b/tests/cfail/priority-too-high.rs @@ -10,13 +10,13 @@ use rtfm::app; #[app(device = lm3s6965)] //~ error evaluation of constant value failed const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} // OK, this is the maximum priority supported by the device #[interrupt(priority = 8)] - fn UART0() {} + fn UART0(_: UART0::Context) {} // this value is too high! #[interrupt(priority = 9)] - fn UART1() {} + fn UART1(_: UART1::Context) {} }; diff --git a/tests/cfail/priority-too-low.rs b/tests/cfail/priority-too-low.rs index 6dcbfd6348..361156df54 100644 --- a/tests/cfail/priority-too-low.rs +++ b/tests/cfail/priority-too-low.rs @@ -10,13 +10,13 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} // OK, this is the minimum priority that tasks can have #[interrupt(priority = 1)] - fn UART0() {} + fn UART0(_: UART0::Context) {} // this value is too low! #[interrupt(priority = 0)] //~ error this literal must be in the range 1...255 - fn UART1() {} + fn UART1(_: UART1::Context) {} }; diff --git a/tests/cfail/resource-not-declared.rs b/tests/cfail/resource-not-declared.rs index f6d08a65f4..a37be42d38 100644 --- a/tests/cfail/resource-not-declared.rs +++ b/tests/cfail/resource-not-declared.rs @@ -10,5 +10,5 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init(resources = [X])] //~ ERROR this resource has NOT been declared - fn init() {} + fn init(_: init::Context) {} }; diff --git a/tests/cfail/resource-pub.rs b/tests/cfail/resource-pub.rs index 970fc6cc05..3fb21f463c 100644 --- a/tests/cfail/resource-pub.rs +++ b/tests/cfail/resource-pub.rs @@ -13,5 +13,5 @@ const APP: () = { //~^ ERROR resources must have inherited / private visibility #[init] - fn init() {} + fn init(_: init::Context) {} }; diff --git a/tests/cfail/task-divergent.rs b/tests/cfail/task-divergent.rs index 3822d754ac..577f0e06aa 100644 --- a/tests/cfail/task-divergent.rs +++ b/tests/cfail/task-divergent.rs @@ -5,16 +5,14 @@ extern crate lm3s6965; extern crate panic_halt; extern crate rtfm; -use rtfm::app; - -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} #[task] - fn foo() -> ! { - //~^ ERROR `task` handlers must have type signature `[unsafe] fn(..)` + fn foo(_: foo::Context) -> ! { + //~^ ERROR this `task` handler must have type signature `fn(foo::Context, ..)` loop {} } diff --git a/tests/cfail/task-idle.rs b/tests/cfail/task-idle.rs index 62d927b923..963bf1ee81 100644 --- a/tests/cfail/task-idle.rs +++ b/tests/cfail/task-idle.rs @@ -10,10 +10,10 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} #[task] - fn idle() { + fn idle(_: idle::Context) { //~^ ERROR `task` handlers can NOT be named `idle`, `init` or `resources` } diff --git a/tests/cfail/task-not-declared.rs b/tests/cfail/task-not-declared.rs index 3e6d87c426..04309f599b 100644 --- a/tests/cfail/task-not-declared.rs +++ b/tests/cfail/task-not-declared.rs @@ -10,5 +10,5 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init(spawn = [X])] //~ ERROR this task has NOT been declared - fn init() {} + fn init(_: init::Context) {} }; diff --git a/tests/cfail/unsafe-exception.rs b/tests/cfail/unsafe-exception.rs new file mode 100644 index 0000000000..353194a590 --- /dev/null +++ b/tests/cfail/unsafe-exception.rs @@ -0,0 +1,18 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init(_: init::Context) {} + + #[exception(binds = SVCall)] + unsafe fn foo(_: foo::Context) {} + //~^ ERROR this `exception` handler must have type signature `fn(foo::Context)` +}; diff --git a/tests/cfail/unsafe-idle.rs b/tests/cfail/unsafe-idle.rs new file mode 100644 index 0000000000..fab1b0f1f6 --- /dev/null +++ b/tests/cfail/unsafe-idle.rs @@ -0,0 +1,20 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init(_: init::Context) {} + + #[idle] + unsafe fn idle(_: idle::Context) -> ! { + //~^ ERROR `idle` must have type signature `fn(idle::Context) -> !` + loop {} + } +}; diff --git a/tests/cfail/late-uninit.rs b/tests/cfail/unsafe-init.rs similarity index 54% rename from tests/cfail/late-uninit.rs rename to tests/cfail/unsafe-init.rs index 55122ed7e4..d8bb560571 100644 --- a/tests/cfail/late-uninit.rs +++ b/tests/cfail/unsafe-init.rs @@ -1,5 +1,3 @@ -// TODO remove in v0.5.x - #![no_main] #![no_std] @@ -11,8 +9,7 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { - static mut X: u32 = (); //~ ERROR late resources MUST be initialized at the end of `init` - #[init] - fn init() {} + unsafe fn init(_: init::Context) {} + //~^ ERROR `init` must have type signature `fn(init::Context) [-> init::LateResources]` }; diff --git a/tests/cfail/unsafe-interrupt.rs b/tests/cfail/unsafe-interrupt.rs new file mode 100644 index 0000000000..93225edf14 --- /dev/null +++ b/tests/cfail/unsafe-interrupt.rs @@ -0,0 +1,18 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init(_: init::Context) {} + + #[interrupt(binds = UART0)] + unsafe fn foo(_: foo::Context) {} + //~^ ERROR this `interrupt` handler must have type signature `fn(foo::Context)` +}; diff --git a/tests/cfail/unsafe-task.rs b/tests/cfail/unsafe-task.rs new file mode 100644 index 0000000000..58c4d70c21 --- /dev/null +++ b/tests/cfail/unsafe-task.rs @@ -0,0 +1,22 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init(_: init::Context) {} + + #[task] + unsafe fn foo(_: foo::Context) {} + //~^ ERROR this `task` handler must have type signature `fn(foo::Context, ..)` + + extern "C" { + fn UART0(); + } +}; diff --git a/tests/cfail/used-free-interrupt-2.rs b/tests/cfail/used-free-interrupt-2.rs index 616d308d0d..ba9424fd4d 100644 --- a/tests/cfail/used-free-interrupt-2.rs +++ b/tests/cfail/used-free-interrupt-2.rs @@ -10,10 +10,10 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} #[interrupt(binds = UART0)] //~ ERROR free interrupts (`extern { .. }`) can't be used as interrupt handlers - fn foo() {} + fn foo(_: foo::Context) {} extern "C" { fn UART0(); diff --git a/tests/cfail/used-free-interrupt.rs b/tests/cfail/used-free-interrupt.rs index 78ae540764..1a56741b14 100644 --- a/tests/cfail/used-free-interrupt.rs +++ b/tests/cfail/used-free-interrupt.rs @@ -10,10 +10,11 @@ use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} #[interrupt] - fn UART0() {} //~ ERROR free interrupts (`extern { .. }`) can't be used as interrupt handlers + fn UART0(_: UART0::Context) {} + //~^ ERROR free interrupts (`extern { .. }`) can't be used as interrupt handlers extern "C" { fn UART0(); diff --git a/tests/cpass/binds.rs b/tests/cpass/binds.rs index 7cb91741fd..897e083abc 100644 --- a/tests/cpass/binds.rs +++ b/tests/cpass/binds.rs @@ -1,4 +1,6 @@ //! Check that `binds` works as advertised +#![deny(unsafe_code)] +#![deny(warnings)] #![no_main] #![no_std] @@ -6,18 +8,20 @@ extern crate lm3s6965; extern crate panic_halt; extern crate rtfm; -use rtfm::app; - -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[init] - fn init() {} + fn init(_: init::Context) {} #[exception(binds = SVCall)] - fn foo() {} + fn foo(c: foo::Context) { + foo_trampoline(c) + } #[interrupt(binds = UART0)] - fn bar() {} + fn bar(c: bar::Context) { + bar_trampoline(c) + } }; #[allow(dead_code)] diff --git a/tests/cpass/cfg.rs b/tests/cpass/cfg.rs index c91ab60465..a0b6a870d0 100644 --- a/tests/cpass/cfg.rs +++ b/tests/cpass/cfg.rs @@ -6,24 +6,22 @@ #![no_std] extern crate lm3s6965; -extern crate panic_semihosting; +extern crate panic_halt; extern crate rtfm; -use rtfm::app; - -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[cfg(never)] static mut FOO: u32 = 0; #[init] - fn init() { + fn init(_: init::Context) { #[cfg(never)] static mut BAR: u32 = 0; } #[idle] - fn idle() -> ! { + fn idle(_: idle::Context) -> ! { #[cfg(never)] static mut BAR: u32 = 0; @@ -31,20 +29,20 @@ const APP: () = { } #[task(resources = [FOO], schedule = [quux], spawn = [quux])] - fn foo() { + fn foo(_: foo::Context) { #[cfg(never)] static mut BAR: u32 = 0; } #[task(priority = 3, resources = [FOO], schedule = [quux], spawn = [quux])] - fn bar() { + fn bar(_: bar::Context) { #[cfg(never)] static mut BAR: u32 = 0; } #[cfg(never)] #[task] - fn quux() {} + fn quux(_: quux::Context) {} extern "C" { fn UART0(); diff --git a/tests/cpass/late-not-send.rs b/tests/cpass/late-not-send.rs index 5b278ab5c3..0f69096748 100644 --- a/tests/cpass/late-not-send.rs +++ b/tests/cpass/late-not-send.rs @@ -1,3 +1,5 @@ +#![deny(unsafe_code)] +#![deny(warnings)] #![no_main] #![no_std] @@ -7,20 +9,18 @@ extern crate rtfm; use core::marker::PhantomData; -use rtfm::app; - pub struct NotSend { _0: PhantomData<*const ()>, } -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { static mut X: NotSend = (); static mut Y: Option = None; #[init(resources = [Y])] - fn init() -> init::LateResources { - *resources.Y = Some(NotSend { _0: PhantomData }); + fn init(c: init::Context) -> init::LateResources { + *c.resources.Y = Some(NotSend { _0: PhantomData }); init::LateResources { X: NotSend { _0: PhantomData }, @@ -28,7 +28,7 @@ const APP: () = { } #[idle(resources = [X, Y])] - fn idle() -> ! { + fn idle(_: idle::Context) -> ! { loop {} } }; diff --git a/tests/cpass/late-resource.rs b/tests/cpass/late-resource.rs index 0dec4cbee0..37dcf33163 100644 --- a/tests/cpass/late-resource.rs +++ b/tests/cpass/late-resource.rs @@ -1,4 +1,6 @@ //! Runtime initialized resources +#![deny(unsafe_code)] +#![deny(warnings)] #![no_main] #![no_std] @@ -6,15 +8,13 @@ extern crate lm3s6965; extern crate panic_halt; extern crate rtfm; -use rtfm::app; - -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { static mut X: u32 = (); static Y: u32 = (); #[init] - fn init() -> init::LateResources { + fn init(_: init::Context) -> init::LateResources { init::LateResources { X: 0, Y: 1 } } }; diff --git a/tests/cpass/peripheral.rs b/tests/cpass/peripheral.rs index 509a6be162..34352b845c 100644 --- a/tests/cpass/peripheral.rs +++ b/tests/cpass/peripheral.rs @@ -1,4 +1,6 @@ //! Core and device peripherals +#![deny(unsafe_code)] +#![deny(warnings)] #![no_main] #![no_std] @@ -6,13 +8,11 @@ extern crate lm3s6965; extern crate panic_halt; extern crate rtfm; -use rtfm::app; - -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[init] - fn init() { - let _: rtfm::Peripherals = core; - let _: lm3s6965::Peripherals = device; + fn init(c: init::Context) { + let _: rtfm::Peripherals = c.core; + let _: lm3s6965::Peripherals = c.device; } }; diff --git a/tests/cpass/resource.rs b/tests/cpass/resource.rs index bb8373939f..4e92a0320b 100644 --- a/tests/cpass/resource.rs +++ b/tests/cpass/resource.rs @@ -1,5 +1,7 @@ //! Check code generation of resources +#![deny(unsafe_code)] +#![deny(warnings)] #![no_main] #![no_std] @@ -7,9 +9,9 @@ extern crate lm3s6965; extern crate panic_halt; extern crate rtfm; -use rtfm::{app, Exclusive}; +use rtfm::Exclusive; -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { static mut O1: u32 = 0; // init static mut O2: u32 = 0; // idle @@ -23,57 +25,57 @@ const APP: () = { static S3: u32 = 0; #[init(resources = [O1, O4, O5, O6, S3])] - fn init() { + fn init(c: init::Context) { // owned by `init` == `&'static mut` - let _: &'static mut u32 = resources.O1; + let _: &'static mut u32 = c.resources.O1; // owned by `init` == `&'static` if read-only - let _: &'static u32 = resources.O6; + let _: &'static u32 = c.resources.O6; // `init` has exclusive access to all resources - let _: &mut u32 = resources.O4; - let _: &mut u32 = resources.O5; - let _: &mut u32 = resources.S3; + let _: &mut u32 = c.resources.O4; + let _: &mut u32 = c.resources.O5; + let _: &mut u32 = c.resources.S3; } #[idle(resources = [O2, O4, S1, S3])] - fn idle() -> ! { + fn idle(mut c: idle::Context) -> ! { // owned by `idle` == `&'static mut` - let _: &'static mut u32 = resources.O2; + let _: &'static mut u32 = c.resources.O2; // owned by `idle` == `&'static` if read-only - let _: &'static u32 = resources.O4; + let _: &'static u32 = c.resources.O4; // shared with `idle` == `Mutex` - resources.S1.lock(|_| {}); + c.resources.S1.lock(|_| {}); // `&` if read-only - let _: &u32 = resources.S3; + let _: &u32 = c.resources.S3; loop {} } #[interrupt(resources = [O3, S1, S2, S3])] - fn UART0() { + fn UART0(c: UART0::Context) { // owned by interrupt == `&mut` - let _: &mut u32 = resources.O3; + let _: &mut u32 = c.resources.O3; // no `Mutex` proxy when access from highest priority task - let _: Exclusive = resources.S1; + let _: Exclusive = c.resources.S1; // no `Mutex` proxy when co-owned by cooperative (same priority) tasks - let _: Exclusive = resources.S2; + let _: Exclusive = c.resources.S2; // `&` if read-only - let _: &u32 = resources.S3; + let _: &u32 = c.resources.S3; } #[interrupt(resources = [S2, O5])] - fn UART1() { + fn UART1(c: UART1::Context) { // owned by interrupt == `&` if read-only - let _: &u32 = resources.O5; + let _: &u32 = c.resources.O5; // no `Mutex` proxy when co-owned by cooperative (same priority) tasks - let _: Exclusive = resources.S2; + let _: Exclusive = c.resources.S2; } }; diff --git a/tests/cpass/schedule.rs b/tests/cpass/schedule.rs index 0728d8b322..346f9124a6 100644 --- a/tests/cpass/schedule.rs +++ b/tests/cpass/schedule.rs @@ -1,3 +1,5 @@ +#![deny(unsafe_code)] +#![deny(warnings)] #![no_main] #![no_std] @@ -5,52 +7,52 @@ extern crate lm3s6965; extern crate panic_halt; extern crate rtfm; -use rtfm::{app, Instant}; +use rtfm::Instant; -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[init(schedule = [foo, bar, baz])] - fn init() { - let _: Result<(), ()> = schedule.foo(start + 10.cycles()); - let _: Result<(), u32> = schedule.bar(start + 20.cycles(), 0); - let _: Result<(), (u32, u32)> = schedule.baz(start + 30.cycles(), 0, 1); + fn init(c: init::Context) { + let _: Result<(), ()> = c.schedule.foo(c.start + 10.cycles()); + let _: Result<(), u32> = c.schedule.bar(c.start + 20.cycles(), 0); + let _: Result<(), (u32, u32)> = c.schedule.baz(c.start + 30.cycles(), 0, 1); } #[idle(schedule = [foo, bar, baz])] - fn idle() -> ! { - let _: Result<(), ()> = schedule.foo(Instant::now() + 40.cycles()); - let _: Result<(), u32> = schedule.bar(Instant::now() + 50.cycles(), 0); - let _: Result<(), (u32, u32)> = schedule.baz(Instant::now() + 60.cycles(), 0, 1); + fn idle(c: idle::Context) -> ! { + let _: Result<(), ()> = c.schedule.foo(Instant::now() + 40.cycles()); + let _: Result<(), u32> = c.schedule.bar(Instant::now() + 50.cycles(), 0); + let _: Result<(), (u32, u32)> = c.schedule.baz(Instant::now() + 60.cycles(), 0, 1); loop {} } #[exception(schedule = [foo, bar, baz])] - fn SVCall() { - let _: Result<(), ()> = schedule.foo(start + 70.cycles()); - let _: Result<(), u32> = schedule.bar(start + 80.cycles(), 0); - let _: Result<(), (u32, u32)> = schedule.baz(start + 90.cycles(), 0, 1); + fn SVCall(c: SVCall::Context) { + let _: Result<(), ()> = c.schedule.foo(c.start + 70.cycles()); + let _: Result<(), u32> = c.schedule.bar(c.start + 80.cycles(), 0); + let _: Result<(), (u32, u32)> = c.schedule.baz(c.start + 90.cycles(), 0, 1); } #[interrupt(schedule = [foo, bar, baz])] - fn UART0() { - let _: Result<(), ()> = schedule.foo(start + 100.cycles()); - let _: Result<(), u32> = schedule.bar(start + 110.cycles(), 0); - let _: Result<(), (u32, u32)> = schedule.baz(start + 120.cycles(), 0, 1); + fn UART0(c: UART0::Context) { + let _: Result<(), ()> = c.schedule.foo(c.start + 100.cycles()); + let _: Result<(), u32> = c.schedule.bar(c.start + 110.cycles(), 0); + let _: Result<(), (u32, u32)> = c.schedule.baz(c.start + 120.cycles(), 0, 1); } #[task(schedule = [foo, bar, baz])] - fn foo() { - let _: Result<(), ()> = schedule.foo(scheduled + 130.cycles()); - let _: Result<(), u32> = schedule.bar(scheduled + 140.cycles(), 0); - let _: Result<(), (u32, u32)> = schedule.baz(scheduled + 150.cycles(), 0, 1); + fn foo(c: foo::Context) { + let _: Result<(), ()> = c.schedule.foo(c.scheduled + 130.cycles()); + let _: Result<(), u32> = c.schedule.bar(c.scheduled + 140.cycles(), 0); + let _: Result<(), (u32, u32)> = c.schedule.baz(c.scheduled + 150.cycles(), 0, 1); } #[task] - fn bar(_x: u32) {} + fn bar(_: bar::Context, _x: u32) {} #[task] - fn baz(_x: u32, _y: u32) {} + fn baz(_: baz::Context, _x: u32, _y: u32) {} extern "C" { fn UART1(); diff --git a/tests/cpass/singleton.rs b/tests/cpass/singleton.rs deleted file mode 100644 index d50f8523ee..0000000000 --- a/tests/cpass/singleton.rs +++ /dev/null @@ -1,66 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate owned_singleton; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::{app, Exclusive}; - -#[app(device = lm3s6965)] -const APP: () = { - #[Singleton] - static mut O1: u32 = 0; - #[Singleton] - static mut O2: u32 = 0; - #[Singleton] - static mut O3: u32 = 0; - #[Singleton] - static O4: u32 = 0; - #[Singleton] - static O5: u32 = 0; - #[Singleton] - static O6: u32 = 0; - - #[Singleton] - static mut S1: u32 = 0; - #[Singleton] - static S2: u32 = 0; - - #[init(resources = [O1, O2, O3, O4, O5, O6, S1, S2])] - fn init() { - let _: O1 = resources.O1; - let _: &mut O2 = resources.O2; - let _: &mut O3 = resources.O3; - let _: O4 = resources.O4; - let _: &mut O5 = resources.O5; - let _: &mut O6 = resources.O6; - - let _: &mut S1 = resources.S1; - let _: &mut S2 = resources.S2; - } - - #[idle(resources = [O2, O5])] - fn idle() -> ! { - let _: O2 = resources.O2; - let _: O5 = resources.O5; - - loop {} - } - - #[interrupt(resources = [O3, O6, S1, S2])] - fn UART0() { - let _: &mut O3 = resources.O3; - let _: &O6 = resources.O6; - - let _: Exclusive = resources.S1; - let _: &S2 = resources.S2; - } - - #[interrupt(resources = [S1, S2])] - fn UART1() { - let _: Exclusive = resources.S1; - let _: &S2 = resources.S2; - } -}; diff --git a/tests/cpass/spawn.rs b/tests/cpass/spawn.rs index 3df606a4a0..0a27c4f6bc 100644 --- a/tests/cpass/spawn.rs +++ b/tests/cpass/spawn.rs @@ -1,4 +1,6 @@ //! Check code generation of `spawn` +#![deny(unsafe_code)] +#![deny(warnings)] #![no_main] #![no_std] @@ -6,52 +8,50 @@ extern crate lm3s6965; extern crate panic_halt; extern crate rtfm; -use rtfm::app; - -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[init(spawn = [foo, bar, baz])] - fn init() { - let _: Result<(), ()> = spawn.foo(); - let _: Result<(), u32> = spawn.bar(0); - let _: Result<(), (u32, u32)> = spawn.baz(0, 1); + fn init(c: init::Context) { + let _: Result<(), ()> = c.spawn.foo(); + let _: Result<(), u32> = c.spawn.bar(0); + let _: Result<(), (u32, u32)> = c.spawn.baz(0, 1); } #[idle(spawn = [foo, bar, baz])] - fn idle() -> ! { - let _: Result<(), ()> = spawn.foo(); - let _: Result<(), u32> = spawn.bar(0); - let _: Result<(), (u32, u32)> = spawn.baz(0, 1); + fn idle(c: idle::Context) -> ! { + let _: Result<(), ()> = c.spawn.foo(); + let _: Result<(), u32> = c.spawn.bar(0); + let _: Result<(), (u32, u32)> = c.spawn.baz(0, 1); loop {} } #[exception(spawn = [foo, bar, baz])] - fn SVCall() { - let _: Result<(), ()> = spawn.foo(); - let _: Result<(), u32> = spawn.bar(0); - let _: Result<(), (u32, u32)> = spawn.baz(0, 1); + fn SVCall(c: SVCall::Context) { + let _: Result<(), ()> = c.spawn.foo(); + let _: Result<(), u32> = c.spawn.bar(0); + let _: Result<(), (u32, u32)> = c.spawn.baz(0, 1); } #[interrupt(spawn = [foo, bar, baz])] - fn UART0() { - let _: Result<(), ()> = spawn.foo(); - let _: Result<(), u32> = spawn.bar(0); - let _: Result<(), (u32, u32)> = spawn.baz(0, 1); + fn UART0(c: UART0::Context) { + let _: Result<(), ()> = c.spawn.foo(); + let _: Result<(), u32> = c.spawn.bar(0); + let _: Result<(), (u32, u32)> = c.spawn.baz(0, 1); } #[task(spawn = [foo, bar, baz])] - fn foo() { - let _: Result<(), ()> = spawn.foo(); - let _: Result<(), u32> = spawn.bar(0); - let _: Result<(), (u32, u32)> = spawn.baz(0, 1); + fn foo(c: foo::Context) { + let _: Result<(), ()> = c.spawn.foo(); + let _: Result<(), u32> = c.spawn.bar(0); + let _: Result<(), (u32, u32)> = c.spawn.baz(0, 1); } #[task] - fn bar(_x: u32) {} + fn bar(_: bar::Context, _x: u32) {} #[task] - fn baz(_x: u32, _y: u32) {} + fn baz(_: baz::Context, _x: u32, _y: u32) {} extern "C" { fn UART1(); diff --git a/tests/cpass/unsafe.rs b/tests/cpass/unsafe.rs deleted file mode 100644 index b6996ad115..0000000000 --- a/tests/cpass/unsafe.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! Check code generation of `unsafe` `init` / `idle` / `exception` / `interrupt` / `task` -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -unsafe fn foo() {} - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - unsafe fn init() { - foo(); - } - - #[idle] - unsafe fn idle() -> ! { - foo(); - - loop {} - } - - #[exception] - unsafe fn SVCall() { - foo(); - } - - #[interrupt] - unsafe fn UART0() { - foo(); - } - - #[task] - unsafe fn bar() { - foo(); - } - - extern "C" { - fn UART1(); - } -};