diff --git a/Cargo.toml b/Cargo.toml index ef6ac65499..81ca256c77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,56 +17,72 @@ version = "0.5.0-alpha.1" [lib] name = "rtfm" +[[test]] +required-features = ["heterogeneous"] +name = "multi" + [[example]] name = "baseline" -required-features = ["timer-queue"] +required-features = ["__v7"] [[example]] name = "periodic" -required-features = ["timer-queue"] +required-features = ["__v7"] [[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"] +required-features = ["__v7"] [[example]] name = "schedule" -required-features = ["timer-queue"] +required-features = ["__v7"] + +[[example]] +name = "t-cfg" +required-features = ["__v7"] + +[[example]] +name = "t-schedule" +required-features = ["__v7"] [[example]] name = "types" -required-features = ["timer-queue"] +required-features = ["__v7"] [dependencies] -cortex-m = "0.5.8" -cortex-m-rt = "0.6.7" -cortex-m-rtfm-macros = { path = "macros", version = "0.5.0-alpha.1" } -heapless = "0.5.0-alpha.1" +cortex-m = "0.6.0" +cortex-m-rtfm-macros = { path = "macros" } +rtfm-core = { git = "https://github.com/japaric/rtfm-core" } +cortex-m-rt = "0.6.8" +heapless = "0.5.0-alpha.2" + +[dependencies.microamp] +optional = true +version = "0.1.0-alpha.1" [dev-dependencies] -cortex-m-semihosting = "0.3.2" lm3s6965 = "0.1.3" panic-halt = "0.2.0" +cortex-m-semihosting = "0.3.3" [dev-dependencies.panic-semihosting] features = ["exit"] -version = "0.5.1" - -[features] -timer-queue = ["cortex-m-rtfm-macros/timer-queue"] +version = "0.5.2" [target.x86_64-unknown-linux-gnu.dev-dependencies] -compiletest_rs = "0.3.21" -tempdir = "0.3.7" +compiletest_rs = "0.3.22" -[package.metadata.docs.rs] -features = ["timer-queue"] +[features] +heterogeneous = ["cortex-m-rtfm-macros/heterogeneous", "microamp"] +# used for testing this crate; do not use in applications +__v7 =[] [profile.release] codegen-units = 1 lto = true [workspace] -members = ["macros"] \ No newline at end of file +members = [ + "macros", + "mc", +] diff --git a/ci/install.sh b/ci/install.sh index 9000772307..fea846b6e0 100644 --- a/ci/install.sh +++ b/ci/install.sh @@ -1,10 +1,12 @@ set -euxo pipefail main() { - if [ $TARGET != x86_64-unknown-linux-gnu ]; then - rustup target add $TARGET + if [ $TARGET = x86_64-unknown-linux-gnu ]; then + ( cd .. && cargo install microamp-tools --version 0.1.0-alpha.2 -f ) fi + rustup target add $TARGET + mkdir qemu curl -L https://github.com/japaric/qemu-bin/raw/master/14.04/qemu-system-arm-2.12.0 > qemu/qemu-system-arm chmod +x qemu/qemu-system-arm diff --git a/ci/script.sh b/ci/script.sh index 2292d474ba..a6485cf766 100644 --- a/ci/script.sh +++ b/ci/script.sh @@ -37,46 +37,58 @@ main() { mkdir -p ci/builds if [ $T = x86_64-unknown-linux-gnu ]; then - # compile-fail and compile-pass tests + if [ $TRAVIS_RUST_VERSION = nightly ]; then + # compile-fail tests + cargo test --test single --target $T + cargo test --test multi --features heterogeneous --target $T - # TODO how to run a subset of these tests when timer-queue is disabled? - cargo test --features "timer-queue" --test compiletest --target $T + # multi-core compile-pass tests + pushd mc + local exs=( + smallest + x-init-2 + x-init + x-schedule + x-spawn + ) + for ex in ${exs[@]}; do + cargo microamp --example $ex --target thumbv7m-none-eabi,thumbv6m-none-eabi --check + done + + popd + + else + if [ $TRAVIS_RUST_VERSION != nightly ]; then + rm -f .cargo/config + cargo doc + ( cd book/en && mdbook build ) + ( cd book/ru && mdbook build ) + + local td=$(mktemp -d) + cp -r target/doc $td/api + mkdir $td/book + cp -r book/en/book $td/book/en + cp -r book/ru/book $td/book/ru + cp LICENSE-* $td/book/en + cp LICENSE-* $td/book/ru + + linkchecker $td/book/en/ + linkchecker $td/book/ru/ + linkchecker $td/api/rtfm/ + linkchecker $td/api/cortex_m_rtfm_macros/ + fi + fi cargo check --target $T - if [ $TARGET != thumbv6m-none-eabi ]; then - cargo check --features "timer-queue" --target $T - fi - - if [ $TRAVIS_RUST_VERSION != nightly ]; then - rm -f .cargo/config - if [ $TARGET != thumbv6m-none-eabi ]; then - cargo doc --features timer-queue - else - cargo doc - fi - ( cd book/en && mdbook build ) - ( cd book/ru && mdbook build ) - - local td=$(mktemp -d) - cp -r target/doc $td/api - mkdir $td/book - cp -r book/en/book $td/book/en - cp -r book/ru/book $td/book/ru - cp LICENSE-* $td/book/en - cp LICENSE-* $td/book/ru - - linkchecker $td/book/en/ - linkchecker $td/book/ru/ - linkchecker $td/api/rtfm/ - linkchecker $td/api/cortex_m_rtfm_macros/ - fi + ( cd macros && cargo test --target $T ) return fi - cargo check --target $T --examples - if [ $TARGET != thumbv6m-none-eabi ]; then - cargo check --features "timer-queue" --target $T --examples + if [ $TARGET = thumbv6m-none-eabi ]; then + cargo check --target $T --examples + else + cargo check --target $T --examples --features __v7 fi # run-pass tests @@ -108,74 +120,71 @@ main() { ) for ex in ${exs[@]}; do - if [ $ex = ramfunc ] && [ $T = thumbv6m-none-eabi ]; then - # LLD doesn't support this at the moment - continue - fi - if [ $ex = pool ]; then - if [ $TARGET != thumbv6m-none-eabi ]; then - local td=$(mktemp -d) - - local features="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 + if [ $TARGET = thumbv6m-none-eabi ]; then + continue fi + local td=$(mktemp -d) + + cargo run --example $ex --target $TARGET --features __v7 >\ + $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}___v7_debug_1.hex + + cargo run --example $ex --target $TARGET --features __v7 --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}___v7_release_1.hex + + rm -rf $td + continue fi - if [ $ex != types ]; then - arm_example "run" $ex "debug" "" "1" - arm_example "run" $ex "release" "" "1" + if [ $ex = types ]; then + if [ $TARGET = thumbv6m-none-eabi ]; then + continue + fi + + arm_example "run" $ex "debug" "__v7" "1" + arm_example "run" $ex "release" "__v7" "1" + + continue fi - if [ $TARGET != thumbv6m-none-eabi ]; then - arm_example "run" $ex "debug" "timer-queue" "1" - arm_example "run" $ex "release" "timer-queue" "1" - fi + arm_example "run" $ex "debug" "" "1" + arm_example "run" $ex "release" "" "1" done local built=() cargo clean for ex in ${exs[@]}; do - if [ $ex = ramfunc ] && [ $T = thumbv6m-none-eabi ]; then - # LLD doesn't support this at the moment - continue - fi + if [ $ex = types ] || [ $ex = pool ]; then + if [ $TARGET = thumbv6m-none-eabi ]; then + continue + fi - if [ $ex != types ] && [ $ex != pool ]; then + arm_example "build" $ex "debug" "__v7" "2" + cmp ci/builds/${ex}___v7_debug_1.hex \ + ci/builds/${ex}___v7_debug_2.hex + arm_example "build" $ex "release" "__v7" "2" + cmp ci/builds/${ex}___v7_release_1.hex \ + ci/builds/${ex}___v7_release_2.hex + else arm_example "build" $ex "debug" "" "2" cmp ci/builds/${ex}_debug_1.hex \ ci/builds/${ex}_debug_2.hex arm_example "build" $ex "release" "" "2" cmp ci/builds/${ex}_release_1.hex \ ci/builds/${ex}_release_2.hex - - built+=( $ex ) fi - if [ $TARGET != thumbv6m-none-eabi ]; then - arm_example "build" $ex "debug" "timer-queue" "2" - cmp ci/builds/${ex}_timer-queue_debug_1.hex \ - ci/builds/${ex}_timer-queue_debug_2.hex - arm_example "build" $ex "release" "timer-queue" "2" - cmp ci/builds/${ex}_timer-queue_release_1.hex \ - ci/builds/${ex}_timer-queue_release_2.hex - fi + built+=( $ex ) done ( cd target/$TARGET/release/examples/ && size ${built[@]} ) diff --git a/examples/baseline.rs b/examples/baseline.rs index d743107dc1..cc9b412c02 100644 --- a/examples/baseline.rs +++ b/examples/baseline.rs @@ -5,13 +5,12 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; +use panic_semihosting as _; // NOTE: does NOT properly work on QEMU -#[rtfm::app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)] const APP: () = { #[init(spawn = [foo])] fn init(c: init::Context) { diff --git a/examples/binds.rs b/examples/binds.rs index 3d2d9b541b..1959d7598b 100644 --- a/examples/binds.rs +++ b/examples/binds.rs @@ -5,10 +5,9 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; +use panic_semihosting as _; // `examples/interrupt.rs` rewritten to use `binds` #[rtfm::app(device = lm3s6965)] diff --git a/examples/capacity.rs b/examples/capacity.rs index 07edd9b8e1..e1a835ca62 100644 --- a/examples/capacity.rs +++ b/examples/capacity.rs @@ -5,10 +5,9 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { diff --git a/examples/cfg.rs b/examples/cfg.rs index 03f9dbdcb2..b1f65cfd03 100644 --- a/examples/cfg.rs +++ b/examples/cfg.rs @@ -5,10 +5,9 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - #[cfg(debug_assertions)] use cortex_m_semihosting::hprintln; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { @@ -21,12 +20,12 @@ const APP: () = { } #[task(priority = 3, resources = [COUNT], spawn = [log])] - fn foo(c: foo::Context) { + fn foo(_c: foo::Context) { #[cfg(debug_assertions)] { - *c.resources.COUNT += 1; + *_c.resources.COUNT += 1; - c.spawn.log(*c.resources.COUNT).ok(); + _c.spawn.log(*_c.resources.COUNT).ok(); } // this wouldn't compile in `release` mode diff --git a/examples/generics.rs b/examples/generics.rs index e624da39cf..a35ba23761 100644 --- a/examples/generics.rs +++ b/examples/generics.rs @@ -5,11 +5,10 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; -use rtfm::Mutex; +use panic_semihosting as _; +use rtfm::{Exclusive, Mutex}; #[rtfm::app(device = lm3s6965)] const APP: () = { @@ -35,17 +34,15 @@ const APP: () = { } #[interrupt(priority = 2, resources = [SHARED])] - fn UART1(mut c: UART1::Context) { + fn UART1(c: UART1::Context) { static mut STATE: u32 = 0; hprintln!("UART1(STATE = {})", *STATE).unwrap(); - // just to show that `SHARED` can be accessed directly and .. + // just to show that `SHARED` can be accessed directly *c.resources.SHARED += 0; - // .. also through a (no-op) `lock` - c.resources.SHARED.lock(|shared| *shared += 0); - advance(STATE, c.resources.SHARED); + advance(STATE, Exclusive(c.resources.SHARED)); } }; diff --git a/examples/idle.rs b/examples/idle.rs index d10cc43ed0..c6f676b0cf 100644 --- a/examples/idle.rs +++ b/examples/idle.rs @@ -5,9 +5,8 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { diff --git a/examples/init.rs b/examples/init.rs index df687794a8..361db4b76d 100644 --- a/examples/init.rs +++ b/examples/init.rs @@ -5,18 +5,17 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; +use panic_semihosting as _; -#[rtfm::app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965, peripherals = true)] const APP: () = { #[init] fn init(c: init::Context) { static mut X: u32 = 0; // Cortex-M peripherals - let _core: rtfm::Peripherals = c.core; + let _core: cortex_m::Peripherals = c.core; // Device specific peripherals let _device: lm3s6965::Peripherals = c.device; diff --git a/examples/interrupt.rs b/examples/interrupt.rs index dd6efa0df5..3fe8ff3552 100644 --- a/examples/interrupt.rs +++ b/examples/interrupt.rs @@ -5,10 +5,9 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { diff --git a/examples/late.rs b/examples/late.rs index 0074fb3233..4d48a6a2ad 100644 --- a/examples/late.rs +++ b/examples/late.rs @@ -5,20 +5,21 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; use heapless::{ consts::*, spsc::{Consumer, Producer, Queue}, }; use lm3s6965::Interrupt; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { // Late resources - static mut P: Producer<'static, u32, U4> = (); - static mut C: Consumer<'static, u32, U4> = (); + extern "C" { + static mut P: Producer<'static, u32, U4>; + static mut C: Consumer<'static, u32, U4>; + } #[init] fn init(_: init::Context) -> init::LateResources { diff --git a/examples/lock.rs b/examples/lock.rs index 814c73640f..b7d36b4129 100644 --- a/examples/lock.rs +++ b/examples/lock.rs @@ -5,10 +5,9 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { @@ -46,7 +45,7 @@ const APP: () = { } #[interrupt(priority = 2, resources = [SHARED])] - fn GPIOB(mut c: GPIOB::Context) { + fn GPIOB(c: GPIOB::Context) { // the higher priority task does *not* need a critical section *c.resources.SHARED += 1; diff --git a/examples/message.rs b/examples/message.rs index 1fd3b9d4c5..8bfed523d5 100644 --- a/examples/message.rs +++ b/examples/message.rs @@ -5,9 +5,8 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { diff --git a/examples/not-send.rs b/examples/not-send.rs index c1b6bcddf8..f240e511fa 100644 --- a/examples/not-send.rs +++ b/examples/not-send.rs @@ -5,11 +5,10 @@ #![no_main] #![no_std] -extern crate panic_halt; - use core::marker::PhantomData; use cortex_m_semihosting::debug; +use panic_halt as _; use rtfm::app; pub struct NotSend { @@ -38,13 +37,13 @@ const APP: () = { } #[task(priority = 2, resources = [SHARED])] - fn baz(mut c: baz::Context) { + fn baz(c: baz::Context) { // scenario 2: resource shared between tasks that run at the same priority *c.resources.SHARED = Some(NotSend { _0: PhantomData }); } #[task(priority = 2, resources = [SHARED])] - fn quux(mut c: quux::Context) { + fn quux(c: quux::Context) { // scenario 2 let _not_send = c.resources.SHARED.take().unwrap(); diff --git a/examples/not-sync.rs b/examples/not-sync.rs index bc71406571..6b4991115e 100644 --- a/examples/not-sync.rs +++ b/examples/not-sync.rs @@ -5,11 +5,10 @@ #![no_main] #![no_std] -extern crate panic_halt; - use core::marker::PhantomData; use cortex_m_semihosting::debug; +use panic_halt as _; pub struct NotSync { _0: PhantomData<*const ()>, diff --git a/examples/periodic.rs b/examples/periodic.rs index f784118367..b8910db24e 100644 --- a/examples/periodic.rs +++ b/examples/periodic.rs @@ -5,15 +5,14 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::hprintln; -use rtfm::Instant; +use panic_semihosting as _; +use rtfm::cyccnt::{Instant, U32Ext}; const PERIOD: u32 = 8_000_000; // NOTE: does NOT work on QEMU! -#[rtfm::app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)] const APP: () = { #[init(schedule = [foo])] fn init(c: init::Context) { diff --git a/examples/pool.rs b/examples/pool.rs index 0b594b192d..db321b533f 100644 --- a/examples/pool.rs +++ b/examples/pool.rs @@ -5,14 +5,13 @@ #![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 panic_semihosting as _; use rtfm::app; // Declare a pool of 128-byte memory blocks diff --git a/examples/ramfunc.rs b/examples/ramfunc.rs index 4b0d69c793..c38635ff1f 100644 --- a/examples/ramfunc.rs +++ b/examples/ramfunc.rs @@ -5,9 +5,8 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { diff --git a/examples/resource.rs b/examples/resource.rs index 06bdf395ae..8268950429 100644 --- a/examples/resource.rs +++ b/examples/resource.rs @@ -5,10 +5,9 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { @@ -33,7 +32,7 @@ const APP: () = { // `SHARED` can be access from this context #[interrupt(resources = [SHARED])] - fn UART0(mut c: UART0::Context) { + fn UART0(c: UART0::Context) { *c.resources.SHARED += 1; hprintln!("UART0: SHARED = {}", c.resources.SHARED).unwrap(); @@ -41,7 +40,7 @@ const APP: () = { // `SHARED` can be access from this context #[interrupt(resources = [SHARED])] - fn UART1(mut c: UART1::Context) { + fn UART1(c: UART1::Context) { *c.resources.SHARED += 1; hprintln!("UART1: SHARED = {}", c.resources.SHARED).unwrap(); diff --git a/examples/schedule.rs b/examples/schedule.rs index eaafb4c947..1cf2b1ea00 100644 --- a/examples/schedule.rs +++ b/examples/schedule.rs @@ -5,13 +5,12 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::hprintln; -use rtfm::Instant; +use panic_halt as _; +use rtfm::cyccnt::{Instant, U32Ext as _}; // NOTE: does NOT work on QEMU! -#[rtfm::app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)] const APP: () = { #[init(schedule = [foo, bar])] fn init(c: init::Context) { diff --git a/examples/shared-with-init.rs b/examples/shared-with-init.rs index 0fb9191cbd..1640ca9bbb 100644 --- a/examples/shared-with-init.rs +++ b/examples/shared-with-init.rs @@ -5,10 +5,9 @@ #![no_main] #![no_std] -extern crate panic_halt; - use cortex_m_semihosting::debug; use lm3s6965::Interrupt; +use panic_halt as _; use rtfm::app; pub struct MustBeSend; diff --git a/examples/smallest.rs b/examples/smallest.rs index c153716805..e4228061a4 100644 --- a/examples/smallest.rs +++ b/examples/smallest.rs @@ -5,13 +5,8 @@ #![no_main] #![no_std] -// panic-handler crate -extern crate panic_semihosting; - +use panic_semihosting as _; // panic handler use rtfm::app; #[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} -}; +const APP: () = {}; diff --git a/examples/static.rs b/examples/static.rs index 2e3b5b4197..eeb522f58b 100644 --- a/examples/static.rs +++ b/examples/static.rs @@ -5,14 +5,15 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { - static KEY: u32 = (); + extern "C" { + static KEY: u32; + } #[init] fn init(_: init::Context) -> init::LateResources { diff --git a/tests/cpass/binds.rs b/examples/t-binds.rs similarity index 81% rename from tests/cpass/binds.rs rename to examples/t-binds.rs index 897e083abc..b4693a47cb 100644 --- a/tests/cpass/binds.rs +++ b/examples/t-binds.rs @@ -1,12 +1,11 @@ -//! Check that `binds` works as advertised +//! [compile-pass] Check that `binds` works as advertised + #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; +use panic_halt as _; #[rtfm::app(device = lm3s6965)] const APP: () = { diff --git a/tests/cpass/cfg.rs b/examples/t-cfg.rs similarity index 78% rename from tests/cpass/cfg.rs rename to examples/t-cfg.rs index a0b6a870d0..158eef5586 100644 --- a/tests/cpass/cfg.rs +++ b/examples/t-cfg.rs @@ -1,15 +1,11 @@ -//! Compile-pass test that checks that `#[cfg]` attributes are respected +//! [compile-pass] check that `#[cfg]` attributes are respected -#![deny(unsafe_code)] -#![deny(warnings)] #![no_main] #![no_std] -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; +use panic_halt as _; -#[rtfm::app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)] const APP: () = { #[cfg(never)] static mut FOO: u32 = 0; diff --git a/tests/cpass/late-not-send.rs b/examples/t-late-not-send.rs similarity index 71% rename from tests/cpass/late-not-send.rs rename to examples/t-late-not-send.rs index 0f69096748..55a053dfa2 100644 --- a/tests/cpass/late-not-send.rs +++ b/examples/t-late-not-send.rs @@ -1,25 +1,27 @@ -#![deny(unsafe_code)] -#![deny(warnings)] +//! [compile-pass] late resources don't need to be `Send` if they are owned by `idle` + #![no_main] #![no_std] -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - use core::marker::PhantomData; +use panic_halt as _; + pub struct NotSend { _0: PhantomData<*const ()>, } #[rtfm::app(device = lm3s6965)] const APP: () = { - static mut X: NotSend = (); + extern "C" { + static mut X: NotSend; + } + static mut Y: Option = None; #[init(resources = [Y])] fn init(c: init::Context) -> init::LateResources { + // equivalent to late resource initialization *c.resources.Y = Some(NotSend { _0: PhantomData }); init::LateResources { diff --git a/tests/cpass/resource.rs b/examples/t-resource.rs similarity index 87% rename from tests/cpass/resource.rs rename to examples/t-resource.rs index 4e92a0320b..40dc2a65ff 100644 --- a/tests/cpass/resource.rs +++ b/examples/t-resource.rs @@ -1,15 +1,11 @@ -//! Check code generation of resources +//! [compile-pass] Check code generation of resources #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::Exclusive; +use panic_halt as _; #[rtfm::app(device = lm3s6965)] const APP: () = { @@ -61,10 +57,10 @@ const APP: () = { let _: &mut u32 = c.resources.O3; // no `Mutex` proxy when access from highest priority task - let _: Exclusive = c.resources.S1; + let _: &mut u32 = c.resources.S1; // no `Mutex` proxy when co-owned by cooperative (same priority) tasks - let _: Exclusive = c.resources.S2; + let _: &mut u32 = c.resources.S2; // `&` if read-only let _: &u32 = c.resources.S3; @@ -76,6 +72,6 @@ const APP: () = { let _: &u32 = c.resources.O5; // no `Mutex` proxy when co-owned by cooperative (same priority) tasks - let _: Exclusive = c.resources.S2; + let _: &mut u32 = c.resources.S2; } }; diff --git a/tests/cpass/schedule.rs b/examples/t-schedule.rs similarity index 91% rename from tests/cpass/schedule.rs rename to examples/t-schedule.rs index 346f9124a6..67ff358b9a 100644 --- a/tests/cpass/schedule.rs +++ b/examples/t-schedule.rs @@ -1,15 +1,14 @@ +//! [compile-pass] Check `schedule` code generation + #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; +use panic_halt as _; +use rtfm::cyccnt::{Instant, U32Ext as _}; -use rtfm::Instant; - -#[rtfm::app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)] const APP: () = { #[init(schedule = [foo, bar, baz])] fn init(c: init::Context) { diff --git a/tests/cpass/spawn.rs b/examples/t-spawn.rs similarity index 93% rename from tests/cpass/spawn.rs rename to examples/t-spawn.rs index 0a27c4f6bc..6bb9b31fd0 100644 --- a/tests/cpass/spawn.rs +++ b/examples/t-spawn.rs @@ -1,12 +1,11 @@ -//! Check code generation of `spawn` +//! [compile-pass] Check code generation of `spawn` + #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; +use panic_halt as _; #[rtfm::app(device = lm3s6965)] const APP: () = { diff --git a/examples/task.rs b/examples/task.rs index 5bb32acb73..43f7e56914 100644 --- a/examples/task.rs +++ b/examples/task.rs @@ -5,9 +5,8 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { diff --git a/examples/types.rs b/examples/types.rs index c3dd89ca4c..2e72f0a00b 100644 --- a/examples/types.rs +++ b/examples/types.rs @@ -5,12 +5,11 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::debug; -use rtfm::{Exclusive, Instant}; +use panic_semihosting as _; +use rtfm::cyccnt::Instant; -#[rtfm::app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965, peripherals = true, monotonic = rtfm::cyccnt::CYCCNT)] const APP: () = { static mut SHARED: u32 = 0; @@ -43,7 +42,7 @@ const APP: () = { #[task(priority = 2, resources = [SHARED], schedule = [foo], spawn = [foo])] fn foo(c: foo::Context) { let _: Instant = c.scheduled; - let _: Exclusive = c.resources.SHARED; + let _: &mut u32 = c.resources.SHARED; let _: foo::Resources = c.resources; let _: foo::Schedule = c.schedule; let _: foo::Spawn = c.spawn; diff --git a/macros/Cargo.toml b/macros/Cargo.toml index df20f8c0e5..2854dad43a 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -15,12 +15,12 @@ version = "0.5.0-alpha.1" proc-macro = true [dependencies] -quote = "0.6.10" -proc-macro2 = "0.4.24" +proc-macro2 = "0.4.30" +quote = "0.6.12" +syn = "0.15.34" -[dependencies.syn] -features = ["extra-traits", "full"] -version = "0.15.23" +[dependencies.rtfm-syntax] +git = "https://github.com/japaric/rtfm-syntax" [features] -timer-queue = [] \ No newline at end of file +heterogeneous = [] diff --git a/macros/src/analyze.rs b/macros/src/analyze.rs index a47be77972..e3ed7781db 100644 --- a/macros/src/analyze.rs +++ b/macros/src/analyze.rs @@ -1,265 +1,59 @@ -use std::{ - cmp, - collections::{BTreeMap, HashMap, HashSet}, +use core::ops; +use std::collections::{BTreeMap, BTreeSet}; + +use rtfm_syntax::{ + analyze::{self, Priority}, + ast::App, + Core, P, }; +use syn::Ident; -use syn::{Attribute, Ident, Type}; - -use crate::syntax::{App, Idents}; - -pub type Ownerships = HashMap; - +/// Extend the upstream `Analysis` struct with our field pub struct Analysis { - /// Capacities of free queues - pub capacities: Capacities, - pub dispatchers: Dispatchers, - // Ceilings of free queues - pub free_queues: HashMap, - pub resources_assert_send: HashSet>, - pub tasks_assert_send: HashSet, - /// Types of RO resources that need to be Sync - pub assert_sync: HashSet>, - // Resource ownership - pub ownerships: Ownerships, - // Ceilings of ready queues - pub ready_queues: HashMap, - pub timer_queue: TimerQueue, + parent: P, + pub interrupts: BTreeMap>, } -#[derive(Clone, Copy, PartialEq)] -pub enum Ownership { - // NOTE priorities and ceilings are "logical" (0 = lowest priority, 255 = highest priority) - Owned { priority: u8 }, - CoOwned { priority: u8 }, - Shared { ceiling: u8 }, -} +impl ops::Deref for Analysis { + type Target = analyze::Analysis; -impl Ownership { - pub fn needs_lock(&self, priority: u8) -> bool { - match *self { - Ownership::Owned { .. } | Ownership::CoOwned { .. } => false, - Ownership::Shared { ceiling } => { - debug_assert!(ceiling >= priority); - - priority < ceiling - } - } - } - - pub fn is_owned(&self) -> bool { - match *self { - Ownership::Owned { .. } => true, - _ => false, - } + fn deref(&self) -> &Self::Target { + &self.parent } } -pub struct Dispatcher { - /// Attributes to apply to the dispatcher - pub attrs: Vec, - pub interrupt: Ident, - /// Tasks dispatched at this priority level - pub tasks: Vec, - // Queue capacity - pub capacity: u8, -} - -/// Priority -> Dispatcher -pub type Dispatchers = BTreeMap; - -pub type Capacities = HashMap; - -pub fn app(app: &App) -> Analysis { - // Ceiling analysis of R/W resource and Sync analysis of RO resources - // (Resource shared by tasks that run at different priorities need to be `Sync`) - let mut ownerships = Ownerships::new(); - let mut resources_assert_send = HashSet::new(); - let mut tasks_assert_send = HashSet::new(); - let mut assert_sync = HashSet::new(); - - for (priority, res) in app.resource_accesses() { - if let Some(ownership) = ownerships.get_mut(res) { - match *ownership { - Ownership::Owned { priority: ceiling } - | Ownership::CoOwned { priority: ceiling } - | Ownership::Shared { ceiling } - if priority != ceiling => - { - *ownership = Ownership::Shared { - ceiling: cmp::max(ceiling, priority), - }; - - let res = &app.resources[res]; - if res.mutability.is_none() { - assert_sync.insert(res.ty.clone()); - } +// Assign an `extern` interrupt to each priority level +pub fn app(analysis: P, app: &App) -> P { + let mut interrupts = BTreeMap::new(); + for core in 0..app.args.cores { + let priorities = app + .software_tasks + .values() + .filter_map(|task| { + if task.args.core == core { + Some(task.args.priority) + } else { + None } - Ownership::Owned { priority: ceiling } if ceiling == priority => { - *ownership = Ownership::CoOwned { priority }; - } - _ => {} - } + }) + .chain(analysis.timer_queues.get(&core).map(|tq| tq.priority)) + .collect::>(); - continue; - } - - ownerships.insert(res.clone(), Ownership::Owned { priority }); - } - - // Compute sizes of free queues - // We assume at most one message per `spawn` / `schedule` - let mut capacities: Capacities = app.tasks.keys().map(|task| (task.clone(), 0)).collect(); - for (_, task) in app.spawn_calls().chain(app.schedule_calls()) { - *capacities.get_mut(task).expect("BUG: capacities.get_mut") += 1; - } - - // Override computed capacities if user specified a capacity in `#[task]` - for (name, task) in &app.tasks { - if let Some(cap) = task.args.capacity { - *capacities.get_mut(name).expect("BUG: capacities.get_mut") = cap; + if !priorities.is_empty() { + interrupts.insert( + core, + priorities + .iter() + .cloned() + .rev() + .zip(app.extern_interrupts[&core].keys().cloned()) + .collect(), + ); } } - // Compute the size of the timer queue - // Compute the priority of the timer queue, which matches the priority of the highest - // `schedule`-able task - let mut tq_capacity = 0; - let mut tq_priority = 1; - let mut tq_tasks = Idents::new(); - for (_, task) in app.schedule_calls() { - tq_capacity += capacities[task]; - tq_priority = cmp::max(tq_priority, app.tasks[task].args.priority); - tq_tasks.insert(task.clone()); - } - - // Compute dispatchers capacities - // Determine which tasks are dispatched by which dispatcher - // Compute the timer queue priority which matches the priority of the highest priority - // dispatcher - let mut dispatchers = Dispatchers::new(); - let mut free_interrupts = app.free_interrupts.iter(); - let mut tasks = app.tasks.iter().collect::>(); - tasks.sort_by(|l, r| l.1.args.priority.cmp(&r.1.args.priority)); - for (name, task) in tasks { - let dispatcher = dispatchers.entry(task.args.priority).or_insert_with(|| { - let (name, fi) = free_interrupts - .next() - .expect("BUG: not enough free_interrupts"); - - Dispatcher { - attrs: fi.attrs.clone(), - capacity: 0, - interrupt: name.clone(), - tasks: vec![], - } - }); - - dispatcher.capacity += capacities[name]; - dispatcher.tasks.push(name.clone()); - } - - // All messages sent from `init` need to be `Send` - for task in app.init.args.spawn.iter().chain(&app.init.args.schedule) { - tasks_assert_send.insert(task.clone()); - } - - // All late resources need to be `Send`, unless they are owned by `idle` - for (name, res) in &app.resources { - let owned_by_idle = Ownership::Owned { priority: 0 }; - if res.expr.is_none() - && ownerships - .get(name) - .map(|ship| *ship != owned_by_idle) - .unwrap_or(false) - { - resources_assert_send.insert(res.ty.clone()); - } - } - - // All resources shared with init need to be `Send`, unless they are owned by `idle` - // This is equivalent to late initialization (e.g. `static mut LATE: Option = None`) - for name in &app.init.args.resources { - let owned_by_idle = Ownership::Owned { priority: 0 }; - if ownerships - .get(name) - .map(|ship| *ship != owned_by_idle) - .unwrap_or(false) - { - resources_assert_send.insert(app.resources[name].ty.clone()); - } - } - - // Ceiling analysis of free queues (consumer end point) -- first pass - // Ceiling analysis of ready queues (producer end point) -- first pass - // Also compute more Send-ness requirements - 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 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 - .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 - // priority of the receiving task - if app.tasks[task].args.priority != priority { - tasks_assert_send.insert(task.clone()); - } - } else { - // spawns from `init` are excluded from the ceiling 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 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 queue - tq_ceiling = cmp::max(tq_ceiling, priority); - } else { - // spawns from `init` are excluded from the ceiling analysis - } - } - - Analysis { - capacities, - dispatchers, - free_queues, - tasks_assert_send, - resources_assert_send, - assert_sync, - ownerships, - ready_queues, - timer_queue: TimerQueue { - capacity: tq_capacity, - ceiling: tq_ceiling, - priority: tq_priority, - tasks: tq_tasks, - }, - } -} - -pub struct TimerQueue { - pub capacity: u8, - pub ceiling: u8, - pub priority: u8, - pub tasks: Idents, + P::new(Analysis { + parent: analysis, + interrupts, + }) } diff --git a/macros/src/check.rs b/macros/src/check.rs index 8ad13f3c6c..c22a0f1fa1 100644 --- a/macros/src/check.rs +++ b/macros/src/check.rs @@ -1,122 +1,209 @@ -use std::{collections::HashSet, iter}; +use std::collections::HashSet; use proc_macro2::Span; -use syn::parse; +use rtfm_syntax::{ + analyze::Analysis, + ast::{App, CustomArg, HardwareTaskKind}, +}; +use syn::{parse, Path}; -use crate::syntax::App; +pub struct Extra<'a> { + pub device: &'a Path, + pub monotonic: Option<&'a Path>, + pub peripherals: Option, +} -pub fn app(app: &App) -> parse::Result<()> { - // Check that all referenced resources have been declared - for res in app - .idle - .as_ref() - .map(|idle| -> Box> { Box::new(idle.args.resources.iter()) }) - .unwrap_or_else(|| Box::new(iter::empty())) - .chain(&app.init.args.resources) - .chain(app.exceptions.values().flat_map(|e| &e.args.resources)) - .chain(app.interrupts.values().flat_map(|i| &i.args.resources)) - .chain(app.tasks.values().flat_map(|t| &t.args.resources)) +impl<'a> Extra<'a> { + pub fn monotonic(&self) -> &'a Path { + self.monotonic.expect("UNREACHABLE") + } +} + +pub fn app<'a>(app: &'a App, analysis: &Analysis) -> parse::Result> { + // check that all exceptions are valid; only exceptions with configurable priorities are + // accepted + for (name, task) in app + .hardware_tasks + .iter() + .filter(|(_, task)| task.kind == HardwareTaskKind::Exception) { - if !app.resources.contains_key(res) { - return Err(parse::Error::new( - res.span(), - "this resource has NOT been declared", - )); + let name_s = task.args.binds(name).to_string(); + match &*name_s { + // NOTE that some of these don't exist on ARMv6-M but we don't check that here -- the + // code we generate will check that the exception actually exists on ARMv6-M + "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" + | "DebugMonitor" | "PendSV" => {} // OK + + "SysTick" => { + if analysis.timer_queues.get(&task.args.core).is_some() { + return Err(parse::Error::new( + name.span(), + "this exception can't be used because it's being used by the runtime", + )); + } else { + // OK + } + } + + _ => { + return Err(parse::Error::new( + name.span(), + "only exceptions with configurable priority can be used as hardware tasks", + )); + } } } - // Check that late resources have not been assigned to `init` - for res in &app.init.args.resources { - if app.resources.get(res).unwrap().expr.is_none() { - return Err(parse::Error::new( - res.span(), - "late resources can NOT be assigned to `init`", - )); - } - } - - if app.resources.iter().any(|(_, res)| res.expr.is_none()) { - // Check that `init` returns `LateResources` if there's any declared late resource - if !app.init.returns_late_resources { - return Err(parse::Error::new( - app.init.span, - "late resources have been specified so `init` must return `init::LateResources`", - )); - } - } else if app.init.returns_late_resources { - // If there are no late resources the signature should be `fn(init::Context)` - if app.init.returns_late_resources { - return Err(parse::Error::new( - app.init.span, - "`init` signature must be `fn(init::Context)` if there are no late resources", - )); - } - } - - // Check that all referenced tasks have been declared - for task in app - .idle - .as_ref() - .map(|idle| -> Box> { - Box::new(idle.args.schedule.iter().chain(&idle.args.spawn)) - }) - .unwrap_or_else(|| Box::new(iter::empty())) - .chain(&app.init.args.schedule) - .chain(&app.init.args.spawn) - .chain( - app.exceptions - .values() - .flat_map(|e| e.args.schedule.iter().chain(&e.args.spawn)), - ) - .chain( - app.interrupts - .values() - .flat_map(|i| i.args.schedule.iter().chain(&i.args.spawn)), - ) - .chain( - app.tasks - .values() - .flat_map(|t| t.args.schedule.iter().chain(&t.args.spawn)), - ) + // check that external (device-specific) interrupts are not named after known (Cortex-M) + // exceptions + for name in app + .extern_interrupts + .iter() + .flat_map(|(_, interrupts)| interrupts.keys()) { - if !app.tasks.contains_key(task) { - return Err(parse::Error::new( - task.span(), - "this task has NOT been declared", - )); + let name_s = name.to_string(); + + match &*name_s { + "NonMaskableInt" | "HardFault" | "MemoryManagement" | "BusFault" | "UsageFault" + | "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => { + return Err(parse::Error::new( + name.span(), + "Cortex-M exceptions can't be used as `extern` interrupts", + )); + } + + _ => {} } } - // Check that there are enough free interrupts to dispatch all tasks - let ndispatchers = app - .tasks - .values() - .map(|t| t.args.priority) - .collect::>() - .len(); - if ndispatchers > app.free_interrupts.len() { + // check that there are enough external interrupts to dispatch the software tasks and the timer + // queue handler + for core in 0..app.args.cores { + let mut first = None; + let priorities = app + .software_tasks + .iter() + .filter_map(|(name, task)| { + if task.args.core == core { + first = Some(name); + Some(task.args.priority) + } else { + None + } + }) + .chain(analysis.timer_queues.get(&core).map(|tq| tq.priority)) + .collect::>(); + + let need = priorities.len(); + let given = app + .extern_interrupts + .get(&core) + .map(|ei| ei.len()) + .unwrap_or(0); + if need > given { + let s = if app.args.cores == 1 { + format!( + "not enough `extern` interrupts to dispatch \ + all software tasks (need: {}; given: {})", + need, given + ) + } else { + format!( + "not enough `extern` interrupts to dispatch \ + all software tasks on this core (need: {}; given: {})", + need, given + ) + }; + + return Err(parse::Error::new(first.unwrap().span(), &s)); + } + } + + let mut device = None; + let mut monotonic = None; + let mut peripherals = None; + + for (k, v) in &app.args.custom { + let ks = k.to_string(); + + match &*ks { + "device" => match v { + CustomArg::Path(p) => device = Some(p), + + _ => { + return Err(parse::Error::new( + k.span(), + "unexpected argument value; this should be a path", + )); + } + }, + + "monotonic" => match v { + CustomArg::Path(p) => monotonic = Some(p), + + _ => { + return Err(parse::Error::new( + k.span(), + "unexpected argument value; this should be a path", + )); + } + }, + + "peripherals" => match v { + CustomArg::Bool(x) if app.args.cores == 1 => { + peripherals = if *x { Some(0) } else { None } + } + + CustomArg::UInt(x) if app.args.cores != 1 => { + peripherals = if *x < u64::from(app.args.cores) { + Some(*x as u8) + } else { + return Err(parse::Error::new( + k.span(), + &format!( + "unexpected argument value; \ + this should be an integer in the range 0..={}", + app.args.cores + ), + )); + } + } + + _ => { + return Err(parse::Error::new( + k.span(), + if app.args.cores == 1 { + "unexpected argument value; this should be a boolean" + } else { + "unexpected argument value; this should be an integer" + }, + )); + } + }, + + _ => { + return Err(parse::Error::new(k.span(), "unexpected argument")); + } + } + } + + if !analysis.timer_queues.is_empty() && monotonic.is_none() { return Err(parse::Error::new( Span::call_site(), - &*format!( - "{} free interrupt{} (`extern {{ .. }}`) {} required to dispatch all soft tasks", - ndispatchers, - if ndispatchers > 1 { "s" } else { "" }, - if ndispatchers > 1 { "are" } else { "is" }, - ), + "a `monotonic` timer must be specified to use the `schedule` API", )); } - // Check that free interrupts are not being used - for (handler, interrupt) in &app.interrupts { - let name = interrupt.args.binds(handler); - - if app.free_interrupts.contains_key(name) { - return Err(parse::Error::new( - name.span(), - "free interrupts (`extern { .. }`) can't be used as interrupt handlers", - )); - } + if let Some(device) = device { + Ok(Extra { + device, + monotonic, + peripherals, + }) + } else { + Err(parse::Error::new( + Span::call_site(), + "a `device` argument must be specified in `#[rtfm::app]`", + )) } - - Ok(()) } diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 88f11739e4..86b4a67ee0 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -1,136 +1,75 @@ -use proc_macro::TokenStream; -use std::collections::{BTreeMap, BTreeSet}; - -use proc_macro2::Span; +use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use syn::{ArgCaptured, Attribute, Ident, IntSuffix, LitInt}; +use rtfm_syntax::ast::App; -use crate::{ - analyze::{Analysis, Ownership}, - syntax::{App, Static}, -}; +use crate::{analyze::Analysis, check::Extra}; -pub fn app(name: &Ident, app: &App, analysis: &Analysis) -> TokenStream { - let (const_app_resources, mod_resources) = resources(app, analysis); +mod assertions; +mod dispatchers; +mod hardware_tasks; +mod idle; +mod init; +mod locals; +mod module; +mod post_init; +mod pre_init; +mod resources; +mod resources_struct; +mod schedule; +mod schedule_body; +mod software_tasks; +mod spawn; +mod spawn_body; +mod timer_queue; +mod util; - let ( - const_app_exceptions, - exception_mods, - exception_locals, - exception_resources, - user_exceptions, - ) = exceptions(app, analysis); +// TODO document the syntax here or in `rtfm-syntax` +pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { + let mut const_app = vec![]; + let mut mains = vec![]; + let mut root = vec![]; + let mut user = vec![]; - let ( - const_app_interrupts, - interrupt_mods, - interrupt_locals, - interrupt_resources, - user_interrupts, - ) = interrupts(app, analysis); + // generate a `main` function for each core + for core in 0..app.args.cores { + let assertion_stmts = assertions::codegen(core, analysis); - let (const_app_tasks, task_mods, task_locals, task_resources, user_tasks) = - tasks(app, analysis); + let (const_app_pre_init, pre_init_stmts) = pre_init::codegen(core, &app, analysis, extra); - let const_app_dispatchers = dispatchers(&app, analysis); + let (const_app_init, root_init, user_init, call_init) = + init::codegen(core, app, analysis, extra); - let const_app_spawn = spawn(app, analysis); + let (const_app_post_init, post_init_stmts) = post_init::codegen(core, analysis, extra); - let const_app_tq = timer_queue(app, analysis); + let (const_app_idle, root_idle, user_idle, call_idle) = + idle::codegen(core, app, analysis, extra); - let const_app_schedule = schedule(app); + user.push(quote!( + #user_init - let assertion_stmts = assertions(app, analysis); + #user_idle + )); - let pre_init_stmts = pre_init(&app, analysis); + root.push(quote!( + #(#root_init)* - let ( - const_app_init, - mod_init, - init_locals, - init_resources, - init_late_resources, - user_init, - call_init, - ) = init(app, analysis); + #(#root_idle)* + )); - let post_init_stmts = post_init(&app, analysis); - - let (const_app_idle, mod_idle, idle_locals, idle_resources, user_idle, call_idle) = - idle(app, analysis); - - let device = &app.args.device; - quote!( - #user_init - - #user_idle - - #(#user_exceptions)* - - #(#user_interrupts)* - - #(#user_tasks)* - - #mod_resources - - #init_locals - - #init_resources - - #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.push(quote!( + #(#const_app_pre_init)* #const_app_init + #(#const_app_post_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)* - + let cfg_core = util::cfg_core(core, app.args.cores); + mains.push(quote!( #[no_mangle] + #cfg_core unsafe fn main() -> ! { #(#assertion_stmts)* @@ -142,2297 +81,65 @@ pub fn app(name: &Ident, app: &App, analysis: &Analysis) -> TokenStream { #call_idle } - }; - ) - .into() -} - -/* 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 ty = &res.ty; - - if let Some(expr) = res.expr.as_ref() { - const_app.push(quote!( - #(#attrs)* - #(#cfgs)* - static mut #name: #ty = #expr; - )); - } else { - const_app.push(quote!( - #(#attrs)* - #(#cfgs)* - static mut #name: core::mem::MaybeUninit<#ty> = - core::mem::MaybeUninit::uninit(); - )); - } - - // generate a resource proxy when needed - if res.mutability.is_some() { - if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { - let ptr = if res.expr.is_none() { - quote!(#name.as_mut_ptr()) - } else { - quote!(&mut #name) - }; - - 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 mod_resources = if mod_resources.is_empty() { - quote!() - } else { - quote!(mod resources { - use rtfm::export::Priority; - - #(#mod_resources)* - }) - }; - - (const_app, mod_resources) -} - -// 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, -) -> ( - // 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![]; - - 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_instant - - rtfm::export::run(PRIORITY, || { - crate::#name( - #name::Locals::new(), - #name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant) - ) - }); - } - )); - - 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, - ); - - resources_structs.push(item); - - const_app.push(constructor); - } - - 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, - )); - - 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 stmts = &exception.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, - ) -} + let (const_app_resources, mod_resources) = resources::codegen(app, analysis, extra); -// 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![]; + let (const_app_hardware_tasks, root_hardware_tasks, user_hardware_tasks) = + hardware_tasks::codegen(app, analysis, extra); - 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 { - (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; + let (const_app_software_tasks, root_software_tasks, user_software_tasks) = + software_tasks::codegen(app, analysis, extra); - #let_instant + let const_app_dispatchers = dispatchers::codegen(app, analysis, extra); - // check that this interrupt exists - let _ = #device::Interrupt::#symbol; + let const_app_spawn = spawn::codegen(app, analysis, extra); - rtfm::export::run(PRIORITY, || { - crate::#name( - #name::Locals::new(), - #name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant) - ) - }); - } - )); + let const_app_timer_queue = timer_queue::codegen(app, analysis, extra); - 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, - ); + let const_app_schedule = schedule::codegen(app, extra); - 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!(core::mem::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: [core::mem::MaybeUninit; #cap_lit] = - [#(#elems,)*]; - )); - } - - const_app.push(quote!( - /// Buffer that holds the inputs of a task - static mut #task_inputs: [core::mem::MaybeUninit<#ty>; #cap_lit] = - [#(#elems,)*]; - )); - - 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>); - const_app.push(quote!( - #[doc = #doc] - static mut #task_fq: #fq_ty = unsafe { - rtfm::export::Queue(rtfm::export::i::Queue::u8_sc()) - }; - )); - let ptr = quote!(&mut #task_fq); - - if let Some(ceiling) = analysis.free_queues.get(name) { - const_app.push(quote!(struct #task_fq<'a> { - priority: &'a rtfm::export::Priority, - })); - - 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, - )); - - let attrs = &task.attrs; - let use_u32ext = if cfg!(feature = "timer-queue") { - Some(quote!( - use rtfm::U32Ext as _; - )) - } else { - None - }; - 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)* - #[allow(non_snake_case)] - fn #name(__locals: #name::Locals, #context: #name::Context #(,#inputs)*) { - use rtfm::Mutex as _; - #use_u32ext - - #(#lets;)* - - #(#stmts)* - } - )); - } - - ( - const_app, - mods, - locals_structs, - resources_structs, - user_code, - ) -} - -/// 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 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>); - items.push(quote!( - #[doc = #doc] - static mut #rq: #rq_ty = unsafe { - rtfm::export::Queue(rtfm::export::i::Queue::u8_sc()) - }; - )); - let ptr = quote!(&mut #rq); - - 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 cfgs = &app.tasks[task].cfgs; - - quote!( - #(#cfgs)* - #task - ) - }) - .collect::>(); - - let doc = format!( - "Software tasks to be dispatched at priority level {}", - level - ); - items.push(quote!( - #[allow(non_camel_case_types)] - #[derive(Clone, Copy)] - #[doc = #doc] - enum #t { - #(#variants,)* - } - )); - - let arms = dispatcher - .tasks - .iter() - .map(|name| { - let task = &app.tasks[name]; - let cfgs = &task.cfgs; - let (_, tupled, pats, _) = regroup_inputs(&task.inputs); - - let inputs = mk_inputs_ident(name); - let fq = mk_fq_ident(name); - - let input = quote!(#inputs.get_unchecked(usize::from(index)).as_ptr().read()); - let fq = quote!(#fq); - - let (let_instant, _instant) = if cfg!(feature = "timer-queue") { - let instants = mk_instants_ident(name); - let instant = - quote!(#instants.get_unchecked(usize::from(index)).as_ptr().read()); - - ( - Some(quote!(let instant = #instant;)), - Some(quote!(, instant)), - ) - } else { - (None, None) - }; - - let call = { - let pats = pats.clone(); - - quote!( - #name( - #name::Locals::new(), - #name::Context::new(priority #_instant) - #(,#pats)* - ) - ) - }; - - quote!( - #(#cfgs)* - #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 rq = quote!((&mut #rq)); - items.push(quote!( - #[doc = #doc] - #(#attrs)* - #[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; - - rtfm::export::run(PRIORITY, || { - while let Some((task, index)) = #rq.split().1.dequeue() { - match task { - #(#arms)* - } - } - }); - } - )); - } - - items -} - -/// Generates all the `Spawn.$task` related code -fn spawn(app: &App, analysis: &Analysis) -> Vec { - let mut items = vec![]; - - let mut seen = BTreeSet::new(); - for (spawner, spawnees) in app.spawn_callers() { - if spawnees.is_empty() { - continue; - } - - let mut methods = vec![]; - - 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 - } - )); - } - - 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<#lt> #spawner::Spawn<#lt> { - #(#methods)* - } - )); - } - - items -} - -/// 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![]; - - let tasks = &analysis.timer_queue.tasks; - - if tasks.is_empty() { - return items; - } - - let variants = tasks - .iter() - .map(|task| { - let cfgs = &app.tasks[task].cfgs; - quote!( - #(#cfgs)* - #task - ) - }) - .collect::>(); - - items.push(quote!( - /// `schedule`-dable tasks - #[allow(non_camel_case_types)] - #[derive(Clone, Copy)] - enum T { - #(#variants,)* - } - )); - - let cap = mk_typenum_capacity(analysis.timer_queue.capacity, false); - let ty = quote!(rtfm::export::TimerQueue); - items.push(quote!( - /// The timer queue - static mut TQ: core::mem::MaybeUninit<#ty> = core::mem::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 device = &app.args.device; - let arms = tasks - .iter() - .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)* - 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) - } - ) - }) - .collect::>(); - - let priority = analysis.timer_queue.priority; - items.push(quote!( - /// The system timer - #[no_mangle] - unsafe fn SysTick() { - use rtfm::Mutex as _; - - /// System timer priority - const PRIORITY: u8 = #priority; - - 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)* - } - } - }); - } - )); - - items -} - -/// 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();)); - - // 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() { - stmts.push(quote!(TQ.as_mut_ptr().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; - - stmts.push(quote!(core.NVIC.enable(#device::Interrupt::#name);)); - - // 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; - - stmts.push(quote!(core.NVIC.enable(#device::Interrupt::#name);)); - - // 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") { - stmts.push(quote!(core.DWT.ctrl.modify(|r| r & !1);)); - stmts.push(quote!(core.DWT.cyccnt.write(0);)); - } - - 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, - ) -} - -/// Generates code that we must run after `init` returns. See comments inside -fn post_init(app: &App, analysis: &Analysis) -> Vec { - let mut stmts = vec![]; - - 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.as_mut_ptr().write(late.#name);)); - } - - // 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), - );)); - } - - // 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), - );)); - } - - 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 -} - -// 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], - resources_prefix: bool, - name: &Ident, - ty: proc_macro2::TokenStream, - ceiling: u8, - ptr: proc_macro2::TokenStream, -) -> proc_macro2::TokenStream { - let path = if resources_prefix { - quote!(resources::#name) - } else { - quote!(#name) - }; - - let priority = if resources_prefix { - quote!(self.priority()) - } else { - quote!(self.priority) - }; - - let device = &app.args.device; + let name = &app.name; + let device = extra.device; quote!( - #(#cfgs)* - impl<'a> rtfm::Mutex for #path<'a> { - type T = #ty; + #(#user)* - #[inline(always)] - fn lock(&mut self, f: impl FnOnce(&mut #ty) -> R) -> R { - /// Priority ceiling - const CEILING: u8 = #ceiling; + #(#user_hardware_tasks)* - unsafe { - rtfm::export::lock( - #ptr, - #priority, - CEILING, - #device::NVIC_PRIO_BITS, - f, - ) - } - } - } + #(#user_software_tasks)* + + #(#root)* + + #(#mod_resources)* + + #(#root_hardware_tasks)* + + #(#root_software_tasks)* + + /// Implementation details + // the user can't access the items within this `const` item + const #name: () = { + /// Always include the device crate which contains the vector table + use #device as _; + + #(#const_app)* + + #(#const_app_resources)* + + #(#const_app_hardware_tasks)* + + #(#const_app_software_tasks)* + + #(#const_app_dispatchers)* + + #(#const_app_spawn)* + + #(#const_app_timer_queue)* + + #(#const_app_schedule)* + + #(#mains)* + }; ) } - -/// 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)).as_mut_ptr().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 - ( - quote!(#fq.dequeue()), - quote!(#rq.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)).as_mut_ptr().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 = quote!(#fq.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)).as_mut_ptr().write(instant); - - #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().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 - .checked_next_power_of_two() - .expect("capacity.next_power_of_two()") - } else { - capacity - }; - - let ident = Ident::new(&format!("U{}", capacity), Span::call_site()); - - quote!(rtfm::export::consts::#ident) -} - -/// e.g. `foo` -> `foo_INPUTS` -fn mk_inputs_ident(base: &Ident) -> Ident { - Ident::new(&format!("{}_INPUTS", base), Span::call_site()) -} - -/// e.g. `foo` -> `foo_INSTANTS` -fn mk_instants_ident(base: &Ident) -> Ident { - Ident::new(&format!("{}_INSTANTS", base), Span::call_site()) -} - -/// e.g. `foo` -> `foo_FQ` -fn mk_fq_ident(base: &Ident) -> Ident { - Ident::new(&format!("{}_FQ", base), Span::call_site()) -} - -/// e.g. `3` -> `RQ3` -fn mk_rq_ident(level: u8) -> Ident { - Ident::new(&format!("RQ{}", level), Span::call_site()) -} - -/// 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; - - ( - 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) - } -} - -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -enum Kind { - Exception(Ident), - Idle, - Init, - Interrupt(Ident), - Task(Ident), -} - -impl Kind { - fn ident(&self) -> Ident { - let span = Span::call_site(); - match self { - 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 - } - - fn is_init(&self) -> bool { - *self == Kind::Init - } - - fn runs_once(&self) -> bool { - match *self { - Kind::Init | Kind::Idle => true, - _ => false, - } - } -} diff --git a/macros/src/codegen/assertions.rs b/macros/src/codegen/assertions.rs new file mode 100644 index 0000000000..95268a2cf4 --- /dev/null +++ b/macros/src/codegen/assertions.rs @@ -0,0 +1,26 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::analyze::Analysis; + +/// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits +pub fn codegen(core: u8, analysis: &Analysis) -> Vec { + let mut stmts = vec![]; + + // we don't generate *all* assertions on all cores because the user could conditionally import a + // type only on some core (e.g. `#[cfg(core = "0")] use some::Type;`) + + if let Some(types) = analysis.send_types.get(&core) { + for ty in types { + stmts.push(quote!(rtfm::export::assert_send::<#ty>();)); + } + } + + if let Some(types) = analysis.sync_types.get(&core) { + for ty in types { + stmts.push(quote!(rtfm::export::assert_sync::<#ty>();)); + } + } + + stmts +} diff --git a/macros/src/codegen/dispatchers.rs b/macros/src/codegen/dispatchers.rs new file mode 100644 index 0000000000..65d25c789e --- /dev/null +++ b/macros/src/codegen/dispatchers.rs @@ -0,0 +1,178 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::ast::App; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates task dispatchers +pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec { + let mut items = vec![]; + + for (&receiver, dispatchers) in &analysis.channels { + let interrupts = &analysis.interrupts[&receiver]; + + for (&level, channels) in dispatchers { + let mut stmts = vec![]; + + for (&sender, channel) in channels { + let cfg_sender = util::cfg_core(sender, app.args.cores); + + let variants = channel + .tasks + .iter() + .map(|name| { + let cfgs = &app.software_tasks[name].cfgs; + + quote!( + #(#cfgs)* + #name + ) + }) + .collect::>(); + + let doc = format!( + "Software tasks spawned from core #{} to be dispatched at priority level {} by core #{}", + sender, level, receiver, + ); + let t = util::spawn_t_ident(receiver, level, sender); + items.push(quote!( + #[allow(non_camel_case_types)] + #[derive(Clone, Copy)] + #[doc = #doc] + enum #t { + #(#variants,)* + } + )); + + let n = util::capacity_typenum(channel.capacity, true); + let rq = util::rq_ident(receiver, level, sender); + let (rq_attr, rq_ty, rq_expr) = if sender == receiver { + ( + cfg_sender.clone(), + quote!(rtfm::export::SCRQ<#t, #n>), + quote!(rtfm::export::Queue(unsafe { + rtfm::export::iQueue::u8_sc() + })), + ) + } else { + ( + Some(quote!(#[rtfm::export::shared])), + quote!(rtfm::export::MCRQ<#t, #n>), + quote!(rtfm::export::Queue(rtfm::export::iQueue::u8())), + ) + }; + + let doc = format!( + "Queue of tasks sent by core #{} ready to be dispatched by core #{} at priority level {}", + sender, + receiver, + level + ); + items.push(quote!( + #[doc = #doc] + #rq_attr + static mut #rq: #rq_ty = #rq_expr; + )); + + if let Some(ceiling) = channel.ceiling { + items.push(quote!( + #cfg_sender + struct #rq<'a> { + priority: &'a rtfm::export::Priority, + } + )); + + items.push(util::impl_mutex( + extra, + &[], + cfg_sender.as_ref(), + false, + &rq, + rq_ty, + ceiling, + quote!(&mut #rq), + )); + } + + let arms = channel + .tasks + .iter() + .map(|name| { + let task = &app.software_tasks[name]; + let cfgs = &task.cfgs; + let fq = util::fq_ident(name, sender); + let inputs = util::inputs_ident(name, sender); + let (_, tupled, pats, _) = util::regroup_inputs(&task.inputs); + + let (let_instant, instant) = if app.uses_schedule(receiver) { + let instants = util::instants_ident(name, sender); + + ( + quote!( + let instant = + #instants.get_unchecked(usize::from(index)).as_ptr().read(); + ), + quote!(, instant), + ) + } else { + (quote!(), quote!()) + }; + + let locals_new = if task.locals.is_empty() { + quote!() + } else { + quote!(#name::Locals::new(),) + }; + + quote!( + #(#cfgs)* + #t::#name => { + let #tupled = + #inputs.get_unchecked(usize::from(index)).as_ptr().read(); + #let_instant + #fq.split().0.enqueue_unchecked(index); + let priority = &rtfm::export::Priority::new(PRIORITY); + #name( + #locals_new + #name::Context::new(priority #instant) + #(,#pats)* + ) + } + ) + }) + .collect::>(); + + stmts.push(quote!( + while let Some((task, index)) = #rq.split().1.dequeue() { + match task { + #(#arms)* + } + } + )); + } + + let doc = format!( + "Interrupt handler used by core #{} to dispatch tasks at priority {}", + receiver, level + ); + let cfg_receiver = util::cfg_core(receiver, app.args.cores); + let interrupt = &interrupts[&level]; + items.push(quote!( + #[allow(non_snake_case)] + #[doc = #doc] + #[no_mangle] + #cfg_receiver + unsafe fn #interrupt() { + /// The priority of this interrupt handler + const PRIORITY: u8 = #level; + + rtfm::export::run(PRIORITY, || { + #(#stmts)* + }); + } + )); + } + } + + items +} diff --git a/macros/src/codegen/hardware_tasks.rs b/macros/src/codegen/hardware_tasks.rs new file mode 100644 index 0000000000..e65bad5688 --- /dev/null +++ b/macros/src/codegen/hardware_tasks.rs @@ -0,0 +1,121 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{locals, module, resources_struct, util}, +}; + +/// Generate support code for hardware tasks (`#[exception]`s and `#[interrupt]`s) +pub fn codegen( + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // const_app_hardware_tasks -- interrupt handlers and `${task}Resources` constructors + Vec, + // root_hardware_tasks -- items that must be placed in the root of the crate: + // - `${task}Locals` structs + // - `${task}Resources` structs + // - `${task}` modules + Vec, + // user_hardware_tasks -- the `#[task]` functions written by the user + Vec, +) { + let mut const_app = vec![]; + let mut root = vec![]; + let mut user_tasks = vec![]; + + for (name, task) in &app.hardware_tasks { + let core = task.args.core; + let cfg_core = util::cfg_core(core, app.args.cores); + + let (let_instant, instant) = if app.uses_schedule(core) { + let m = extra.monotonic(); + + ( + Some(quote!(let instant = <#m as rtfm::Monotonic>::now();)), + Some(quote!(, instant)), + ) + } else { + (None, None) + }; + + let locals_new = if task.locals.is_empty() { + quote!() + } else { + quote!(#name::Locals::new(),) + }; + + let symbol = task.args.binds(name); + let priority = task.args.priority; + + const_app.push(quote!( + #[allow(non_snake_case)] + #[no_mangle] + #cfg_core + unsafe fn #symbol() { + const PRIORITY: u8 = #priority; + + #let_instant + + rtfm::export::run(PRIORITY, || { + crate::#name( + #locals_new + #name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant) + ) + }); + } + )); + + let mut needs_lt = false; + + // `${task}Resources` + if !task.args.resources.is_empty() { + let (item, constructor) = resources_struct::codegen( + Context::HardwareTask(name), + priority, + &mut needs_lt, + app, + analysis, + ); + + root.push(item); + + const_app.push(constructor); + } + + root.push(module::codegen( + Context::HardwareTask(name), + needs_lt, + app, + extra, + )); + + // `${task}Locals` + let mut locals_pat = None; + if !task.locals.is_empty() { + let (struct_, pat) = locals::codegen(Context::HardwareTask(name), &task.locals, app); + + root.push(struct_); + locals_pat = Some(pat); + } + + let attrs = &task.attrs; + let context = &task.context; + let stmts = &task.stmts; + user_tasks.push(quote!( + #(#attrs)* + #[allow(non_snake_case)] + fn #name(#(#locals_pat,)* #context: #name::Context) { + use rtfm::Mutex as _; + + #(#stmts)* + } + )); + } + + (const_app, root, user_tasks) +} diff --git a/macros/src/codegen/idle.rs b/macros/src/codegen/idle.rs new file mode 100644 index 0000000000..7af01c9132 --- /dev/null +++ b/macros/src/codegen/idle.rs @@ -0,0 +1,92 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{locals, module, resources_struct, util}, +}; + +/// Generates support code for `#[idle]` functions +pub fn codegen( + core: u8, + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // const_app_idle -- the `${idle}Resources` constructor + Option, + // root_idle -- items that must be placed in the root of the crate: + // - the `${idle}Locals` struct + // - the `${idle}Resources` struct + // - the `${idle}` module, which contains types like `${idle}::Context` + Vec, + // user_idle + Option, + // call_idle + TokenStream2, +) { + if let Some(idle) = app.idles.get(&core) { + let mut needs_lt = false; + let mut const_app = None; + let mut root_idle = vec![]; + let mut locals_pat = None; + let mut locals_new = None; + + if !idle.args.resources.is_empty() { + let (item, constructor) = + resources_struct::codegen(Context::Idle(core), 0, &mut needs_lt, app, analysis); + + root_idle.push(item); + const_app = Some(constructor); + } + + let name = &idle.name; + if !idle.locals.is_empty() { + let (locals, pat) = locals::codegen(Context::Idle(core), &idle.locals, app); + + locals_new = Some(quote!(#name::Locals::new())); + locals_pat = Some(pat); + root_idle.push(locals); + } + + root_idle.push(module::codegen(Context::Idle(core), needs_lt, app, extra)); + + let cfg_core = util::cfg_core(core, app.args.cores); + let attrs = &idle.attrs; + let context = &idle.context; + let stmts = &idle.stmts; + let user_idle = Some(quote!( + #cfg_core + #(#attrs)* + #[allow(non_snake_case)] + fn #name(#(#locals_pat,)* #context: #name::Context) -> ! { + use rtfm::Mutex as _; + + #(#stmts)* + } + )); + + let call_idle = quote!(#name( + #(#locals_new,)* + #name::Context::new(&rtfm::export::Priority::new(0)) + )); + + ( + const_app, + root_idle, + user_idle, + call_idle, + ) + } else { + ( + None, + vec![], + None, + quote!(loop { + rtfm::export::wfi() + }), + ) + } +} diff --git a/macros/src/codegen/init.rs b/macros/src/codegen/init.rs new file mode 100644 index 0000000000..271be94ca1 --- /dev/null +++ b/macros/src/codegen/init.rs @@ -0,0 +1,112 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{locals, module, resources_struct, util}, +}; + +/// Generates support code for `#[init]` functions +pub fn codegen( + core: u8, + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // const_app_idle -- the `${init}Resources` constructor + Option, + // root_init -- items that must be placed in the root of the crate: + // - the `${init}Locals` struct + // - the `${init}Resources` struct + // - the `${init}LateResources` struct + // - the `${init}` module, which contains types like `${init}::Context` + Vec, + // user_init -- the `#[init]` function written by the user + Option, + // call_init -- the call to the user `#[init]` if there's one + Option, +) { + if let Some(init) = app.inits.get(&core) { + let cfg_core = util::cfg_core(core, app.args.cores); + let mut needs_lt = false; + let name = &init.name; + + let mut root_init = vec![]; + + let ret = { + let late_fields = analysis + .late_resources + .get(&core) + .map(|resources| { + resources + .iter() + .map(|name| { + let ty = &app.late_resources[name].ty; + + quote!(pub #name: #ty) + }) + .collect::>() + }) + .unwrap_or(vec![]); + + if !late_fields.is_empty() { + let late_resources = util::late_resources_ident(&name); + + root_init.push(quote!( + /// Resources initialized at runtime + #cfg_core + #[allow(non_snake_case)] + pub struct #late_resources { + #(#late_fields),* + } + )); + + Some(quote!(-> #name::LateResources)) + } else { + None + } + }; + + let mut locals_pat = None; + let mut locals_new = None; + if !init.locals.is_empty() { + let (struct_, pat) = locals::codegen(Context::Init(core), &init.locals, app); + + locals_new = Some(quote!(#name::Locals::new())); + locals_pat = Some(pat); + root_init.push(struct_); + } + + let context = &init.context; + let attrs = &init.attrs; + let stmts = &init.stmts; + let user_init = Some(quote!( + #(#attrs)* + #cfg_core + #[allow(non_snake_case)] + fn #name(#(#locals_pat,)* #context: #name::Context) #ret { + #(#stmts)* + } + )); + + let mut const_app = None; + if !init.args.resources.is_empty() { + let (item, constructor) = + resources_struct::codegen(Context::Init(core), 0, &mut needs_lt, app, analysis); + + root_init.push(item); + const_app = Some(constructor); + } + + let call_init = + Some(quote!(let late = #name(#(#locals_new,)* #name::Context::new(core.into()));)); + + root_init.push(module::codegen(Context::Init(core), needs_lt, app, extra)); + + (const_app, root_init, user_init, call_init) + } else { + (None, vec![], None, None) + } +} diff --git a/macros/src/codegen/locals.rs b/macros/src/codegen/locals.rs new file mode 100644 index 0000000000..9663563730 --- /dev/null +++ b/macros/src/codegen/locals.rs @@ -0,0 +1,94 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ + ast::{App, Local}, + Context, Map, +}; + +use crate::codegen::util; + +pub fn codegen( + ctxt: Context, + locals: &Map, + app: &App, +) -> ( + // locals + TokenStream2, + // pat + TokenStream2, +) { + assert!(!locals.is_empty()); + + let runs_once = ctxt.runs_once(); + let ident = util::locals_ident(ctxt, app); + + let mut lt = None; + let mut fields = vec![]; + let mut items = vec![]; + let mut names = vec![]; + let mut values = vec![]; + let mut pats = vec![]; + let mut has_cfgs = false; + + for (name, local) in locals { + let lt = if runs_once { + quote!('static) + } else { + lt = Some(quote!('a)); + quote!('a) + }; + + let cfgs = &local.cfgs; + has_cfgs |= !cfgs.is_empty(); + + let expr = &local.expr; + let ty = &local.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 + )); + names.push(name); + pats.push(quote!( + #(#cfgs)* + #name + )); + } + + if lt.is_some() && has_cfgs { + 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),* + } + } + } + ); + + let ident = ctxt.ident(app); + ( + locals, + quote!(#ident::Locals { #(#pats,)* .. }: #ident::Locals), + ) +} diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs new file mode 100644 index 0000000000..5f077a2200 --- /dev/null +++ b/macros/src/codegen/module.rs @@ -0,0 +1,328 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; + +use crate::{check::Extra, codegen::util}; + +pub fn codegen(ctxt: Context, resources_tick: bool, app: &App, extra: &Extra) -> TokenStream2 { + let mut items = vec![]; + let mut fields = vec![]; + let mut values = vec![]; + + let name = ctxt.ident(app); + + let core = ctxt.core(app); + let mut needs_instant = false; + let mut lt = None; + match ctxt { + Context::Init(core) => { + if app.uses_schedule(core) { + let m = extra.monotonic(); + + fields.push(quote!( + /// System start time = `Instant(0 /* cycles */)` + pub start: <#m as rtfm::Monotonic>::Instant + )); + + values.push(quote!(start: <#m as rtfm::Monotonic>::zero())); + + fields.push(quote!( + /// Core (Cortex-M) peripherals minus the SysTick + pub core: rtfm::Peripherals + )); + } else { + fields.push(quote!( + /// Core (Cortex-M) peripherals + pub core: rtfm::export::Peripherals + )); + } + + if extra.peripherals == Some(core) { + let device = extra.device; + + fields.push(quote!( + /// Device peripherals + pub device: #device::Peripherals + )); + + values.push(quote!(device: #device::Peripherals::steal())); + } + + values.push(quote!(core)); + } + + Context::Idle(..) => {} + + Context::HardwareTask(..) => { + if app.uses_schedule(core) { + let m = extra.monotonic(); + + fields.push(quote!( + /// Time at which this handler started executing + pub start: <#m as rtfm::Monotonic>::Instant + )); + + values.push(quote!(start: instant)); + + needs_instant = true; + } + } + + Context::SoftwareTask(..) => { + if app.uses_schedule(core) { + let m = extra.monotonic(); + + fields.push(quote!( + /// The time at which this task was scheduled to run + pub scheduled: <#m as rtfm::Monotonic>::Instant + )); + + values.push(quote!(scheduled: instant)); + + needs_instant = true; + } + } + } + + if ctxt.has_locals(app) { + let ident = util::locals_ident(ctxt, app); + items.push(quote!( + #[doc(inline)] + pub use super::#ident as Locals; + )); + } + + if ctxt.has_resources(app) { + let ident = util::resources_ident(ctxt, app); + let lt = if resources_tick { + 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 ctxt.is_init() { + None + } else { + Some(quote!(priority)) + }; + values.push(quote!(resources: Resources::new(#priority))); + } + + if ctxt.uses_schedule(app) { + let doc = "Tasks that can be `schedule`-d from this context"; + if ctxt.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 ctxt.uses_spawn(app) { + let doc = "Tasks that can be `spawn`-ed from this context"; + if ctxt.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 ctxt.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 app.uses_schedule(core) { + let m = extra.monotonic(); + + needs_instant = true; + instant_method = Some(quote!( + pub unsafe fn instant(&self) -> <#m as rtfm::Monotonic>::Instant { + self.instant + } + )); + Some(quote!(instant: <#m as rtfm::Monotonic>::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 let Context::Init(core) = ctxt { + let init = &app.inits[&core]; + if init.returns_late_resources { + let late_resources = util::late_resources_ident(&init.name); + + items.push(quote!( + #[doc(inline)] + pub use super::#late_resources as LateResources; + )); + } + } + + let doc = match ctxt { + Context::Idle(_) => "Idle loop", + Context::Init(_) => "Initialization function", + Context::HardwareTask(_) => "Hardware task", + Context::SoftwareTask(_) => "Software task", + }; + + let core = if ctxt.is_init() { + if app.uses_schedule(core) { + Some(quote!(core: rtfm::Peripherals,)) + } else { + Some(quote!(core: rtfm::export::Peripherals,)) + } + } else { + None + }; + + let priority = if ctxt.is_init() { + None + } else { + Some(quote!(priority: &#lt rtfm::export::Priority)) + }; + + let instant = if needs_instant { + let m = extra.monotonic(); + + Some(quote!(, instant: <#m as rtfm::Monotonic>::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() { + let cfg_core = util::cfg_core(ctxt.core(app), app.args.cores); + + quote!( + #[allow(non_snake_case)] + #[doc = #doc] + #cfg_core + pub mod #name { + #(#items)* + } + ) + } else { + quote!() + } +} diff --git a/macros/src/codegen/post_init.rs b/macros/src/codegen/post_init.rs new file mode 100644 index 0000000000..f492d31d75 --- /dev/null +++ b/macros/src/codegen/post_init.rs @@ -0,0 +1,139 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates code that runs after `#[init]` returns +pub fn codegen( + core: u8, + analysis: &Analysis, + extra: &Extra, +) -> (Vec, Vec) { + let mut const_app = vec![]; + let mut stmts = vec![]; + + // initialize late resources + if let Some(late_resources) = analysis.late_resources.get(&core) { + for name in late_resources { + // if it's live + if analysis.locations.get(name).is_some() { + stmts.push(quote!(#name.as_mut_ptr().write(late.#name);)); + } + } + } + + if analysis.timer_queues.is_empty() { + // cross-initialization barriers -- notify *other* cores that their resources have been + // initialized + if analysis.initialization_barriers.contains_key(&core) { + let ib = util::init_barrier(core); + + const_app.push(quote!( + #[rtfm::export::shared] + static #ib: rtfm::export::Barrier = rtfm::export::Barrier::new(); + )); + + stmts.push(quote!( + #ib.release(); + )); + } + + // then wait until the other cores have initialized *our* resources + for (&initializer, users) in &analysis.initialization_barriers { + if users.contains(&core) { + let ib = util::init_barrier(initializer); + + stmts.push(quote!( + #ib.wait(); + )); + } + } + + // cross-spawn barriers: wait until other cores are ready to receive messages + for (&receiver, senders) in &analysis.spawn_barriers { + if senders.get(&core) == Some(&false) { + let sb = util::spawn_barrier(receiver); + + stmts.push(quote!( + #sb.wait(); + )); + } + } + } else { + // if the `schedule` API is used then we'll synchronize all cores to leave the + // `init`-ialization phase at the same time. In this case the rendezvous barrier makes the + // cross-initialization and spawn barriers unnecessary + + let m = extra.monotonic(); + + if analysis.timer_queues.len() == 1 { + // reset the monotonic timer / counter + stmts.push(quote!( + <#m as rtfm::Monotonic>::reset(); + )); + } else { + // in the multi-core case we need a rendezvous (RV) barrier between *all* the cores that + // use the `schedule` API; otherwise one of the cores could observe the before-reset + // value of the monotonic counter + // (this may be easier to implement with `AtomicU8.fetch_sub` but that API is not + // available on ARMv6-M) + + // this core will reset the monotonic counter + const FIRST: u8 = 0; + + if core == FIRST { + for &i in analysis.timer_queues.keys() { + let rv = util::rendezvous_ident(i); + + const_app.push(quote!( + #[rtfm::export::shared] + static #rv: rtfm::export::Barrier = rtfm::export::Barrier::new(); + )); + + // wait until all the other cores have reached the RV point + if i != FIRST { + stmts.push(quote!( + #rv.wait(); + )); + } + } + + let rv = util::rendezvous_ident(core); + stmts.push(quote!( + // the compiler fences are used to prevent `reset` from being re-ordering wrt to + // the atomic operations -- we don't know if `reset` contains load or store + // operations + + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + + // reset the counter + <#m as rtfm::Monotonic>::reset(); + + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + + // now unblock all the other cores + #rv.release(); + )); + } else { + let rv = util::rendezvous_ident(core); + + // let the first core know that we have reached the RV point + stmts.push(quote!( + #rv.release(); + )); + + let rv = util::rendezvous_ident(FIRST); + + // wait until the first core has reset the monotonic timer + stmts.push(quote!( + #rv.wait(); + )); + } + } + } + + // enable the interrupts -- this completes the `init`-ialization phase + stmts.push(quote!(rtfm::export::interrupt::enable();)); + + (const_app, stmts) +} diff --git a/macros/src/codegen/pre_init.rs b/macros/src/codegen/pre_init.rs new file mode 100644 index 0000000000..3ba17dcf4c --- /dev/null +++ b/macros/src/codegen/pre_init.rs @@ -0,0 +1,150 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::ast::{App, HardwareTaskKind}; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates code that runs before `#[init]` +pub fn codegen( + core: u8, + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // `const_app_pre_init` -- `static` variables for barriers + Vec, + // `pre_init_stmts` + Vec, +) { + let mut const_app = vec![]; + let mut stmts = vec![]; + + // disable interrupts -- `init` must run with interrupts disabled + stmts.push(quote!(rtfm::export::interrupt::disable();)); + + // populate this core `FreeQueue`s + for (name, senders) in &analysis.free_queues { + let task = &app.software_tasks[name]; + let cap = task.args.capacity; + + for &sender in senders.keys() { + if sender == core { + let fq = util::fq_ident(name, sender); + + stmts.push(quote!( + (0..#cap).for_each(|i| #fq.enqueue_unchecked(i)); + )); + } + } + } + + stmts.push(quote!( + let mut core = rtfm::export::Peripherals::steal(); + )); + + let device = extra.device; + let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); + + // unmask interrupts and set their priorities + for (&priority, name) in analysis + .interrupts + .get(&core) + .iter() + .flat_map(|interrupts| *interrupts) + .chain(app.hardware_tasks.iter().flat_map(|(name, task)| { + if task.kind == HardwareTaskKind::Interrupt { + Some((&task.args.priority, task.args.binds(name))) + } else { + // we do exceptions in another pass + None + } + })) + { + // compile time assert that this priority is supported by the device + stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + + // NOTE this also checks that the interrupt exists in the `Interrupt` enumeration + stmts.push(quote!( + core.NVIC.set_priority( + #device::Interrupt::#name, + rtfm::export::logical2hw(#priority, #nvic_prio_bits), + ); + )); + + // NOTE unmask the interrupt *after* setting its priority: changing the priority of a pended + // interrupt is implementation defined + stmts.push(quote!(core.NVIC.enable(#device::Interrupt::#name);)); + } + + // cross-spawn barriers: now that priorities have been set and the interrupts have been unmasked + // we are ready to receive messages from *other* cores + if analysis.spawn_barriers.contains_key(&core) { + let sb = util::spawn_barrier(core); + + const_app.push(quote!( + #[rtfm::export::shared] + static #sb: rtfm::export::Barrier = rtfm::export::Barrier::new(); + )); + + // unblock cores that may send us a message + stmts.push(quote!( + #sb.release(); + )); + } + + // set exception priorities + for (name, priority) in app.hardware_tasks.iter().filter_map(|(name, task)| { + if task.kind == HardwareTaskKind::Exception { + Some((task.args.binds(name), task.args.priority)) + } else { + None + } + }) { + // 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), + );)); + } + + // initialize the SysTick + if let Some(tq) = analysis.timer_queues.get(&core) { + let priority = tq.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), + );)); + + stmts.push(quote!( + core.SYST.set_clock_source(rtfm::export::SystClkSource::Core); + core.SYST.enable_counter(); + core.DCB.enable_trace(); + )); + } + + // if there's no user `#[idle]` then optimize returning from interrupt handlers + if app.idles.get(&core).is_none() { + // Set SLEEPONEXIT bit to enter sleep mode when returning from ISR + stmts.push(quote!(core.SCB.scr.modify(|r| r | 1 << 1);)); + } + + // cross-spawn barriers: wait until other cores are ready to receive messages + for (&receiver, senders) in &analysis.spawn_barriers { + // only block here if `init` can send messages to `receiver` + if senders.get(&core) == Some(&true) { + let sb = util::spawn_barrier(receiver); + + stmts.push(quote!( + #sb.wait(); + )); + } + } + + (const_app, stmts) +} diff --git a/macros/src/codegen/resources.rs b/macros/src/codegen/resources.rs new file mode 100644 index 0000000000..2dd10eac47 --- /dev/null +++ b/macros/src/codegen/resources.rs @@ -0,0 +1,115 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ + analyze::{Location, Ownership}, + ast::App, +}; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates `static [mut]` variables and resource proxies +pub fn codegen( + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // const_app -- the `static [mut]` variables behind the proxies + Vec, + // mod_resources -- the `resources` module + TokenStream2, +) { + let mut const_app = vec![]; + let mut mod_resources = vec![]; + + for (name, res, expr, loc) in app.resources(analysis) { + let cfgs = &res.cfgs; + let ty = &res.ty; + + { + let loc_attr = match loc { + Location::Owned { + core, + cross_initialized: false, + } => util::cfg_core(*core, app.args.cores), + + // shared `static`s and cross-initialized resources need to be in `.shared` memory + _ => Some(quote!(#[rtfm::export::shared])), + }; + + let (ty, expr) = if let Some(expr) = expr { + (quote!(#ty), quote!(#expr)) + } else { + ( + quote!(core::mem::MaybeUninit<#ty>), + quote!(core::mem::MaybeUninit::uninit()), + ) + }; + + let attrs = &res.attrs; + const_app.push(quote!( + #loc_attr + #(#attrs)* + #(#cfgs)* + static mut #name: #ty = #expr; + )); + } + + // generate a resource proxy if needed + if res.mutability.is_some() { + if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { + let cfg_core = util::cfg_core(loc.core().expect("UNREACHABLE"), app.args.cores); + + mod_resources.push(quote!( + #(#cfgs)* + #cfg_core + pub struct #name<'a> { + priority: &'a Priority, + } + + #(#cfgs)* + #cfg_core + 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 + } + } + )); + + let ptr = if expr.is_none() { + quote!(#name.as_mut_ptr()) + } else { + quote!(&mut #name) + }; + + const_app.push(util::impl_mutex( + extra, + cfgs, + cfg_core.as_ref(), + true, + name, + quote!(#ty), + *ceiling, + ptr, + )); + } + } + } + + let mod_resources = if mod_resources.is_empty() { + quote!() + } else { + quote!(mod resources { + use rtfm::export::Priority; + + #(#mod_resources)* + }) + }; + + (const_app, mod_resources) +} diff --git a/macros/src/codegen/resources_struct.rs b/macros/src/codegen/resources_struct.rs new file mode 100644 index 0000000000..0248f19945 --- /dev/null +++ b/macros/src/codegen/resources_struct.rs @@ -0,0 +1,178 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; + +use crate::{analyze::Analysis, codegen::util}; + +pub fn codegen( + ctxt: Context, + priority: u8, + needs_lt: &mut bool, + app: &App, + analysis: &Analysis, +) -> (TokenStream2, TokenStream2) { + let mut lt = None; + + let resources = match ctxt { + Context::Init(core) => &app.inits[&core].args.resources, + Context::Idle(core) => &app.idles[&core].args.resources, + Context::HardwareTask(name) => &app.hardware_tasks[name].args.resources, + Context::SoftwareTask(name) => &app.software_tasks[name].args.resources, + }; + + let mut fields = vec![]; + let mut values = vec![]; + let mut has_cfgs = false; + + for name in resources { + let (res, expr) = app.resource(name).expect("UNREACHABLE"); + + let cfgs = &res.cfgs; + has_cfgs |= !cfgs.is_empty(); + + let mut_ = res.mutability; + let ty = &res.ty; + + if ctxt.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]; + + 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 ctxt.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 { + fields.push(quote!( + #(#cfgs)* + pub #name: &#lt mut #ty + )); + } + } + + let is_late = expr.is_none(); + if is_late { + let expr = if mut_.is_some() { + quote!(&mut *#name.as_mut_ptr()) + } else { + quote!(&*#name.as_ptr()) + }; + + values.push(quote!( + #(#cfgs)* + #name: #expr + )); + } else { + values.push(quote!( + #(#cfgs)* + #name: &#mut_ #name + )); + } + } + } + + if lt.is_some() { + *needs_lt = true; + + // the struct could end up empty due to `cfg`s leading to an error due to `'a` being unused + if has_cfgs { + fields.push(quote!( + #[doc(hidden)] + pub __marker__: core::marker::PhantomData<&'a ()> + )); + + values.push(quote!(__marker__: core::marker::PhantomData)) + } + } + + let core = ctxt.core(app); + let cores = app.args.cores; + let cfg_core = util::cfg_core(core, cores); + let doc = format!("Resources `{}` has access to", ctxt.ident(app)); + let ident = util::resources_ident(ctxt, app); + let item = quote!( + #cfg_core + #[allow(non_snake_case)] + #[doc = #doc] + pub struct #ident<#lt> { + #(#fields,)* + } + ); + + let arg = if ctxt.is_init() { + None + } else { + Some(quote!(priority: &#lt rtfm::export::Priority)) + }; + let constructor = quote!( + #cfg_core + impl<#lt> #ident<#lt> { + #[inline(always)] + unsafe fn new(#arg) -> Self { + #ident { + #(#values,)* + } + } + } + ); + + (item, constructor) +} diff --git a/macros/src/codegen/schedule.rs b/macros/src/codegen/schedule.rs new file mode 100644 index 0000000000..57f01a2c79 --- /dev/null +++ b/macros/src/codegen/schedule.rs @@ -0,0 +1,95 @@ +use std::collections::{BTreeMap, HashSet}; + +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::ast::App; + +use crate::{ + check::Extra, + codegen::{schedule_body, util}, +}; + +/// Generates all `${ctxt}::Schedule` methods +pub fn codegen(app: &App, extra: &Extra) -> Vec { + let mut items = vec![]; + + let mut seen = BTreeMap::>::new(); + for (scheduler, schedulees) in app.schedule_callers() { + let m = extra.monotonic(); + let instant = quote!(<#m as rtfm::Monotonic>::Instant); + + let sender = scheduler.core(app); + let cfg_sender = util::cfg_core(sender, app.args.cores); + let seen = seen.entry(sender).or_default(); + let mut methods = vec![]; + + for name in schedulees { + let schedulee = &app.software_tasks[name]; + let cfgs = &schedulee.cfgs; + let (args, _, untupled, ty) = util::regroup_inputs(&schedulee.inputs); + let args = &args; + + if scheduler.is_init() { + // `init` uses a special `schedule` implementation; it doesn't use the + // `schedule_${name}` functions which are shared by other contexts + + let body = schedule_body::codegen(scheduler, &name, app); + + methods.push(quote!( + #(#cfgs)* + fn #name(&self, instant: #instant #(,#args)*) -> Result<(), #ty> { + #body + } + )); + } else { + let schedule = util::schedule_ident(name, sender); + + if !seen.contains(name) { + // generate a `schedule_${name}_S${sender}` function + seen.insert(name); + + let body = schedule_body::codegen(scheduler, &name, app); + + items.push(quote!( + #cfg_sender + #(#cfgs)* + unsafe fn #schedule( + priority: &rtfm::export::Priority, + instant: #instant + #(,#args)* + ) -> Result<(), #ty> { + #body + } + )); + } + + methods.push(quote!( + #(#cfgs)* + #[inline(always)] + fn #name(&self, instant: #instant #(,#args)*) -> Result<(), #ty> { + unsafe { + #schedule(self.priority(), instant #(,#untupled)*) + } + } + )); + } + } + + let lt = if scheduler.is_init() { + None + } else { + Some(quote!('a)) + }; + + let scheduler = scheduler.ident(app); + debug_assert!(!methods.is_empty()); + items.push(quote!( + #cfg_sender + impl<#lt> #scheduler::Schedule<#lt> { + #(#methods)* + } + )); + } + + items +} diff --git a/macros/src/codegen/schedule_body.rs b/macros/src/codegen/schedule_body.rs new file mode 100644 index 0000000000..208fd0b79e --- /dev/null +++ b/macros/src/codegen/schedule_body.rs @@ -0,0 +1,61 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; +use syn::Ident; + +use crate::codegen::util; + +pub fn codegen(scheduler: Context, name: &Ident, app: &App) -> TokenStream2 { + let sender = scheduler.core(app); + let schedulee = &app.software_tasks[name]; + let receiver = schedulee.args.core; + + let fq = util::fq_ident(name, sender); + let tq = util::tq_ident(sender); + let (dequeue, enqueue) = if scheduler.is_init() { + (quote!(#fq.dequeue()), quote!(#tq.enqueue_unchecked(nr);)) + } else { + ( + quote!((#fq { priority }).lock(|fq| fq.split().1.dequeue())), + quote!((#tq { priority }).lock(|tq| tq.enqueue_unchecked(nr));), + ) + }; + + let write_instant = if app.uses_schedule(receiver) { + let instants = util::instants_ident(name, sender); + + Some(quote!( + #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant); + )) + } else { + None + }; + + let (_, tupled, _, _) = util::regroup_inputs(&schedulee.inputs); + let inputs = util::inputs_ident(name, sender); + let t = util::schedule_t_ident(sender); + quote!( + unsafe { + use rtfm::Mutex as _; + + let input = #tupled; + if let Some(index) = #dequeue { + #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input); + + #write_instant + + let nr = rtfm::export::NotReady { + instant, + index, + task: #t::#name, + }; + + #enqueue + + Ok(()) + } else { + Err(input) + } + } + ) +} diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs new file mode 100644 index 0000000000..8b2c0cd5f3 --- /dev/null +++ b/macros/src/codegen/software_tasks.rs @@ -0,0 +1,169 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{locals, module, resources_struct, util}, +}; + +pub fn codegen( + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // const_app_software_tasks -- free queues, buffers and `${task}Resources` constructors + Vec, + // root_software_tasks -- items that must be placed in the root of the crate: + // - `${task}Locals` structs + // - `${task}Resources` structs + // - `${task}` modules + Vec, + // user_software_tasks -- the `#[task]` functions written by the user + Vec, +) { + let mut const_app = vec![]; + let mut root = vec![]; + let mut user_tasks = vec![]; + + for (name, task) in &app.software_tasks { + let receiver = task.args.core; + + let inputs = &task.inputs; + let (_, _, _, input_ty) = util::regroup_inputs(inputs); + + let cap = task.args.capacity; + let cap_lit = util::capacity_literal(cap); + let cap_ty = util::capacity_typenum(cap, true); + + // create free queues and inputs / instants buffers + if let Some(free_queues) = analysis.free_queues.get(name) { + for (&sender, &ceiling) in free_queues { + let cfg_sender = util::cfg_core(sender, app.args.cores); + let fq = util::fq_ident(name, sender); + + let (loc, fq_ty, fq_expr) = if receiver == sender { + ( + cfg_sender.clone(), + quote!(rtfm::export::SCFQ<#cap_ty>), + quote!(rtfm::export::Queue(unsafe { + rtfm::export::iQueue::u8_sc() + })), + ) + } else { + ( + Some(quote!(#[rtfm::export::shared])), + quote!(rtfm::export::MCFQ<#cap_ty>), + quote!(rtfm::export::Queue(rtfm::export::iQueue::u8())), + ) + }; + let loc = &loc; + + const_app.push(quote!( + /// Queue version of a free-list that keeps track of empty slots in + /// the following buffers + #loc + static mut #fq: #fq_ty = #fq_expr; + )); + + // Generate a resource proxy if needed + if let Some(ceiling) = ceiling { + const_app.push(quote!( + #cfg_sender + struct #fq<'a> { + priority: &'a rtfm::export::Priority, + } + )); + + const_app.push(util::impl_mutex( + extra, + &[], + cfg_sender.as_ref(), + false, + &fq, + fq_ty, + ceiling, + quote!(&mut #fq), + )); + } + + let ref elems = (0..cap) + .map(|_| quote!(core::mem::MaybeUninit::uninit())) + .collect::>(); + + if app.uses_schedule(receiver) { + let m = extra.monotonic(); + let instants = util::instants_ident(name, sender); + + const_app.push(quote!( + #loc + /// Buffer that holds the instants associated to the inputs of a task + static mut #instants: + [core::mem::MaybeUninit<<#m as rtfm::Monotonic>::Instant>; #cap_lit] = + [#(#elems,)*]; + )); + } + + let inputs = util::inputs_ident(name, sender); + const_app.push(quote!( + #loc + /// Buffer that holds the inputs of a task + static mut #inputs: [core::mem::MaybeUninit<#input_ty>; #cap_lit] = + [#(#elems,)*]; + )); + } + } + + // `${task}Resources` + let mut needs_lt = false; + if !task.args.resources.is_empty() { + let (item, constructor) = resources_struct::codegen( + Context::SoftwareTask(name), + task.args.priority, + &mut needs_lt, + app, + analysis, + ); + + root.push(item); + + const_app.push(constructor); + } + + // `${task}Locals` + let mut locals_pat = None; + if !task.locals.is_empty() { + let (struct_, pat) = locals::codegen(Context::SoftwareTask(name), &task.locals, app); + + locals_pat = Some(pat); + root.push(struct_); + } + + let cfg_receiver = util::cfg_core(receiver, app.args.cores); + let context = &task.context; + let attrs = &task.attrs; + let cfgs = &task.cfgs; + let stmts = &task.stmts; + user_tasks.push(quote!( + #(#attrs)* + #(#cfgs)* + #cfg_receiver + #[allow(non_snake_case)] + fn #name(#(#locals_pat,)* #context: #name::Context #(,#inputs)*) { + use rtfm::Mutex as _; + + #(#stmts)* + } + )); + + root.push(module::codegen( + Context::SoftwareTask(name), + needs_lt, + app, + extra, + )); + } + + (const_app, root, user_tasks) +} diff --git a/macros/src/codegen/spawn.rs b/macros/src/codegen/spawn.rs new file mode 100644 index 0000000000..1539e27722 --- /dev/null +++ b/macros/src/codegen/spawn.rs @@ -0,0 +1,127 @@ +use std::collections::{BTreeMap, HashSet}; + +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::ast::App; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{spawn_body, util}, +}; + +/// Generates all `${ctxt}::Spawn` methods +pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec { + let mut items = vec![]; + + let mut seen = BTreeMap::>::new(); + for (spawner, spawnees) in app.spawn_callers() { + let sender = spawner.core(app); + let cfg_sender = util::cfg_core(sender, app.args.cores); + let seen = seen.entry(sender).or_default(); + let mut methods = vec![]; + + for name in spawnees { + let spawnee = &app.software_tasks[name]; + let receiver = spawnee.args.core; + let cfgs = &spawnee.cfgs; + let (args, _, untupled, ty) = util::regroup_inputs(&spawnee.inputs); + let args = &args; + + 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 = spawn_body::codegen(spawner, &name, app, analysis, extra); + + let let_instant = if app.uses_schedule(receiver) { + let m = extra.monotonic(); + + Some(quote!(let instant = unsafe { <#m as rtfm::Monotonic>::zero() };)) + } else { + None + }; + + methods.push(quote!( + #(#cfgs)* + fn #name(&self #(,#args)*) -> Result<(), #ty> { + #let_instant + #body + } + )); + } else { + let spawn = util::spawn_ident(name, sender); + + if !seen.contains(name) { + // generate a `spawn_${name}_S${sender}` function + seen.insert(name); + + let instant = if app.uses_schedule(receiver) { + let m = extra.monotonic(); + + Some(quote!(, instant: <#m as rtfm::Monotonic>::Instant)) + } else { + None + }; + + let body = spawn_body::codegen(spawner, &name, app, analysis, extra); + + items.push(quote!( + #cfg_sender + #(#cfgs)* + unsafe fn #spawn( + priority: &rtfm::export::Priority + #instant + #(,#args)* + ) -> Result<(), #ty> { + #body + } + )); + } + + let (let_instant, instant) = if app.uses_schedule(receiver) { + let m = extra.monotonic(); + + ( + Some(if spawner.is_idle() { + quote!(let instant = <#m as rtfm::Monotonic>::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)) + }; + + let spawner = spawner.ident(app); + debug_assert!(!methods.is_empty()); + items.push(quote!( + #cfg_sender + impl<#lt> #spawner::Spawn<#lt> { + #(#methods)* + } + )); + } + + items +} diff --git a/macros/src/codegen/spawn_body.rs b/macros/src/codegen/spawn_body.rs new file mode 100644 index 0000000000..83cb5c0a80 --- /dev/null +++ b/macros/src/codegen/spawn_body.rs @@ -0,0 +1,81 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; +use syn::Ident; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +pub fn codegen( + spawner: Context, + name: &Ident, + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> TokenStream2 { + let sender = spawner.core(app); + let spawnee = &app.software_tasks[name]; + let priority = spawnee.args.priority; + let receiver = spawnee.args.core; + + let write_instant = if app.uses_schedule(receiver) { + let instants = util::instants_ident(name, sender); + + Some(quote!( + #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant); + )) + } else { + None + }; + + let t = util::spawn_t_ident(receiver, priority, sender); + let fq = util::fq_ident(name, sender); + let rq = util::rq_ident(receiver, priority, sender); + let (dequeue, enqueue) = if spawner.is_init() { + ( + quote!(#fq.dequeue()), + quote!(#rq.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)) + }));), + ) + }; + + let device = extra.device; + let interrupt = &analysis.interrupts[&receiver][&priority]; + let pend = if sender != receiver { + quote!( + #device::xpend(#receiver, #device::Interrupt::#interrupt); + ) + } else { + quote!( + rtfm::pend(#device::Interrupt::#interrupt); + ) + }; + + let (_, tupled, _, _) = util::regroup_inputs(&spawnee.inputs); + let inputs = util::inputs_ident(name, sender); + quote!( + unsafe { + use rtfm::Mutex as _; + + let input = #tupled; + if let Some(index) = #dequeue { + #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input); + + #write_instant + + #enqueue + + #pend + + Ok(()) + } else { + Err(input) + } + } + ) +} diff --git a/macros/src/codegen/timer_queue.rs b/macros/src/codegen/timer_queue.rs new file mode 100644 index 0000000000..cb84577444 --- /dev/null +++ b/macros/src/codegen/timer_queue.rs @@ -0,0 +1,147 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::ast::App; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates timer queues and timer queue handlers +pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec { + let mut items = vec![]; + + for (&sender, timer_queue) in &analysis.timer_queues { + let cfg_sender = util::cfg_core(sender, app.args.cores); + let t = util::schedule_t_ident(sender); + + // Enumeration of `schedule`-able tasks + { + let variants = timer_queue + .tasks + .iter() + .map(|name| { + let cfgs = &app.software_tasks[name].cfgs; + + quote!( + #(#cfgs)* + #name + ) + }) + .collect::>(); + + let doc = format!("Tasks that can be scheduled from core #{}", sender); + items.push(quote!( + #cfg_sender + #[doc = #doc] + #[allow(non_camel_case_types)] + #[derive(Clone, Copy)] + enum #t { + #(#variants,)* + } + )); + } + + let tq = util::tq_ident(sender); + + // Static variable and resource proxy + { + let doc = format!("Core #{} timer queue", sender); + let m = extra.monotonic(); + let n = util::capacity_typenum(timer_queue.capacity, false); + let tq_ty = quote!(rtfm::export::TimerQueue<#m, #t, #n>); + + items.push(quote!( + #cfg_sender + #[doc = #doc] + static mut #tq: #tq_ty = rtfm::export::TimerQueue( + rtfm::export::BinaryHeap( + rtfm::export::iBinaryHeap::new() + ) + ); + + #cfg_sender + struct #tq<'a> { + priority: &'a rtfm::export::Priority, + } + )); + + items.push(util::impl_mutex( + extra, + &[], + cfg_sender.as_ref(), + false, + &tq, + tq_ty, + timer_queue.ceiling, + quote!(&mut #tq), + )); + } + + // Timer queue handler + { + let device = extra.device; + let arms = timer_queue + .tasks + .iter() + .map(|name| { + let task = &app.software_tasks[name]; + + let cfgs = &task.cfgs; + let priority = task.args.priority; + let receiver = task.args.core; + let rq = util::rq_ident(receiver, priority, sender); + let rqt = util::spawn_t_ident(receiver, priority, sender); + let interrupt = &analysis.interrupts[&receiver][&priority]; + + let pend = if sender != receiver { + quote!( + #device::xpend(#receiver, #device::Interrupt::#interrupt); + ) + } else { + quote!( + rtfm::pend(#device::Interrupt::#interrupt); + ) + }; + + quote!( + #(#cfgs)* + #t::#name => { + (#rq { priority: &rtfm::export::Priority::new(PRIORITY) }).lock(|rq| { + rq.split().0.enqueue_unchecked((#rqt::#name, index)) + }); + + #pend + } + ) + }) + .collect::>(); + + let priority = timer_queue.priority; + items.push(quote!( + #cfg_sender + #[no_mangle] + unsafe fn SysTick() { + use rtfm::Mutex as _; + + /// The priority of this handler + const PRIORITY: u8 = #priority; + + 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)* + } + } + }); + } + )); + } + } + + items +} diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs new file mode 100644 index 0000000000..203fcee86e --- /dev/null +++ b/macros/src/codegen/util.rs @@ -0,0 +1,253 @@ +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::quote; +use rtfm_syntax::{ast::App, Context, Core}; +use syn::{ArgCaptured, Attribute, Ident, IntSuffix, LitInt}; + +use crate::check::Extra; + +/// Turns `capacity` into an unsuffixed integer literal +pub fn capacity_literal(capacity: u8) -> LitInt { + LitInt::new(u64::from(capacity), IntSuffix::None, Span::call_site()) +} + +/// Turns `capacity` into a type-level (`typenum`) integer +pub fn capacity_typenum(capacity: u8, round_up_to_power_of_two: bool) -> TokenStream2 { + let capacity = if round_up_to_power_of_two { + capacity.checked_next_power_of_two().expect("UNREACHABLE") + } else { + capacity + }; + + let ident = Ident::new(&format!("U{}", capacity), Span::call_site()); + + quote!(rtfm::export::consts::#ident) +} + +/// Generates a `#[cfg(core = "0")]` attribute if we are in multi-core mode +pub fn cfg_core(core: Core, cores: u8) -> Option { + if cores == 1 { + None + } else { + let core = core.to_string(); + Some(quote!(#[cfg(core = #core)])) + } +} + +/// Identifier for the free queue +/// +/// There may be more than one free queue per task because we need one for each sender core so we +/// include the sender (e.g. `S0`) in the name +pub fn fq_ident(task: &Ident, sender: Core) -> Ident { + Ident::new( + &format!("{}_S{}_FQ", task.to_string(), sender), + Span::call_site(), + ) +} + +/// Generates a `Mutex` implementation +pub fn impl_mutex( + extra: &Extra, + cfgs: &[Attribute], + cfg_core: Option<&TokenStream2>, + resources_prefix: bool, + name: &Ident, + ty: TokenStream2, + ceiling: u8, + ptr: TokenStream2, +) -> TokenStream2 { + let (path, priority) = if resources_prefix { + (quote!(resources::#name), quote!(self.priority())) + } else { + (quote!(#name), quote!(self.priority)) + }; + + let device = extra.device; + quote!( + #(#cfgs)* + #cfg_core + impl<'a> rtfm::Mutex for #path<'a> { + type T = #ty; + + #[inline(always)] + fn lock(&mut self, f: impl FnOnce(&mut #ty) -> R) -> R { + /// Priority ceiling + const CEILING: u8 = #ceiling; + + unsafe { + rtfm::export::lock( + #ptr, + #priority, + CEILING, + #device::NVIC_PRIO_BITS, + f, + ) + } + } + } + ) +} + +/// Generates an identifier for a cross-initialization barrier +pub fn init_barrier(initializer: Core) -> Ident { + Ident::new(&format!("IB{}", initializer), Span::call_site()) +} + +/// Generates an identifier for the `INPUTS` buffer (`spawn` & `schedule` API) +pub fn inputs_ident(task: &Ident, sender: Core) -> Ident { + Ident::new(&format!("{}_S{}_INPUTS", task, sender), Span::call_site()) +} + +/// Generates an identifier for the `INSTANTS` buffer (`schedule` API) +pub fn instants_ident(task: &Ident, sender: Core) -> Ident { + Ident::new(&format!("{}_S{}_INSTANTS", task, sender), Span::call_site()) +} + +/// Generates a pre-reexport identifier for the "late resources" struct +pub fn late_resources_ident(init: &Ident) -> Ident { + Ident::new( + &format!("{}LateResources", init.to_string()), + Span::call_site(), + ) +} + +/// Generates a pre-reexport identifier for the "locals" struct +pub fn locals_ident(ctxt: Context, app: &App) -> Ident { + let mut s = match ctxt { + Context::Init(core) => app.inits[&core].name.to_string(), + Context::Idle(core) => app.idles[&core].name.to_string(), + Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), + }; + + s.push_str("Locals"); + + Ident::new(&s, Span::call_site()) +} + +/// Generates an identifier for a rendezvous barrier +pub fn rendezvous_ident(core: Core) -> Ident { + Ident::new(&format!("RV{}", core), Span::call_site()) +} + +// Regroups the inputs of a task +// +// `inputs` could be &[`input: Foo`] OR &[`mut x: i32`, `ref y: i64`] +pub fn regroup_inputs( + inputs: &[ArgCaptured], +) -> ( + // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`] + Vec, + // tupled e.g. `_0`, `(_0, _1)` + TokenStream2, + // untupled e.g. &[`_0`], &[`_0`, `_1`] + Vec, + // ty e.g. `Foo`, `(i32, i64)` + TokenStream2, +) { + if inputs.len() == 1 { + let ty = &inputs[0].ty; + + ( + vec![quote!(_0: #ty)], + quote!(_0), + vec![quote!(_0)], + quote!(#ty), + ) + } else { + let mut args = vec![]; + let mut pats = vec![]; + let mut tys = vec![]; + + for (i, input) in inputs.iter().enumerate() { + let i = Ident::new(&format!("_{}", i), Span::call_site()); + let ty = &input.ty; + + args.push(quote!(#i: #ty)); + + pats.push(quote!(#i)); + + tys.push(quote!(#ty)); + } + + let tupled = { + let pats = pats.clone(); + quote!((#(#pats,)*)) + }; + let ty = quote!((#(#tys,)*)); + (args, tupled, pats, ty) + } +} + +/// Generates a pre-reexport identifier for the "resources" struct +pub fn resources_ident(ctxt: Context, app: &App) -> Ident { + let mut s = match ctxt { + Context::Init(core) => app.inits[&core].name.to_string(), + Context::Idle(core) => app.idles[&core].name.to_string(), + Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), + }; + + s.push_str("Resources"); + + Ident::new(&s, Span::call_site()) +} + +/// Generates an identifier for a ready queue +/// +/// Each core may have several task dispatchers, one for each priority level. Each task dispatcher +/// in turn may use more than one ready queue because the queues are SPSC queues so one is needed +/// per sender core. +pub fn rq_ident(receiver: Core, priority: u8, sender: Core) -> Ident { + Ident::new( + &format!("R{}_P{}_S{}_RQ", receiver, priority, sender), + Span::call_site(), + ) +} + +/// Generates an identifier for a "schedule" function +/// +/// The methods of the `Schedule` structs invoke these functions. As one task may be `schedule`-ed +/// by different cores we need one "schedule" function per possible task-sender pair +pub fn schedule_ident(name: &Ident, sender: Core) -> Ident { + Ident::new( + &format!("schedule_{}_S{}", name.to_string(), sender), + Span::call_site(), + ) +} + +/// Generates an identifier for the `enum` of `schedule`-able tasks +pub fn schedule_t_ident(core: Core) -> Ident { + Ident::new(&format!("T{}", core), Span::call_site()) +} + +/// Generates an identifier for a cross-spawn barrier +pub fn spawn_barrier(receiver: Core) -> Ident { + Ident::new(&format!("SB{}", receiver), Span::call_site()) +} + +/// Generates an identifier for a "spawn" function +/// +/// The methods of the `Spawn` structs invoke these functions. As one task may be `spawn`-ed by +/// different cores we need one "spawn" function per possible task-sender pair +pub fn spawn_ident(name: &Ident, sender: Core) -> Ident { + Ident::new( + &format!("spawn_{}_S{}", name.to_string(), sender), + Span::call_site(), + ) +} + +/// Generates an identifier for the `enum` of `spawn`-able tasks +/// +/// This identifier needs the same structure as the `RQ` identifier because there's one ready queue +/// for each of these `T` enums +pub fn spawn_t_ident(receiver: Core, priority: u8, sender: Core) -> Ident { + Ident::new( + &format!("R{}_P{}_S{}_T", receiver, priority, sender), + Span::call_site(), + ) +} + +/// Generates an identifier for a timer queue +/// +/// At most there's one timer queue per core +pub fn tq_ident(core: Core) -> Ident { + Ident::new(&format!("TQ{}", core), Span::call_site()) +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 736289cb4b..6e1a7978b9 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -6,307 +6,41 @@ extern crate proc_macro; use proc_macro::TokenStream; use std::{fs, path::Path}; -use syn::parse_macro_input; +use rtfm_syntax::Settings; mod analyze; mod check; mod codegen; -mod syntax; +#[cfg(test)] +mod tests; -/// Attribute used to declare a RTFM application -/// -/// This attribute must be applied to a `const` item of type `()`. The `const` item is effectively -/// used as a `mod` item: its value must be a block that contains items commonly found in modules, -/// like functions and `static` variables. -/// -/// The `app` attribute has one mandatory argument: -/// -/// - `device = `. The path must point to a device crate generated using [`svd2rust`] -/// **v0.14.x**. -/// -/// [`svd2rust`]: https://crates.io/crates/svd2rust -/// -/// The items allowed in the block value of the `const` item are specified below: -/// -/// # 1. `static [mut]` variables -/// -/// These variables are used as *resources*. Resources can be owned by tasks or shared between them. -/// Tasks can get `&mut` (exclusives) references to `static mut` resources, but only `&` (shared) -/// references to `static` resources. Lower priority tasks will need a [`lock`] to get a `&mut` -/// reference to a `static mut` resource shared with higher priority tasks. -/// -/// [`lock`]: ../rtfm/trait.Mutex.html#method.lock -/// -/// `static mut` resources that are shared by tasks that run at *different* priorities need to -/// implement the [`Send`] trait. Similarly, `static` resources that are shared by tasks that run at -/// *different* priorities need to implement the [`Sync`] trait. -/// -/// [`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html -/// [`Sync`]: https://doc.rust-lang.org/core/marker/trait.Sync.html -/// -/// Resources can be initialized at runtime by assigning them `()` (the unit value) as their initial -/// value in their declaration. These "late" resources need to be initialized an the end of the -/// `init` function. -/// -/// The `app` attribute will inject a `resources` module in the root of the crate. This module -/// contains proxy `struct`s that implement the [`Mutex`] trait. The `struct` are named after the -/// `static mut` resources. For example, `static mut FOO: u32 = 0` will map to a `resources::FOO` -/// `struct` that implements the `Mutex` trait. -/// -/// [`Mutex`]: ../rtfm/trait.Mutex.html -/// -/// # 2. `fn` -/// -/// Functions must contain *one* of the following attributes: `init`, `idle`, `interrupt`, -/// `exception` or `task`. The attribute defines the role of the function in the application. -/// -/// ## a. `#[init]` -/// -/// This attribute indicates that the function is to be used as the *initialization function*. There -/// must be exactly one instance of the `init` attribute inside the `app` pseudo-module. The -/// signature of the `init` function must be `[unsafe] fn ()`. -/// -/// The `init` function runs after memory (RAM) is initialized and runs with interrupts disabled. -/// Interrupts are re-enabled after `init` returns. -/// -/// The `init` attribute accepts the following optional arguments: -/// -/// - `resources = [RESOURCE_A, RESOURCE_B, ..]`. This is the list of resources this function has -/// access to. -/// -/// - `schedule = [task_a, task_b, ..]`. This is the list of *software* tasks that this function can -/// schedule to run in the future. *IMPORTANT*: This argument is accepted only if the `timer-queue` -/// feature has been enabled. -/// -/// - `spawn = [task_a, task_b, ..]`. This is the list of *software* tasks that this function can -/// immediately spawn. -/// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: -/// -/// - `core: rtfm::Peripherals`. Exclusive access to core peripherals. See [`rtfm::Peripherals`] for -/// more details. -/// -/// [`rtfm::Peripherals`]: ../rtfm/struct.Peripherals.html -/// -/// - `device: ::Peripherals`. Exclusive access to device-specific peripherals. -/// `` is the path to the device crate declared in the top `app` attribute. -/// -/// - `start: rtfm::Instant`. The `start` time of the system: `Instant(0 /* cycles */)`. **NOTE**: -/// only present if the `timer-queue` feature is enabled. -/// -/// - `resources: _`. An opaque `struct` that contains all the resources assigned to this function. -/// The resource maybe appear by value (`impl Singleton`), by references (`&[mut]`) or by proxy -/// (`impl Mutex`). -/// -/// - `schedule: init::Schedule`. A `struct` that can be used to schedule *software* tasks. -/// **NOTE**: only present if the `timer-queue` feature is enabled. -/// -/// - `spawn: init::Spawn`. A `struct` that can be used to spawn *software* tasks. -/// -/// Other properties / constraints: -/// -/// - The `init` function can **not** be called from software. -/// -/// - The `static mut` variables declared at the beginning of this function will be transformed into -/// `&'static mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will -/// become `FOO: &'static mut u32`. -/// -/// - Assignments (e.g. `FOO = 0`) at the end of this function can be used to initialize *late* -/// resources. -/// -/// ## b. `#[idle]` -/// -/// This attribute indicates that the function is to be used as the *idle task*. There can be at -/// most once instance of the `idle` attribute inside the `app` pseudo-module. The signature of the -/// `idle` function must be `fn() -> !`. -/// -/// The `idle` task is a special task that always runs in the background. The `idle` task runs at -/// the lowest priority of `0`. If the `idle` task is not defined then the runtime sets the -/// [SLEEPONEXIT] bit after executing `init`. -/// -/// [SLEEPONEXIT]: https://developer.arm.com/products/architecture/cpu-architecture/m-profile/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit -/// -/// The `idle` attribute accepts the following optional arguments: -/// -/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init). -/// -/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init). -/// -/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init). -/// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: -/// -/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init). -/// -/// - `schedule: idle::Schedule`. Same meaning / function as [`init.schedule`](#a-init). -/// -/// - `spawn: idle::Spawn`. Same meaning / function as [`init.spawn`](#a-init). -/// -/// Other properties / constraints: -/// -/// - The `idle` function can **not** be called from software. -/// -/// - The `static mut` variables declared at the beginning of this function will be transformed into -/// `&'static mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will -/// become `FOO: &'static mut u32`. -/// -/// ## c. `#[exception]` -/// -/// This attribute indicates that the function is to be used as an *exception handler*, a type of -/// hardware task. The signature of `exception` handlers must be `[unsafe] fn()`. -/// -/// The name of the function must match one of the Cortex-M exceptions that has [configurable -/// priority][system-handler]. -/// -/// [system-handler]: ../cortex_m/peripheral/scb/enum.SystemHandler.html -/// -/// The `exception` attribute accepts the following optional arguments. -/// -/// - `priority = `. This is the static priority of the exception handler. The value must -/// be in the range `1..=(1 << ::NVIC_PRIO_BITS)` where `` is the path to -/// the device crate declared in the top `app` attribute. If this argument is omitted the priority -/// is assumed to be 1. -/// -/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init). -/// -/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init). -/// -/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init). -/// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: -/// -/// - `start: rtfm::Instant`. The time at which this handler started executing. **NOTE**: only -/// present if the `timer-queue` feature is enabled. -/// -/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init). -/// -/// - `schedule: ::Schedule`. Same meaning / function as [`init.schedule`](#a-init). -/// -/// - `spawn: ::Spawn`. Same meaning / function as [`init.spawn`](#a-init). -/// -/// Other properties / constraints: -/// -/// - `exception` handlers can **not** be called from software. -/// -/// - The `static mut` variables declared at the beginning of this function will be transformed into -/// `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will -/// become `FOO: &mut u32`. -/// -/// ## d. `#[interrupt]` -/// -/// This attribute indicates that the function is to be used as an *interrupt handler*, a type of -/// hardware task. The signature of `interrupt` handlers must be `[unsafe] fn()`. -/// -/// The name of the function must match one of the device specific interrupts. See your device crate -/// documentation (`Interrupt` enum) for more details. -/// -/// The `interrupt` attribute accepts the following optional arguments. -/// -/// - `priority = (..)`. Same meaning / function as [`#[exception].priority`](#b-exception). -/// -/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init). -/// -/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init). -/// -/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init). -/// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: -/// -/// - `start: rtfm::Instant`. Same meaning / function as [`exception.start`](#b-exception). -/// -/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init). -/// -/// - `schedule: ::Schedule`. Same meaning / function as [`init.schedule`](#a-init). -/// -/// - `spawn: ::Spawn`. Same meaning / function as [`init.spawn`](#a-init). -/// -/// Other properties / constraints: -/// -/// - `interrupt` handlers can **not** be called from software, but they can be [`pend`]-ed by the -/// software from any context. -/// -/// [`pend`]: ../rtfm/fn.pend.html -/// -/// - The `static mut` variables declared at the beginning of this function will be transformed into -/// `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will -/// become `FOO: &mut u32`. -/// -/// ## e. `#[task]` -/// -/// This attribute indicates that the function is to be used as a *software task*. The signature of -/// software `task`s must be `[unsafe] fn()`. -/// -/// The `task` attribute accepts the following optional arguments. -/// -/// - `capacity = `. The maximum number of instances of this task that can be queued onto -/// the task scheduler for execution. The value must be in the range `1..=255`. If the `capacity` -/// argument is omitted then the capacity will be inferred. -/// -/// - `priority = `. Same meaning / function as [`#[exception].priority`](#b-exception). -/// -/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init). -/// -/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init). -/// -/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init). -/// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: -/// -/// - `scheduled: rtfm::Instant`. The time at which this task was scheduled to run. **NOTE**: Only -/// present if `timer-queue` is enabled. -/// -/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init). -/// -/// - `schedule: ::Schedule`. Same meaning / function as [`init.schedule`](#a-init). -/// -/// - `spawn: ::Spawn`. Same meaning / function as [`init.spawn`](#a-init). -/// -/// Other properties / constraints: -/// -/// - Software `task`s can **not** be called from software, but they can be `spawn`-ed and -/// `schedule`-d by the software from any context. -/// -/// - The `static mut` variables declared at the beginning of this function will be transformed into -/// `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will -/// become `FOO: &mut u32`. -/// -/// # 3. `extern` block -/// -/// This `extern` block contains a list of interrupts which are *not* used by the application as -/// hardware tasks. These interrupts will be used to dispatch software tasks. Each interrupt will be -/// used to dispatch *multiple* software tasks *at the same priority level*. -/// -/// This `extern` block must only contain functions with signature `fn ()`. The names of these -/// functions must match the names of the target device interrupts. -/// -/// Importantly, attributes can be applied to the functions inside this block. These attributes will -/// be forwarded to the interrupt handlers generated by the `app` attribute. #[proc_macro_attribute] pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { - // Parse - let args = parse_macro_input!(args as syntax::AppArgs); - let input = parse_macro_input!(input as syntax::Input); - - let app = match syntax::App::parse(input.items, args) { + let (app, analysis) = match rtfm_syntax::parse( + args, + input, + Settings { + parse_cores: cfg!(feature = "heterogeneous"), + parse_exception: true, + parse_extern_interrupt: true, + parse_interrupt: true, + parse_schedule: true, + optimize_priorities: true, + ..Settings::default() + }, + ) { Err(e) => return e.to_compile_error().into(), - Ok(app) => app, + Ok(x) => x, }; - // Check the specification - if let Err(e) = check::app(&app) { - return e.to_compile_error().into(); - } + let extra = match check::app(&app, &analysis) { + Err(e) => return e.to_compile_error().into(), + Ok(x) => x, + }; - // Ceiling analysis - let analysis = analyze::app(&app); + let analysis = analyze::app(analysis, &app); - // Code generation - let ts = codegen::app(&input.ident, &app, &analysis); + let ts = codegen::app(&app, &analysis, &extra); // Try to write the expanded code to disk if Path::new("target").exists() { diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs deleted file mode 100644 index c6814d5f25..0000000000 --- a/macros/src/syntax.rs +++ /dev/null @@ -1,1382 +0,0 @@ -use std::{ - collections::{BTreeMap, BTreeSet}, - iter, u8, -}; - -use proc_macro2::Span; -use syn::{ - braced, bracketed, parenthesized, - parse::{self, Parse, ParseStream}, - punctuated::Punctuated, - spanned::Spanned, - token::Brace, - ArgCaptured, AttrStyle, Attribute, Expr, FnArg, ForeignItem, Ident, IntSuffix, Item, ItemFn, - ItemForeignMod, ItemStatic, LitInt, Pat, Path, PathArguments, ReturnType, Stmt, Token, Type, - TypeTuple, Visibility, -}; - -pub struct AppArgs { - pub device: Path, -} - -impl Parse for AppArgs { - fn parse(input: ParseStream<'_>) -> parse::Result { - let mut device = None; - loop { - if input.is_empty() { - break; - } - - // #ident = .. - let ident: Ident = input.parse()?; - let _eq_token: Token![=] = input.parse()?; - - let ident_s = ident.to_string(); - match &*ident_s { - "device" => { - if device.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - device = Some(input.parse()?); - } - _ => { - return Err(parse::Error::new( - ident.span(), - "expected `device`; other keys are not accepted", - )); - } - } - - if input.is_empty() { - break; - } - - // , - let _: Token![,] = input.parse()?; - } - - Ok(AppArgs { - device: device.ok_or(parse::Error::new( - Span::call_site(), - "`device` argument is required", - ))?, - }) - } -} - -pub struct Input { - _const_token: Token![const], - pub ident: Ident, - _colon_token: Token![:], - _ty: TypeTuple, - _eq_token: Token![=], - _brace_token: Brace, - pub items: Vec, - _semi_token: Token![;], -} - -impl Parse for Input { - fn parse(input: ParseStream<'_>) -> parse::Result { - fn parse_items(input: ParseStream<'_>) -> parse::Result> { - let mut items = vec![]; - - while !input.is_empty() { - items.push(input.parse()?); - } - - Ok(items) - } - - let content; - Ok(Input { - _const_token: input.parse()?, - ident: input.parse()?, - _colon_token: input.parse()?, - _ty: input.parse()?, - _eq_token: input.parse()?, - _brace_token: braced!(content in input), - items: content.call(parse_items)?, - _semi_token: input.parse()?, - }) - } -} - -pub struct App { - pub args: AppArgs, - pub idle: Option, - pub init: Init, - pub exceptions: Exceptions, - pub interrupts: Interrupts, - pub resources: Resources, - pub tasks: Tasks, - pub free_interrupts: FreeInterrupts, -} - -impl App { - pub fn parse(items: Vec, args: AppArgs) -> parse::Result { - let mut idle = None; - let mut init = None; - let mut exceptions = BTreeMap::new(); - let mut interrupts = BTreeMap::new(); - let mut resources = BTreeMap::new(); - let mut tasks = BTreeMap::new(); - let mut free_interrupts = None; - - for item in items { - match item { - Item::Fn(mut item) => { - if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "idle")) { - if idle.is_some() { - return Err(parse::Error::new( - item.span(), - "`#[idle]` function must appear at most once", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - idle = Some(Idle::check(args, item)?); - } else if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "init")) { - if init.is_some() { - return Err(parse::Error::new( - item.span(), - "`#[init]` function must appear exactly once", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - init = Some(Init::check(args, item)?); - } else if let Some(pos) = - item.attrs.iter().position(|attr| eq(attr, "exception")) - { - if exceptions.contains_key(&item.ident) - || interrupts.contains_key(&item.ident) - || tasks.contains_key(&item.ident) - { - return Err(parse::Error::new( - item.ident.span(), - "this task is defined multiple times", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - exceptions.insert(item.ident.clone(), Exception::check(args, item)?); - } else if let Some(pos) = - item.attrs.iter().position(|attr| eq(attr, "interrupt")) - { - if exceptions.contains_key(&item.ident) - || interrupts.contains_key(&item.ident) - || tasks.contains_key(&item.ident) - { - return Err(parse::Error::new( - item.ident.span(), - "this task is defined multiple times", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - interrupts.insert(item.ident.clone(), Interrupt::check(args, item)?); - } else if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "task")) { - if exceptions.contains_key(&item.ident) - || interrupts.contains_key(&item.ident) - || tasks.contains_key(&item.ident) - { - return Err(parse::Error::new( - item.ident.span(), - "this task is defined multiple times", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - tasks.insert(item.ident.clone(), Task::check(args, item)?); - } else { - return Err(parse::Error::new( - item.span(), - "this item must live outside the `#[app]` module", - )); - } - } - Item::Static(item) => { - if resources.contains_key(&item.ident) { - return Err(parse::Error::new( - item.ident.span(), - "this resource is listed twice", - )); - } - - resources.insert(item.ident.clone(), Resource::check(item)?); - } - Item::ForeignMod(item) => { - if free_interrupts.is_some() { - return Err(parse::Error::new( - item.abi.extern_token.span(), - "`extern` block can only appear at most once", - )); - } - - free_interrupts = Some(FreeInterrupt::parse(item)?); - } - _ => { - return Err(parse::Error::new( - item.span(), - "this item must live outside the `#[app]` module", - )); - } - } - } - - Ok(App { - args, - idle, - init: init.ok_or_else(|| { - parse::Error::new(Span::call_site(), "`#[init]` function is missing") - })?, - exceptions, - interrupts, - resources, - tasks, - free_interrupts: free_interrupts.unwrap_or_else(|| FreeInterrupts::new()), - }) - } - - /// Returns an iterator over all resource accesses. - /// - /// Each resource access include the priority it's accessed at (`u8`) and the name of the - /// resource (`Ident`). A resource may appear more than once in this iterator - pub fn resource_accesses(&self) -> impl Iterator { - self.idle - .as_ref() - .map(|idle| -> Box> { - Box::new(idle.args.resources.iter().map(|res| (0, res))) - }) - .unwrap_or_else(|| Box::new(iter::empty())) - .chain(self.exceptions.values().flat_map(|e| { - e.args - .resources - .iter() - .map(move |res| (e.args.priority, res)) - })) - .chain(self.interrupts.values().flat_map(|i| { - i.args - .resources - .iter() - .map(move |res| (i.args.priority, res)) - })) - .chain(self.tasks.values().flat_map(|t| { - t.args - .resources - .iter() - .map(move |res| (t.args.priority, res)) - })) - } - - /// Returns an iterator over all `spawn` calls - /// - /// Each spawn call includes the priority of the task from which it's issued and the name of the - /// task that's spawned. A task may appear more that once in this iterator. - /// - /// A priority of `None` means that this being called from `init` - pub fn spawn_calls(&self) -> impl Iterator, &Ident)> { - self.init - .args - .spawn - .iter() - .map(|s| (None, s)) - .chain( - self.idle - .as_ref() - .map(|idle| -> Box> { - Box::new(idle.args.spawn.iter().map(|s| (Some(0), s))) - }) - .unwrap_or_else(|| Box::new(iter::empty())), - ) - .chain( - self.exceptions - .values() - .flat_map(|e| e.args.spawn.iter().map(move |s| (Some(e.args.priority), s))), - ) - .chain( - self.interrupts - .values() - .flat_map(|i| i.args.spawn.iter().map(move |s| (Some(i.args.priority), s))), - ) - .chain( - self.tasks - .values() - .flat_map(|t| t.args.spawn.iter().map(move |s| (Some(t.args.priority), s))), - ) - } - - /// Returns an iterator over all `schedule` calls - /// - /// Each spawn call includes the priority of the task from which it's issued and the name of the - /// task that's spawned. A task may appear more that once in this iterator. - #[allow(dead_code)] - pub fn schedule_calls(&self) -> impl Iterator, &Ident)> { - self.init - .args - .schedule - .iter() - .map(|s| (None, s)) - .chain( - self.idle - .as_ref() - .map(|idle| -> Box> { - Box::new(idle.args.schedule.iter().map(|s| (Some(0), s))) - }) - .unwrap_or_else(|| Box::new(iter::empty())), - ) - .chain(self.exceptions.values().flat_map(|e| { - e.args - .schedule - .iter() - .map(move |s| (Some(e.args.priority), s)) - })) - .chain(self.interrupts.values().flat_map(|i| { - i.args - .schedule - .iter() - .map(move |s| (Some(i.args.priority), s)) - })) - .chain(self.tasks.values().flat_map(|t| { - t.args - .schedule - .iter() - .map(move |s| (Some(t.args.priority), s)) - })) - } - - #[allow(dead_code)] - pub fn schedule_callers(&self) -> impl Iterator { - self.idle - .as_ref() - .map(|idle| -> Box> { - Box::new(iter::once(( - Ident::new("idle", Span::call_site()), - &idle.args.schedule, - ))) - }) - .unwrap_or_else(|| Box::new(iter::empty())) - .chain(iter::once(( - Ident::new("init", Span::call_site()), - &self.init.args.schedule, - ))) - .chain( - self.exceptions - .iter() - .map(|(name, exception)| (name.clone(), &exception.args.schedule)), - ) - .chain( - self.interrupts - .iter() - .map(|(name, interrupt)| (name.clone(), &interrupt.args.schedule)), - ) - .chain( - self.tasks - .iter() - .map(|(name, task)| (name.clone(), &task.args.schedule)), - ) - } - - pub fn spawn_callers(&self) -> impl Iterator { - self.idle - .as_ref() - .map(|idle| -> Box> { - Box::new(iter::once(( - Ident::new("idle", Span::call_site()), - &idle.args.spawn, - ))) - }) - .unwrap_or_else(|| Box::new(iter::empty())) - .chain(iter::once(( - Ident::new("init", Span::call_site()), - &self.init.args.spawn, - ))) - .chain( - self.exceptions - .iter() - .map(|(name, exception)| (name.clone(), &exception.args.spawn)), - ) - .chain( - self.interrupts - .iter() - .map(|(name, interrupt)| (name.clone(), &interrupt.args.spawn)), - ) - .chain( - self.tasks - .iter() - .map(|(name, task)| (name.clone(), &task.args.spawn)), - ) - } -} - -pub type Idents = BTreeSet; - -pub type Exceptions = BTreeMap; - -pub type Interrupts = BTreeMap; - -pub type Resources = BTreeMap; - -pub type Statics = Vec; - -pub type Tasks = BTreeMap; - -pub type FreeInterrupts = BTreeMap; - -pub struct Idle { - pub args: IdleArgs, - pub attrs: Vec, - pub context: Pat, - pub statics: BTreeMap, - pub stmts: Vec, -} - -pub type IdleArgs = InitArgs; - -impl Idle { - fn check(args: IdleArgs, item: ItemFn) -> parse::Result { - let valid_signature = - check_signature(&item) && item.decl.inputs.len() == 1 && is_bottom(&item.decl.output); - - let span = item.span(); - - 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, - }); - } - } - - Err(parse::Error::new( - span, - "`idle` must have type signature `fn(idle::Context) -> !`", - )) - } -} - -pub struct InitArgs { - pub resources: Idents, - pub schedule: Idents, - pub spawn: Idents, -} - -impl Default for InitArgs { - fn default() -> Self { - InitArgs { - resources: Idents::new(), - schedule: Idents::new(), - spawn: Idents::new(), - } - } -} - -impl Parse for InitArgs { - fn parse(input: ParseStream<'_>) -> parse::Result { - if input.is_empty() { - return Ok(InitArgs::default()); - } - - let mut resources = None; - let mut schedule = None; - let mut spawn = None; - - let content; - parenthesized!(content in input); - loop { - if content.is_empty() { - break; - } - - // #ident = .. - let ident: Ident = content.parse()?; - let _: Token![=] = content.parse()?; - - let ident_s = ident.to_string(); - match &*ident_s { - "schedule" if cfg!(not(feature = "timer-queue")) => { - return Err(parse::Error::new( - ident.span(), - "The `schedule` API requires that the `timer-queue` feature is \ - enabled in the `cortex-m-rtfm` crate", - )); - } - "resources" | "schedule" | "spawn" => {} // OK - _ => { - return Err(parse::Error::new( - ident.span(), - "expected one of: resources, schedule or spawn", - )); - } - } - - // .. [#(#idents)*] - let inner; - bracketed!(inner in content); - let mut idents = Idents::new(); - for ident in inner.call(Punctuated::<_, Token![,]>::parse_terminated)? { - if idents.contains(&ident) { - return Err(parse::Error::new( - ident.span(), - "element appears more than once in list", - )); - } - - idents.insert(ident); - } - - let ident_s = ident.to_string(); - match &*ident_s { - "resources" => { - if resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - resources = Some(idents); - } - "schedule" => { - if schedule.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - schedule = Some(idents); - } - "spawn" => { - if spawn.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - spawn = Some(idents); - } - _ => unreachable!(), - } - - if content.is_empty() { - break; - } - - // , - let _: Token![,] = content.parse()?; - } - - Ok(InitArgs { - resources: resources.unwrap_or(Idents::new()), - schedule: schedule.unwrap_or(Idents::new()), - spawn: spawn.unwrap_or(Idents::new()), - }) - } -} - -pub struct Init { - pub args: InitArgs, - pub attrs: Vec, - pub statics: BTreeMap, - pub context: Pat, - pub stmts: Vec, - pub returns_late_resources: bool, - pub span: Span, -} - -impl Init { - fn check(args: InitArgs, item: ItemFn) -> parse::Result { - 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, - ReturnType::Type(_, ty) => { - match &**ty { - Type::Tuple(t) => { - if t.elems.is_empty() { - // -> () - true - } else { - valid_signature = false; - - DONT_CARE - } - } - - Type::Path(_) => { - if is_path(ty, &["init", "LateResources"]) { - // -> init::LateResources - true - } else { - valid_signature = false; - - DONT_CARE - } - } - - _ => { - valid_signature = false; - - DONT_CARE - } - } - } - }; - - let span = item.span(); - - 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, - }); - } - } - - Err(parse::Error::new( - span, - "`init` must have type signature `fn(init::Context) [-> init::LateResources]`", - )) - } -} - -/// Union of `TaskArgs`, `ExceptionArgs` and `InterruptArgs` -pub struct Args { - pub binds: Option, - pub capacity: Option, - pub priority: u8, - pub resources: Idents, - pub schedule: Idents, - pub spawn: Idents, -} - -impl Default for Args { - fn default() -> Self { - Args { - binds: None, - capacity: None, - priority: 1, - resources: Idents::new(), - schedule: Idents::new(), - spawn: Idents::new(), - } - } -} - -pub struct Exception { - pub args: ExceptionArgs, - pub attrs: Vec, - pub statics: BTreeMap, - pub context: Pat, - pub stmts: Vec, -} - -pub struct ExceptionArgs { - binds: Option, - pub priority: u8, - pub resources: Idents, - pub schedule: Idents, - pub spawn: Idents, -} - -impl ExceptionArgs { - /// Returns the name of the exception / interrupt this handler binds to - pub fn binds<'a>(&'a self, handler: &'a Ident) -> &'a Ident { - self.binds.as_ref().unwrap_or(handler) - } -} - -impl Parse for ExceptionArgs { - fn parse(input: ParseStream<'_>) -> parse::Result { - parse_args(input, /* binds */ true, /* capacity */ false).map( - |Args { - binds, - priority, - resources, - schedule, - spawn, - .. - }| { - ExceptionArgs { - binds, - priority, - resources, - schedule, - spawn, - } - }, - ) - } -} - -impl Exception { - fn check(args: ExceptionArgs, item: ItemFn) -> parse::Result { - let valid_signature = - check_signature(&item) && item.decl.inputs.len() == 1 && is_unit(&item.decl.output); - - let span = item.span(); - - 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, - "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, - }); - } - } - - 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 statics: BTreeMap, - pub context: Pat, - pub stmts: Vec, -} - -pub type InterruptArgs = ExceptionArgs; - -impl Interrupt { - fn check(args: InterruptArgs, item: ItemFn) -> parse::Result { - let valid_signature = - check_signature(&item) && item.decl.inputs.len() == 1 && is_unit(&item.decl.output); - - let span = item.span(); - - 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`", - )); - } - _ => {} - } - - let (statics, stmts) = extract_statics(item.block.stmts); - - return Ok(Interrupt { - args, - attrs: item.attrs, - statics: Static::parse(statics)?, - context, - stmts, - }); - } - } - - Err(parse::Error::new( - span, - format!( - "this `interrupt` handler must have type signature `fn({}::Context)`", - name - ), - )) - } -} - -pub struct Resource { - pub cfgs: Vec, - pub attrs: Vec, - pub mutability: Option, - pub ty: Box, - pub expr: Option>, -} - -impl Resource { - fn check(item: ItemStatic) -> parse::Result { - if item.vis != Visibility::Inherited { - return Err(parse::Error::new( - item.span(), - "resources must have inherited / private visibility", - )); - } - - let uninitialized = match *item.expr { - Expr::Tuple(ref tuple) => tuple.elems.is_empty(), - _ => false, - }; - - let (cfgs, attrs) = extract_cfgs(item.attrs); - - Ok(Resource { - cfgs, - attrs, - mutability: item.mutability, - ty: item.ty, - expr: if uninitialized { None } else { Some(item.expr) }, - }) - } -} - -pub struct TaskArgs { - pub capacity: Option, - pub priority: u8, - pub resources: Idents, - pub spawn: Idents, - pub schedule: Idents, -} - -impl Parse for TaskArgs { - fn parse(input: ParseStream<'_>) -> parse::Result { - parse_args(input, /* binds */ false, /* capacity */ true).map( - |Args { - capacity, - priority, - resources, - schedule, - spawn, - .. - }| { - TaskArgs { - capacity, - priority, - resources, - schedule, - spawn, - } - }, - ) - } -} - -// Parser shared by ExceptionArgs, InterruptArgs and TaskArgs -fn parse_args( - input: ParseStream<'_>, - accepts_binds: bool, - accepts_capacity: bool, -) -> parse::Result { - if input.is_empty() { - return Ok(Args::default()); - } - - let mut binds = None; - let mut capacity = None; - let mut priority = None; - let mut resources = None; - let mut schedule = None; - let mut spawn = None; - - let content; - parenthesized!(content in input); - loop { - if content.is_empty() { - break; - } - - // #ident = .. - let ident: Ident = content.parse()?; - let _: Token![=] = content.parse()?; - - let ident_s = ident.to_string(); - match &*ident_s { - "binds" if accepts_binds => { - if binds.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - // #ident - let ident = content.parse()?; - - binds = Some(ident); - } - "capacity" if accepts_capacity => { - if capacity.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - // #lit - let lit: LitInt = content.parse()?; - - if lit.suffix() != IntSuffix::None { - return Err(parse::Error::new( - lit.span(), - "this literal must be unsuffixed", - )); - } - - let value = lit.value(); - if value > u64::from(u8::MAX) || value == 0 { - return Err(parse::Error::new( - lit.span(), - "this literal must be in the range 1...255", - )); - } - - capacity = Some(value as u8); - } - "priority" => { - if priority.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - // #lit - let lit: LitInt = content.parse()?; - - if lit.suffix() != IntSuffix::None { - return Err(parse::Error::new( - lit.span(), - "this literal must be unsuffixed", - )); - } - - let value = lit.value(); - if value > u64::from(u8::MAX) || value == 0 { - return Err(parse::Error::new( - lit.span(), - "this literal must be in the range 1...255", - )); - } - - priority = Some(value as u8); - } - "schedule" if cfg!(not(feature = "timer-queue")) => { - return Err(parse::Error::new( - ident.span(), - "The `schedule` API requires that the `timer-queue` feature is \ - enabled in the `cortex-m-rtfm` crate", - )); - } - "resources" | "schedule" | "spawn" => { - // .. [#(#idents)*] - let inner; - bracketed!(inner in content); - let mut idents = Idents::new(); - for ident in inner.call(Punctuated::<_, Token![,]>::parse_terminated)? { - if idents.contains(&ident) { - return Err(parse::Error::new( - ident.span(), - "element appears more than once in list", - )); - } - - idents.insert(ident); - } - - match &*ident_s { - "resources" => { - if resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - resources = Some(idents); - } - "schedule" => { - if schedule.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - schedule = Some(idents); - } - "spawn" => { - if spawn.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - spawn = Some(idents); - } - _ => unreachable!(), - } - } - _ => { - return Err(parse::Error::new( - ident.span(), - format!( - "expected one of: {}{}priority, resources, schedule or spawn", - if accepts_binds { "binds, " } else { "" }, - if accepts_capacity { "capacity, " } else { "" }, - ), - )); - } - } - - if content.is_empty() { - break; - } - - // , - let _: Token![,] = content.parse()?; - } - - Ok(Args { - binds, - capacity, - priority: priority.unwrap_or(1), - resources: resources.unwrap_or(Idents::new()), - schedule: schedule.unwrap_or(Idents::new()), - spawn: spawn.unwrap_or(Idents::new()), - }) -} - -pub struct Static { - /// `#[cfg]` attributes - pub cfgs: Vec, - /// Attributes that are not `#[cfg]` - pub attrs: Vec, - pub ty: Box, - pub expr: Box, -} - -impl Static { - fn parse(items: Vec) -> parse::Result> { - let mut statics = BTreeMap::new(); - - for item in items { - if statics.contains_key(&item.ident) { - return Err(parse::Error::new( - item.ident.span(), - "this `static` is listed twice", - )); - } - - let (cfgs, attrs) = extract_cfgs(item.attrs); - - statics.insert( - item.ident, - Static { - cfgs, - attrs, - ty: item.ty, - expr: item.expr, - }, - ); - } - - Ok(statics) - } -} - -pub struct Task { - pub args: TaskArgs, - pub cfgs: Vec, - pub attrs: Vec, - 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 = - check_signature(&item) && !item.decl.inputs.is_empty() && is_unit(&item.decl.output); - - let span = item.span(); - - 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 inputs = rest.map_err(|arg| { - parse::Error::new( - arg.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, - }); - } - } - - Err(parse::Error::new( - span, - &format!( - "this `task` handler must have type signature `fn({}::Context, ..)`", - name - ), - )) - } -} - -pub struct FreeInterrupt { - pub attrs: Vec, -} - -impl FreeInterrupt { - fn parse(mod_: ItemForeignMod) -> parse::Result { - let mut free_interrupts = FreeInterrupts::new(); - - for item in mod_.items { - if let ForeignItem::Fn(f) = item { - let valid_signature = f.vis == Visibility::Inherited - && f.decl.generics.params.is_empty() - && f.decl.generics.where_clause.is_none() - && f.decl.inputs.is_empty() - && f.decl.variadic.is_none() - && is_unit(&f.decl.output); - - if !valid_signature { - return Err(parse::Error::new( - f.span(), - "free interrupts must have type signature `fn()`", - )); - } - - if free_interrupts.contains_key(&f.ident) { - return Err(parse::Error::new( - f.ident.span(), - "this interrupt appears twice", - )); - } - - free_interrupts.insert(f.ident, FreeInterrupt { attrs: f.attrs }); - } else { - return Err(parse::Error::new( - mod_.abi.extern_token.span(), - "`extern` block should only contains functions", - )); - } - } - - Ok(free_interrupts) - } -} - -fn eq(attr: &Attribute, name: &str) -> bool { - attr.style == AttrStyle::Outer && attr.path.segments.len() == 1 && { - let pair = attr.path.segments.first().unwrap(); - let segment = pair.value(); - segment.arguments == PathArguments::None && segment.ident.to_string() == name - } -} - -fn extract_cfgs(attrs: Vec) -> (Vec, Vec) { - let mut cfgs = vec![]; - let mut not_cfgs = vec![]; - - for attr in attrs { - if eq(&attr, "cfg") { - cfgs.push(attr); - } else { - not_cfgs.push(attr); - } - } - - (cfgs, not_cfgs) -} - -/// Extracts `static mut` vars from the beginning of the given statements -fn extract_statics(stmts: Vec) -> (Statics, Vec) { - let mut istmts = stmts.into_iter(); - - let mut statics = Statics::new(); - let mut stmts = vec![]; - while let Some(stmt) = istmts.next() { - match stmt { - Stmt::Item(Item::Static(var)) => { - if var.mutability.is_some() { - statics.push(var); - } else { - stmts.push(Stmt::Item(Item::Static(var))); - break; - } - } - _ => { - stmts.push(stmt); - break; - } - } - } - - stmts.extend(istmts); - - (statics, stmts) -} - -// 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(); - - 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::, _>>(); - - Some((first.pat, rest)) - } else { - None - } - } - - _ => None, - } -} - -/// 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() -} - -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 { - if let ReturnType::Type(_, ty) = ty { - if let Type::Never(_) = **ty { - true - } else { - false - } - } else { - false - } -} - -fn is_unit(ty: &ReturnType) -> bool { - if let ReturnType::Type(_, ty) = ty { - if let Type::Tuple(ref tuple) = **ty { - tuple.elems.is_empty() - } else { - false - } - } else { - true - } -} diff --git a/macros/src/tests.rs b/macros/src/tests.rs new file mode 100644 index 0000000000..470c9058f2 --- /dev/null +++ b/macros/src/tests.rs @@ -0,0 +1,5 @@ +// NOTE these tests are specific to the Cortex-M port; `rtfm-syntax` has a more extensive test suite +// that tests functionality common to all the RTFM ports + +mod multi; +mod single; diff --git a/macros/src/tests/multi.rs b/macros/src/tests/multi.rs new file mode 100644 index 0000000000..37fef53f21 --- /dev/null +++ b/macros/src/tests/multi.rs @@ -0,0 +1,59 @@ +use quote::quote; +use rtfm_syntax::Settings; + +#[test] +fn analyze() { + let (app, analysis) = rtfm_syntax::parse2( + quote!(device = pac, cores = 2), + quote!( + const APP: () = { + #[task(core = 0, priority = 1)] + fn a(_: a::Context) {} + + #[task(core = 0, priority = 2)] + fn b(_: b::Context) {} + + #[task(core = 1, priority = 1)] + fn c(_: c::Context) {} + + #[task(core = 1, priority = 2)] + fn d(_: d::Context) {} + + // first interrupt is assigned to the highest priority dispatcher + extern "C" { + #[core = 0] + fn B(); + + #[core = 0] + fn A(); + + #[core = 1] + fn A(); + + #[core = 1] + fn C(); + } + }; + ), + Settings { + parse_cores: true, + parse_extern_interrupt: true, + ..Settings::default() + }, + ) + .unwrap(); + + let analysis = crate::analyze::app(analysis, &app); + + // first core + let interrupts0 = &analysis.interrupts[&0]; + assert_eq!(interrupts0.len(), 2); + assert_eq!(interrupts0[&2].to_string(), "B"); + assert_eq!(interrupts0[&1].to_string(), "A"); + + // second core + let interrupts1 = &analysis.interrupts[&1]; + assert_eq!(interrupts1.len(), 2); + assert_eq!(interrupts1[&2].to_string(), "A"); + assert_eq!(interrupts1[&1].to_string(), "C"); +} diff --git a/macros/src/tests/single.rs b/macros/src/tests/single.rs new file mode 100644 index 0000000000..fb2d430f39 --- /dev/null +++ b/macros/src/tests/single.rs @@ -0,0 +1,35 @@ +use quote::quote; +use rtfm_syntax::Settings; + +#[test] +fn analyze() { + let (app, analysis) = rtfm_syntax::parse2( + quote!(device = pac), + quote!( + const APP: () = { + #[task(priority = 1)] + fn a(_: a::Context) {} + + #[task(priority = 2)] + fn b(_: b::Context) {} + + // first interrupt is assigned to the highest priority dispatcher + extern "C" { + fn B(); + fn A(); + } + }; + ), + Settings { + parse_extern_interrupt: true, + ..Settings::default() + }, + ) + .unwrap(); + + let analysis = crate::analyze::app(analysis, &app); + let interrupts = &analysis.interrupts[&0]; + assert_eq!(interrupts.len(), 2); + assert_eq!(interrupts[&2].to_string(), "B"); + assert_eq!(interrupts[&1].to_string(), "A"); +} diff --git a/mc/Cargo.toml b/mc/Cargo.toml new file mode 100644 index 0000000000..7c75335db7 --- /dev/null +++ b/mc/Cargo.toml @@ -0,0 +1,18 @@ +[package] +authors = ["Jorge Aparicio "] +edition = "2018" +name = "mc" +# this crate is only used for testing +publish = false +version = "0.0.0-alpha.0" + +[dependencies] +cortex-m = "0.6.0" + +[dependencies.cortex-m-rtfm] +path = ".." +features = ["heterogeneous"] + +[dev-dependencies] +panic-halt = "0.2.0" +microamp = "0.1.0-alpha.1" diff --git a/mc/README.md b/mc/README.md new file mode 100644 index 0000000000..e1335bbfbd --- /dev/null +++ b/mc/README.md @@ -0,0 +1 @@ +This directory contains multi-core compile pass tests. diff --git a/mc/examples/smallest.rs b/mc/examples/smallest.rs new file mode 100644 index 0000000000..792935a811 --- /dev/null +++ b/mc/examples/smallest.rs @@ -0,0 +1,7 @@ +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = mc)] +const APP: () = {}; diff --git a/mc/examples/x-init-2.rs b/mc/examples/x-init-2.rs new file mode 100644 index 0000000000..ff48b110bc --- /dev/null +++ b/mc/examples/x-init-2.rs @@ -0,0 +1,39 @@ +//! [compile-pass] Cross initialization of late resources + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = mc)] +const APP: () = { + extern "C" { + // owned by core #1 but initialized by core #0 + static mut X: u32; + + // owned by core #0 but initialized by core #1 + static mut Y: u32; + } + + #[init(core = 0, late = [X])] + fn a(_: a::Context) -> a::LateResources { + a::LateResources { X: 0 } + } + + #[idle(core = 0, resources = [Y])] + fn b(_: b::Context) -> ! { + loop {} + } + + #[init(core = 1)] + fn c(_: c::Context) -> c::LateResources { + c::LateResources { Y: 0 } + } + + #[idle(core = 1, resources = [X])] + fn d(_: d::Context) -> ! { + loop {} + } +}; diff --git a/mc/examples/x-init.rs b/mc/examples/x-init.rs new file mode 100644 index 0000000000..3f26c5c92f --- /dev/null +++ b/mc/examples/x-init.rs @@ -0,0 +1,26 @@ +//! [compile-pass] Split initialization of late resources + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = mc)] +const APP: () = { + extern "C" { + static mut X: u32; + static mut Y: u32; + } + + #[init(core = 0, late = [X])] + fn a(_: a::Context) -> a::LateResources { + a::LateResources { X: 0 } + } + + #[init(core = 1)] + fn b(_: b::Context) -> b::LateResources { + b::LateResources { Y: 0 } + } +}; diff --git a/mc/examples/x-schedule.rs b/mc/examples/x-schedule.rs new file mode 100644 index 0000000000..76e70acf57 --- /dev/null +++ b/mc/examples/x-schedule.rs @@ -0,0 +1,36 @@ +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = mc, monotonic = mc::MT)] +const APP: () = { + #[init(core = 0, spawn = [ping])] + fn init(c: init::Context) { + c.spawn.ping().ok(); + } + + #[task(core = 0, schedule = [ping])] + fn pong(c: pong::Context) { + c.schedule.ping(c.scheduled + 1_000_000).ok(); + } + + #[task(core = 1, schedule = [pong])] + fn ping(c: ping::Context) { + c.schedule.pong(c.scheduled + 1_000_000).ok(); + } + + extern "C" { + #[core = 0] + fn I0(); + + #[core = 0] + fn I1(); + + #[core = 1] + fn I0(); + + #[core = 1] + fn I1(); + } +}; diff --git a/mc/examples/x-spawn.rs b/mc/examples/x-spawn.rs new file mode 100644 index 0000000000..749918fdf9 --- /dev/null +++ b/mc/examples/x-spawn.rs @@ -0,0 +1,20 @@ +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = mc)] +const APP: () = { + #[init(core = 0, spawn = [foo])] + fn init(c: init::Context) { + c.spawn.foo().ok(); + } + + #[task(core = 1)] + fn foo(_: foo::Context) {} + + extern "C" { + #[core = 1] + fn I0(); + } +}; diff --git a/mc/src/lib.rs b/mc/src/lib.rs new file mode 100644 index 0000000000..d86c0e8e7c --- /dev/null +++ b/mc/src/lib.rs @@ -0,0 +1,99 @@ +//! Fake multi-core PAC + +#![no_std] + +use core::{ + cmp::Ordering, + ops::{Add, Sub}, +}; + +use cortex_m::interrupt::Nr; +use rtfm::Monotonic; + +// Fake priority bits +pub const NVIC_PRIO_BITS: u8 = 3; + +pub struct CrossPend; + +pub fn xpend(_core: u8, _interrupt: impl Nr) {} + +/// Fake monotonic timer +pub struct MT; + +unsafe impl Monotonic for MT { + type Instant = Instant; + + fn ratio() -> u32 { + 1 + } + + unsafe fn reset() { + (0xE0001004 as *mut u32).write_volatile(0) + } + + fn now() -> Instant { + unsafe { Instant((0xE0001004 as *const u32).read_volatile() as i32) } + } + + fn zero() -> Instant { + Instant(0) + } +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Instant(i32); + +impl Add for Instant { + type Output = Instant; + + fn add(self, rhs: u32) -> Self { + Instant(self.0.wrapping_add(rhs as i32)) + } +} + +impl Sub for Instant { + type Output = u32; + + fn sub(self, rhs: Self) -> u32 { + self.0.checked_sub(rhs.0).unwrap() as u32 + } +} + +impl Ord for Instant { + fn cmp(&self, rhs: &Self) -> Ordering { + self.0.wrapping_sub(rhs.0).cmp(&0) + } +} + +impl PartialOrd for Instant { + fn partial_cmp(&self, rhs: &Self) -> Option { + Some(self.cmp(rhs)) + } +} + +// Fake interrupts +pub enum Interrupt { + I0, + I1, + I2, + I3, + I4, + I5, + I6, + I7, +} + +unsafe impl Nr for Interrupt { + fn nr(&self) -> u8 { + match self { + Interrupt::I0 => 0, + Interrupt::I1 => 1, + Interrupt::I2 => 2, + Interrupt::I3 => 3, + Interrupt::I4 => 4, + Interrupt::I5 => 5, + Interrupt::I6 => 6, + Interrupt::I7 => 7, + } + } +} diff --git a/src/cyccnt.rs b/src/cyccnt.rs new file mode 100644 index 0000000000..a2b216c179 --- /dev/null +++ b/src/cyccnt.rs @@ -0,0 +1,205 @@ +//! Data Watchpoint Trace (DWT) unit's CYCle CouNTer + +use core::{ + cmp::Ordering, + convert::{Infallible, TryInto}, + fmt, + marker::PhantomData, + ops, +}; + +use cortex_m::peripheral::DWT; + +/// A measurement of the CYCCNT. Opaque and useful only with `Duration` +/// +/// This data type is only available on ARMv7-M +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Instant { + inner: i32, + _not_send_or_sync: PhantomData<*mut ()>, +} + +unsafe impl Sync for Instant {} + +#[cfg(not(feature = "heterogeneous"))] +unsafe impl Send for Instant {} + +impl Instant { + /// Returns an instant corresponding to "now" + pub fn now() -> Self { + Instant { + inner: DWT::get_cycle_count() as i32, + _not_send_or_sync: PhantomData, + } + } + + /// Returns the amount of time elapsed since this instant was created. + pub fn elapsed(&self) -> Duration { + Instant::now() - *self + } + + /// Returns the amount of time elapsed from another instant to this one. + pub fn duration_since(&self, earlier: Instant) -> Duration { + let diff = self.inner - earlier.inner; + assert!(diff >= 0, "second instant is later than self"); + Duration { inner: diff as u32 } + } +} + +impl fmt::Debug for Instant { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Instant") + .field(&(self.inner as u32)) + .finish() + } +} + +impl ops::AddAssign for Instant { + fn add_assign(&mut self, dur: Duration) { + debug_assert!(dur.inner < (1 << 31)); + self.inner = self.inner.wrapping_add(dur.inner as i32); + } +} + +impl ops::Add for Instant { + type Output = Self; + + fn add(mut self, dur: Duration) -> Self { + self += dur; + self + } +} + +impl ops::SubAssign for Instant { + fn sub_assign(&mut self, dur: Duration) { + // XXX should this be a non-debug assertion? + debug_assert!(dur.inner < (1 << 31)); + self.inner = self.inner.wrapping_sub(dur.inner as i32); + } +} + +impl ops::Sub for Instant { + type Output = Self; + + fn sub(mut self, dur: Duration) -> Self { + self -= dur; + self + } +} + +impl ops::Sub for Instant { + type Output = Duration; + + fn sub(self, other: Instant) -> Duration { + self.duration_since(other) + } +} + +impl Ord for Instant { + fn cmp(&self, rhs: &Self) -> Ordering { + self.inner.wrapping_sub(rhs.inner).cmp(&0) + } +} + +impl PartialOrd for Instant { + fn partial_cmp(&self, rhs: &Self) -> Option { + Some(self.cmp(rhs)) + } +} + +/// A `Duration` type to represent a span of time. +/// +/// This data type is only available on ARMv7-M +#[derive(Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)] +pub struct Duration { + inner: u32, +} + +impl Duration { + /// Returns the total number of clock cycles contained by this `Duration` + pub fn as_cycles(&self) -> u32 { + self.inner + } +} + +impl TryInto for Duration { + type Error = Infallible; + + fn try_into(self) -> Result { + Ok(self.as_cycles()) + } +} + +impl ops::AddAssign for Duration { + fn add_assign(&mut self, dur: Duration) { + self.inner += dur.inner; + } +} + +impl ops::Add for Duration { + type Output = Self; + + fn add(self, other: Self) -> Self { + Duration { + inner: self.inner + other.inner, + } + } +} + +impl ops::SubAssign for Duration { + fn sub_assign(&mut self, rhs: Duration) { + self.inner -= rhs.inner; + } +} + +impl ops::Sub for Duration { + type Output = Self; + + fn sub(self, rhs: Self) -> Self { + Duration { + inner: self.inner - rhs.inner, + } + } +} + +/// Adds the `cycles` method to the `u32` type +/// +/// This trait is only available on ARMv7-M +pub trait U32Ext { + /// Converts the `u32` value into clock cycles + fn cycles(self) -> Duration; +} + +impl U32Ext for u32 { + fn cycles(self) -> Duration { + Duration { inner: self } + } +} + +/// Implementation of the `Monotonic` trait based on CYCle CouNTer +#[cfg(not(feature = "heterogeneous"))] +pub struct CYCCNT; + +#[cfg(not(feature = "heterogeneous"))] +unsafe impl crate::Monotonic for CYCCNT { + type Instant = Instant; + + fn ratio() -> u32 { + 1 + } + + unsafe fn reset() { + (0xE0001004 as *mut u32).write_volatile(0) + } + + fn now() -> Instant { + Instant::now() + } + + fn zero() -> Instant { + Instant { + inner: 0, + _not_send_or_sync: PhantomData, + } + } +} diff --git a/src/export.rs b/src/export.rs index afed9091a5..7646e3c504 100644 --- a/src/export.rs +++ b/src/export.rs @@ -1,21 +1,27 @@ -//! IMPLEMENTATION DETAILS. DO NOT USE ANYTHING IN THIS MODULE - -use core::{cell::Cell, u8}; - -#[cfg(armv7m)] -use cortex_m::register::basepri; -pub use cortex_m::{ - asm::wfi, interrupt, peripheral::scb::SystemHandler, peripheral::syst::SystClkSource, - peripheral::Peripherals, +use core::{ + cell::Cell, + sync::atomic::{AtomicBool, Ordering}, }; -use heapless::spsc::SingleCore; -pub use heapless::{consts, i, spsc::Queue}; -#[cfg(feature = "timer-queue")] pub use crate::tq::{NotReady, TimerQueue}; +#[cfg(armv7m)] +pub use cortex_m::register::basepri; +pub use cortex_m::{ + asm::wfi, + interrupt, + peripheral::{scb::SystemHandler, syst::SystClkSource, DWT}, + Peripherals, +}; +use heapless::spsc::{MultiCore, SingleCore}; +pub use heapless::{consts, i::Queue as iQueue, spsc::Queue}; +pub use heapless::{i::BinaryHeap as iBinaryHeap, BinaryHeap}; +#[cfg(feature = "heterogeneous")] +pub use microamp::shared; -pub type FreeQueue = Queue; -pub type ReadyQueue = Queue<(T, u8), N, u8, SingleCore>; +pub type MCFQ = Queue; +pub type MCRQ = Queue<(T, u8), N, u8, MultiCore>; +pub type SCFQ = Queue; +pub type SCRQ = Queue<(T, u8), N, u8, SingleCore>; #[cfg(armv7m)] #[inline(always)] @@ -43,6 +49,26 @@ where f(); } +pub struct Barrier { + inner: AtomicBool, +} + +impl Barrier { + pub const fn new() -> Self { + Barrier { + inner: AtomicBool::new(false), + } + } + + pub fn release(&self) { + self.inner.store(true, Ordering::Release) + } + + pub fn wait(&self) { + while !self.inner.load(Ordering::Acquire) {} + } +} + // Newtype over `Cell` that forbids mutation through a shared reference pub struct Priority { inner: Cell, @@ -95,7 +121,7 @@ pub unsafe fn lock( if current < ceiling { if ceiling == (1 << nvic_prio_bits) { - priority.set(u8::MAX); + priority.set(u8::max_value()); let r = interrupt::free(|_| f(&mut *ptr)); priority.set(current); r @@ -124,7 +150,7 @@ pub unsafe fn lock( let current = priority.get(); if current < ceiling { - priority.set(u8::MAX); + priority.set(u8::max_value()); let r = interrupt::free(|_| f(&mut *ptr)); priority.set(current); r diff --git a/src/lib.rs b/src/lib.rs index 1fe88c4728..73e6e2001c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,68 +33,45 @@ //! //! # Cargo features //! -//! - `timer-queue`. This opt-in feature enables the `schedule` API which can be used to schedule -//! tasks to run in the future. Also see [`Instant`] and [`Duration`]. -//! -//! [`Instant`]: struct.Instant.html -//! [`Duration`]: struct.Duration.html -//! -//! - `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! +//! - `heterogeneous`. This opt-in feature enables the *experimental* heterogeneous multi-core support. #![deny(missing_docs)] +#![deny(rust_2018_compatibility)] +#![deny(rust_2018_idioms)] #![deny(warnings)] #![no_std] -#[cfg(feature = "timer-queue")] -use core::cmp::Ordering; -use core::{fmt, ops}; +use core::ops::Sub; -#[cfg(not(feature = "timer-queue"))] -use cortex_m::peripheral::SYST; use cortex_m::{ interrupt::Nr, peripheral::{CBP, CPUID, DCB, DWT, FPB, FPU, ITM, MPU, NVIC, SCB, TPIU}, }; +#[cfg(not(feature = "heterogeneous"))] +use cortex_m_rt as _; // vector table pub use cortex_m_rtfm_macros::app; +pub use rtfm_core::{Exclusive, Mutex}; +#[cfg(armv7m)] +pub mod cyccnt; #[doc(hidden)] pub mod export; #[doc(hidden)] -#[cfg(feature = "timer-queue")] mod tq; -#[cfg(all(feature = "timer-queue", armv6m))] -compile_error!( - "The `timer-queue` feature is currently not supported on ARMv6-M (`thumbv6m-none-eabi`)" -); - -/// Core peripherals -/// -/// This is `cortex_m::Peripherals` minus the peripherals that the RTFM runtime uses -/// -/// - The `NVIC` field is never present. -/// - When the `timer-queue` feature is enabled the following fields are *not* present: `DWT` and -/// `SYST`. +/// `cortex_m::Peripherals` minus `SYST` #[allow(non_snake_case)] -pub struct Peripherals<'a> { +pub struct Peripherals { /// Cache and branch predictor maintenance operations (not present on Cortex-M0 variants) pub CBP: CBP, /// CPUID pub CPUID: CPUID, - /// Debug Control Block (by value if the `timer-queue` feature is disabled) - #[cfg(feature = "timer-queue")] - pub DCB: &'a mut DCB, - - /// Debug Control Block (borrowed if the `timer-queue` feature is enabled) - #[cfg(not(feature = "timer-queue"))] + /// Debug Control Block pub DCB: DCB, - /// Data Watchpoint and Trace unit (not present if the `timer-queue` feature is enabled) - #[cfg(not(feature = "timer-queue"))] + /// Data Watchpoint and Trace unit pub DWT: DWT, /// Flash Patch and Breakpoint unit (not present on Cortex-M0 variants) @@ -109,245 +86,52 @@ pub struct Peripherals<'a> { /// Memory Protection Unit pub MPU: MPU, - // Nested Vector Interrupt Controller - // pub NVIC: NVIC, + /// Nested Vector Interrupt Controller + pub NVIC: NVIC, + /// System Control Block - pub SCB: &'a mut SCB, - - /// SysTick: System Timer (not present if the `timer-queue` is enabled) - #[cfg(not(feature = "timer-queue"))] - pub SYST: SYST, + pub SCB: SCB, + // SysTick: System Timer + // pub SYST: SYST, /// Trace Port Interface Unit (not present on Cortex-M0 variants) pub TPIU: TPIU, } -/// A measurement of a monotonically nondecreasing clock. Opaque and useful only with `Duration` -/// -/// This data type is only available when the `timer-queue` feature is enabled -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[cfg(feature = "timer-queue")] -pub struct Instant(i32); - -#[cfg(feature = "timer-queue")] -impl Instant { - /// IMPLEMENTATION DETAIL. DO NOT USE - #[doc(hidden)] - pub unsafe fn artificial(timestamp: i32) -> Self { - Instant(timestamp) - } - - /// Returns an instant corresponding to "now" - pub fn now() -> Self { - Instant(DWT::get_cycle_count() as i32) - } - - /// Returns the amount of time elapsed since this instant was created. - pub fn elapsed(&self) -> Duration { - Instant::now() - *self - } - - /// Returns the amount of time elapsed from another instant to this one. - pub fn duration_since(&self, earlier: Instant) -> Duration { - let diff = self.0 - earlier.0; - assert!(diff >= 0, "second instant is later than self"); - Duration(diff as u32) +impl From for Peripherals { + fn from(p: cortex_m::Peripherals) -> Self { + Self { + CBP: p.CBP, + CPUID: p.CPUID, + DCB: p.DCB, + DWT: p.DWT, + FPB: p.FPB, + FPU: p.FPU, + ITM: p.ITM, + MPU: p.MPU, + NVIC: p.NVIC, + SCB: p.SCB, + TPIU: p.TPIU, + } } } -#[cfg(feature = "timer-queue")] -impl ops::AddAssign for Instant { - fn add_assign(&mut self, dur: Duration) { - debug_assert!(dur.0 < (1 << 31)); - self.0 = self.0.wrapping_add(dur.0 as i32); - } -} +/// A monotonic clock / counter +pub unsafe trait Monotonic { + /// A measurement of this clock + type Instant: Copy + Ord + Sub; -#[cfg(feature = "timer-queue")] -impl ops::Add for Instant { - type Output = Self; + /// The ratio between the SysTick (system timer) frequency and this clock frequency + fn ratio() -> u32; - fn add(mut self, dur: Duration) -> Self { - self += dur; - self - } -} + /// Returns the current time + fn now() -> Self::Instant; -#[cfg(feature = "timer-queue")] -impl ops::SubAssign for Instant { - fn sub_assign(&mut self, dur: Duration) { - // XXX should this be a non-debug assertion? - debug_assert!(dur.0 < (1 << 31)); - self.0 = self.0.wrapping_sub(dur.0 as i32); - } -} + /// Resets the counter to *zero* + unsafe fn reset(); -#[cfg(feature = "timer-queue")] -impl ops::Sub for Instant { - type Output = Self; - - fn sub(mut self, dur: Duration) -> Self { - self -= dur; - self - } -} - -#[cfg(feature = "timer-queue")] -impl ops::Sub for Instant { - type Output = Duration; - - fn sub(self, other: Instant) -> Duration { - self.duration_since(other) - } -} - -#[cfg(feature = "timer-queue")] -impl Ord for Instant { - fn cmp(&self, rhs: &Self) -> Ordering { - self.0.wrapping_sub(rhs.0).cmp(&0) - } -} - -#[cfg(feature = "timer-queue")] -impl PartialOrd for Instant { - fn partial_cmp(&self, rhs: &Self) -> Option { - Some(self.cmp(rhs)) - } -} - -/// A `Duration` type to represent a span of time. -/// -/// This data type is only available when the `timer-queue` feature is enabled -#[derive(Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)] -#[cfg(feature = "timer-queue")] -pub struct Duration(u32); - -#[cfg(feature = "timer-queue")] -impl Duration { - /// Returns the total number of clock cycles contained by this `Duration` - pub fn as_cycles(&self) -> u32 { - self.0 - } -} - -#[cfg(feature = "timer-queue")] -impl ops::AddAssign for Duration { - fn add_assign(&mut self, dur: Duration) { - self.0 += dur.0; - } -} - -#[cfg(feature = "timer-queue")] -impl ops::Add for Duration { - type Output = Self; - - fn add(self, other: Self) -> Self { - Duration(self.0 + other.0) - } -} - -#[cfg(feature = "timer-queue")] -impl ops::SubAssign for Duration { - fn sub_assign(&mut self, rhs: Duration) { - self.0 -= rhs.0; - } -} - -#[cfg(feature = "timer-queue")] -impl ops::Sub for Duration { - type Output = Self; - - fn sub(self, rhs: Self) -> Self { - Duration(self.0 - rhs.0) - } -} - -/// Adds the `cycles` method to the `u32` type -/// -/// This trait is only available when the `timer-queue` feature is enabled -#[cfg(feature = "timer-queue")] -pub trait U32Ext { - /// Converts the `u32` value into clock cycles - fn cycles(self) -> Duration; -} - -#[cfg(feature = "timer-queue")] -impl U32Ext for u32 { - fn cycles(self) -> Duration { - Duration(self) - } -} - -/// Memory safe access to shared resources -/// -/// In RTFM, locks are implemented as critical sections that prevent other tasks from *starting*. -/// These critical sections are implemented by temporarily increasing the dynamic priority (see -/// [BASEPRI]) of the current context. Entering and leaving these critical sections is always done -/// in constant time (a few instructions). -/// -/// [BASEPRI]: https://developer.arm.com/products/architecture/cpu-architecture/m-profile/docs/100701/latest/special-purpose-mask-registers -pub trait Mutex { - /// Data protected by the mutex - type T; - - /// Creates a critical section and grants temporary access to the protected data - fn lock(&mut self, f: impl FnOnce(&mut Self::T) -> R) -> R; -} - -impl<'a, M> Mutex for &'a mut M -where - M: Mutex, -{ - type T = M::T; - - fn lock(&mut self, f: impl FnOnce(&mut M::T) -> R) -> R { - (**self).lock(f) - } -} - -/// Newtype over `&'a mut T` that implements the `Mutex` trait -/// -/// The `Mutex` implementation for this type is a no-op, no critical section is created -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: impl FnOnce(&mut T) -> R) -> R { - f(self.0) - } -} - -impl<'a, T> fmt::Debug for Exclusive<'a, T> -where - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - (**self).fmt(f) - } -} - -impl<'a, T> fmt::Display for Exclusive<'a, T> -where - T: fmt::Display, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - (**self).fmt(f) - } -} - -impl<'a, T> ops::Deref for Exclusive<'a, T> { - type Target = T; - - fn deref(&self) -> &T { - self.0 - } -} - -impl<'a, T> ops::DerefMut for Exclusive<'a, T> { - fn deref_mut(&mut self) -> &mut T { - self.0 - } + /// A `Self::Instant` that represents a count of *zero* + fn zero() -> Self::Instant; } /// Sets the given `interrupt` as pending diff --git a/src/tq.rs b/src/tq.rs index 8ca1bd3f8e..4f9b6e7e91 100644 --- a/src/tq.rs +++ b/src/tq.rs @@ -1,36 +1,34 @@ -use core::cmp::{self, Ordering}; +use core::{ + cmp::{self, Ordering}, + convert::TryInto, + mem, + ops::Sub, +}; use cortex_m::peripheral::{SCB, SYST}; use heapless::{binary_heap::Min, ArrayLength, BinaryHeap}; -use crate::Instant; +use crate::Monotonic; -pub struct TimerQueue +pub struct TimerQueue(pub BinaryHeap, N, Min>) where - N: ArrayLength>, + M: Monotonic, + ::Output: TryInto, + N: ArrayLength>, + T: Copy; + +impl TimerQueue +where + M: Monotonic, + ::Output: TryInto, + N: ArrayLength>, T: Copy, { - pub syst: SYST, - pub queue: BinaryHeap, N, Min>, -} - -impl TimerQueue -where - N: ArrayLength>, - T: Copy, -{ - pub fn new(syst: SYST) -> Self { - TimerQueue { - syst, - queue: BinaryHeap::new(), - } - } - #[inline] - pub unsafe fn enqueue_unchecked(&mut self, nr: NotReady) { + pub unsafe fn enqueue_unchecked(&mut self, nr: NotReady) { let mut is_empty = true; if self - .queue + .0 .peek() .map(|head| { is_empty = false; @@ -39,77 +37,102 @@ where .unwrap_or(true) { if is_empty { - self.syst.enable_interrupt(); + mem::transmute::<_, SYST>(()).enable_interrupt(); } // set SysTick pending SCB::set_pendst(); } - self.queue.push_unchecked(nr); + self.0.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); + unsafe { + if let Some(instant) = self.0.peek().map(|p| p.instant) { + let now = M::now(); - if diff < 0 { - // task became ready - let nr = unsafe { self.queue.pop_unchecked() }; + if instant < now { + // task became ready + let nr = self.0.pop_unchecked(); - Some((nr.task, nr.index)) + Some((nr.task, nr.index)) + } else { + // set a new timeout + const MAX: u32 = 0x00ffffff; + + let dur = match (instant - now) + .try_into() + .ok() + .and_then(|x| x.checked_mul(M::ratio())) + { + None => MAX, + Some(x) => cmp::min(MAX, x), + }; + mem::transmute::<_, SYST>(()).set_reload(dur); + + // start counting down from the new reload + mem::transmute::<_, SYST>(()).clear_current(); + + None + } } 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(); + // the queue is empty + mem::transmute::<_, SYST>(()).disable_interrupt(); None } - } else { - // the queue is empty - self.syst.disable_interrupt(); - None } } } -pub struct NotReady +pub struct NotReady where T: Copy, + M: Monotonic, + ::Output: TryInto, { pub index: u8, - pub instant: Instant, + pub instant: M::Instant, pub task: T, } -impl Eq for NotReady where T: Copy {} - -impl Ord for NotReady +impl Eq for NotReady where T: Copy, + M: Monotonic, + ::Output: TryInto, +{ +} + +impl Ord for NotReady +where + T: Copy, + M: Monotonic, + ::Output: TryInto, { fn cmp(&self, other: &Self) -> Ordering { self.instant.cmp(&other.instant) } } -impl PartialEq for NotReady +impl PartialEq for NotReady where T: Copy, + M: Monotonic, + ::Output: TryInto, { fn eq(&self, other: &Self) -> bool { self.instant == other.instant } } -impl PartialOrd for NotReady +impl PartialOrd for NotReady where T: Copy, + M: Monotonic, + ::Output: TryInto, { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(&other)) diff --git a/tests/cfail/duplicate-args-2.rs b/tests/cfail/duplicate-args-2.rs deleted file mode 100644 index 5bef79b5f7..0000000000 --- a/tests/cfail/duplicate-args-2.rs +++ /dev/null @@ -1,24 +0,0 @@ -#![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( - priority = 1, - priority = 2, //~ ERROR argument appears more than once - )] - fn foo(_: foo::Context) {} - - extern "C" { - fn UART0(); - } -}; diff --git a/tests/cfail/duplicate-args.rs b/tests/cfail/duplicate-args.rs deleted file mode 100644 index 6938cd0d72..0000000000 --- a/tests/cfail/duplicate-args.rs +++ /dev/null @@ -1,24 +0,0 @@ -#![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( - capacity = 1, - capacity = 2, //~ ERROR argument appears more than once - )] - fn foo(_: foo::Context) {} - - extern "C" { - fn UART0(); - } -}; diff --git a/tests/cfail/exception-divergent.rs b/tests/cfail/exception-divergent.rs deleted file mode 100644 index 3fe9a36528..0000000000 --- a/tests/cfail/exception-divergent.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![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] - 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 deleted file mode 100644 index d1363fe58d..0000000000 --- a/tests/cfail/exception-input.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![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] - 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 deleted file mode 100644 index 4bb8f1ec08..0000000000 --- a/tests/cfail/exception-invalid.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![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] - 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 deleted file mode 100644 index 8f6729857e..0000000000 --- a/tests/cfail/exception-output.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![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] - 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 deleted file mode 100644 index d5eae20baa..0000000000 --- a/tests/cfail/exception-sys-tick.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![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] - 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 deleted file mode 100644 index feb83e8b72..0000000000 --- a/tests/cfail/idle-input.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![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] - 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 deleted file mode 100644 index 505fba1455..0000000000 --- a/tests/cfail/idle-not-divergent.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![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] - 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 deleted file mode 100644 index 0e779ffcee..0000000000 --- a/tests/cfail/init-divergent.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![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) -> ! { - //~^ ERROR `init` must have type signature `fn(init::Context) [-> init::LateResources]` - loop {} - } -}; diff --git a/tests/cfail/init-extra-late-resources.rs b/tests/cfail/init-extra-late-resources.rs deleted file mode 100644 index d2d4a6d7c2..0000000000 --- a/tests/cfail/init-extra-late-resources.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![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) -> init::LateResources {} - //~^ error: `init` signature must be `fn(init::Context)` if there are no late resources -}; diff --git a/tests/cfail/init-input.rs b/tests/cfail/init-input.rs deleted file mode 100644 index 9063efe355..0000000000 --- a/tests/cfail/init-input.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![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, undef: u32) { - //~^ ERROR `init` must have type signature `fn(init::Context) [-> init::LateResources]` - } -}; diff --git a/tests/cfail/init-missing-late-resources.rs b/tests/cfail/init-missing-late-resources.rs deleted file mode 100644 index cec18babaf..0000000000 --- a/tests/cfail/init-missing-late-resources.rs +++ /dev/null @@ -1,17 +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 X: i32 = (); - - #[init] - fn init(_: init::Context) {} - //~^ error: late resources have been specified so `init` must return `init::LateResources` -}; diff --git a/tests/cfail/init-not-send.rs b/tests/cfail/init-not-send.rs deleted file mode 100644 index 5a33fac764..0000000000 --- a/tests/cfail/init-not-send.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! This is equivalent to the `late-not-send` cfail test - -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use core::marker::PhantomData; - -use rtfm::app; - -pub struct NotSend { - _0: PhantomData<*const ()>, -} - -#[app(device = lm3s6965)] //~ ERROR `*const ()` cannot be sent between threads safely -const APP: () = { - static mut X: Option = None; - - #[init(resources = [X])] - fn init(c: init::Context) { - *c.resources.X = Some(NotSend { _0: PhantomData }) - } - - #[interrupt(resources = [X])] - fn UART0(_: UART0::Context) {} -}; diff --git a/tests/cfail/init-output.rs b/tests/cfail/init-output.rs deleted file mode 100644 index f88d5340c0..0000000000 --- a/tests/cfail/init-output.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![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) -> 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 deleted file mode 100644 index 7148fbf34f..0000000000 --- a/tests/cfail/insufficient-free-interrupts.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] //~ ERROR 1 free interrupt (`extern { .. }`) is required -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[task] - fn foo(_: foo::Context) {} -}; diff --git a/tests/cfail/interrupt-divergent.rs b/tests/cfail/interrupt-divergent.rs deleted file mode 100644 index b67601eea5..0000000000 --- a/tests/cfail/interrupt-divergent.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![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] - 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 deleted file mode 100644 index f11b2d3960..0000000000 --- a/tests/cfail/interrupt-input.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![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] - 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 deleted file mode 100644 index 69e4957fd8..0000000000 --- a/tests/cfail/interrupt-output.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![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] - 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 deleted file mode 100644 index 00d6c8ceed..0000000000 --- a/tests/cfail/late-assigned-to-init.rs +++ /dev/null @@ -1,16 +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 X: u32 = (); - - #[init(resources = [X])] //~ ERROR late resources can NOT be assigned to `init` - fn init(_: init::Context) {} -}; diff --git a/tests/cfail/late-not-send.rs b/tests/cfail/late-not-send.rs deleted file mode 100644 index 04a4af158b..0000000000 --- a/tests/cfail/late-not-send.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! `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 - -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use core::marker::PhantomData; - -use rtfm::app; - -struct NotSend { - _0: PhantomData<*const ()>, -} - -#[app(device = lm3s6965)] //~ ERROR `*const ()` cannot be sent between threads safely -const APP: () = { - static mut X: NotSend = (); - - #[init] - fn init(_: init::Context) -> init::LateResources { - init::LateResources { - X: NotSend { _0: PhantomData }, - } - } - - #[interrupt(resources = [X])] - fn UART0(_: UART0::Context) {} -}; diff --git a/tests/cfail/needs-send.rs b/tests/cfail/needs-send.rs deleted file mode 100644 index 8dc9707fc9..0000000000 --- a/tests/cfail/needs-send.rs +++ /dev/null @@ -1,29 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use core::marker::PhantomData; - -use rtfm::app; - -pub struct NotSend { - _0: PhantomData<*const ()>, -} - -unsafe impl Sync for NotSend {} - -#[app(device = lm3s6965)] //~ ERROR cannot be sent between threads safely -const APP: () = { - #[init(spawn = [foo])] - fn init(_: init::Context) {} - - #[task] - fn foo(_: foo::Context, _x: NotSend) {} - - extern "C" { - fn UART0(); - } -}; diff --git a/tests/cfail/needs-sync.rs b/tests/cfail/needs-sync.rs deleted file mode 100644 index 6025e7d56a..0000000000 --- a/tests/cfail/needs-sync.rs +++ /dev/null @@ -1,35 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use core::marker::PhantomData; - -use rtfm::app; - -pub struct NotSync { - _0: PhantomData<*const ()>, -} - -unsafe impl Send for NotSync {} - -#[app(device = lm3s6965)] //~ ERROR cannot be shared between threads safely -const APP: () = { - static X: NotSync = NotSync { _0: PhantomData }; - - #[init(spawn = [foo])] - fn init(_: init::Context) {} - - #[task(priority = 1, resources = [X])] - fn foo(_: foo::Context) {} - - #[task(priority = 2, resources = [X])] - fn bar(_: bar::Context) {} - - extern "C" { - fn UART0(); - fn UART1(); - } -}; diff --git a/tests/cfail/priority-too-high.rs b/tests/cfail/priority-too-high.rs deleted file mode 100644 index 817462a350..0000000000 --- a/tests/cfail/priority-too-high.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] //~ error evaluation of constant value failed -const APP: () = { - #[init] - fn init(_: init::Context) {} - - // OK, this is the maximum priority supported by the device - #[interrupt(priority = 8)] - fn UART0(_: UART0::Context) {} - - // this value is too high! - #[interrupt(priority = 9)] - fn UART1(_: UART1::Context) {} -}; diff --git a/tests/cfail/priority-too-low.rs b/tests/cfail/priority-too-low.rs deleted file mode 100644 index 361156df54..0000000000 --- a/tests/cfail/priority-too-low.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![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) {} - - // OK, this is the minimum priority that tasks can have - #[interrupt(priority = 1)] - fn UART0(_: UART0::Context) {} - - // this value is too low! - #[interrupt(priority = 0)] //~ error this literal must be in the range 1...255 - fn UART1(_: UART1::Context) {} -}; diff --git a/tests/cfail/resource-not-declared.rs b/tests/cfail/resource-not-declared.rs deleted file mode 100644 index a37be42d38..0000000000 --- a/tests/cfail/resource-not-declared.rs +++ /dev/null @@ -1,14 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init(resources = [X])] //~ ERROR this resource has NOT been declared - fn init(_: init::Context) {} -}; diff --git a/tests/cfail/resource-pub.rs b/tests/cfail/resource-pub.rs deleted file mode 100644 index 3fb21f463c..0000000000 --- a/tests/cfail/resource-pub.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - pub static mut X: u32 = 0; - //~^ ERROR resources must have inherited / private visibility - - #[init] - fn init(_: init::Context) {} -}; diff --git a/tests/cfail/task-divergent.rs b/tests/cfail/task-divergent.rs deleted file mode 100644 index 577f0e06aa..0000000000 --- a/tests/cfail/task-divergent.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -#[rtfm::app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[task] - fn foo(_: foo::Context) -> ! { - //~^ ERROR this `task` handler must have type signature `fn(foo::Context, ..)` - loop {} - } - - extern "C" { - fn UART0(); - } -}; diff --git a/tests/cfail/task-idle.rs b/tests/cfail/task-idle.rs deleted file mode 100644 index 963bf1ee81..0000000000 --- a/tests/cfail/task-idle.rs +++ /dev/null @@ -1,23 +0,0 @@ -#![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] - fn idle(_: idle::Context) { - //~^ ERROR `task` handlers can NOT be named `idle`, `init` or `resources` - } - - extern "C" { - fn UART0(); - } -}; diff --git a/tests/cfail/task-not-declared.rs b/tests/cfail/task-not-declared.rs deleted file mode 100644 index 04309f599b..0000000000 --- a/tests/cfail/task-not-declared.rs +++ /dev/null @@ -1,14 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init(spawn = [X])] //~ ERROR this task has NOT been declared - fn init(_: init::Context) {} -}; diff --git a/tests/cfail/unsafe-exception.rs b/tests/cfail/unsafe-exception.rs deleted file mode 100644 index 353194a590..0000000000 --- a/tests/cfail/unsafe-exception.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![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 deleted file mode 100644 index fab1b0f1f6..0000000000 --- a/tests/cfail/unsafe-idle.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![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/unsafe-init.rs b/tests/cfail/unsafe-init.rs deleted file mode 100644 index d8bb560571..0000000000 --- a/tests/cfail/unsafe-init.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[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 deleted file mode 100644 index 93225edf14..0000000000 --- a/tests/cfail/unsafe-interrupt.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![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 deleted file mode 100644 index 58c4d70c21..0000000000 --- a/tests/cfail/unsafe-task.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![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 deleted file mode 100644 index ba9424fd4d..0000000000 --- a/tests/cfail/used-free-interrupt-2.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![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)] //~ ERROR free interrupts (`extern { .. }`) can't be used as interrupt handlers - fn foo(_: foo::Context) {} - - extern "C" { - fn UART0(); - } -}; diff --git a/tests/cfail/used-free-interrupt.rs b/tests/cfail/used-free-interrupt.rs deleted file mode 100644 index 1a56741b14..0000000000 --- a/tests/cfail/used-free-interrupt.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![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] - fn UART0(_: UART0::Context) {} - //~^ ERROR free interrupts (`extern { .. }`) can't be used as interrupt handlers - - extern "C" { - fn UART0(); - } -}; diff --git a/tests/compiletest.rs b/tests/compiletest.rs deleted file mode 100644 index 58702eecdf..0000000000 --- a/tests/compiletest.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::{fs, path::PathBuf, process::Command}; - -use compiletest_rs::{common::Mode, Config}; -use tempdir::TempDir; - -#[test] -fn cfail() { - let mut config = Config::default(); - - config.mode = Mode::CompileFail; - config.src_base = PathBuf::from("tests/cfail"); - config.link_deps(); - - // remove duplicate and trailing `-L` flags - let mut s = String::new(); - if let Some(flags) = config.target_rustcflags.as_mut() { - let mut iter = flags.split_whitespace().peekable(); - - while let Some(flag) = iter.next() { - if flag == "-L" && (iter.peek() == Some(&"-L") || iter.peek() == None) { - iter.next(); - continue; - } - - s += flag; - s += " "; - } - - // path to proc-macro crate - s += "-L target/debug/deps "; - - // avoid "error: language item required, but not found: `eh_personality`" - s += "-C panic=abort "; - } - - let td = TempDir::new("rtfm").unwrap(); - for f in fs::read_dir("tests/cpass").unwrap() { - let f = f.unwrap().path(); - let name = f.file_stem().unwrap().to_str().unwrap(); - - assert!(Command::new("rustc") - .args(s.split_whitespace()) - .arg(f.display().to_string()) - .arg("-o") - .arg(td.path().join(name).display().to_string()) - .arg("-C") - .arg("linker=true") - .status() - .unwrap() - .success()); - } - - config.target_rustcflags = Some(s); - config.clean_rmeta(); - - compiletest_rs::run_tests(&config); -} diff --git a/tests/cpass/late-resource.rs b/tests/cpass/late-resource.rs deleted file mode 100644 index 37dcf33163..0000000000 --- a/tests/cpass/late-resource.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Runtime initialized resources -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -#[rtfm::app(device = lm3s6965)] -const APP: () = { - static mut X: u32 = (); - static Y: u32 = (); - - #[init] - fn init(_: init::Context) -> init::LateResources { - init::LateResources { X: 0, Y: 1 } - } -}; diff --git a/tests/cpass/peripheral.rs b/tests/cpass/peripheral.rs deleted file mode 100644 index 34352b845c..0000000000 --- a/tests/cpass/peripheral.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Core and device peripherals -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -#[rtfm::app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(c: init::Context) { - let _: rtfm::Peripherals = c.core; - let _: lm3s6965::Peripherals = c.device; - } -}; diff --git a/tests/multi.rs b/tests/multi.rs new file mode 100644 index 0000000000..675fc5e9ce --- /dev/null +++ b/tests/multi.rs @@ -0,0 +1,16 @@ +use std::path::PathBuf; + +use compiletest_rs::{common::Mode, Config}; + +#[test] +fn ui() { + let mut config = Config::default(); + + config.mode = Mode::Ui; + config.src_base = PathBuf::from("ui/multi"); + config.target_rustcflags = Some("--edition=2018 -Z unstable-options --extern rtfm".to_owned()); + config.link_deps(); + config.clean_rmeta(); + + compiletest_rs::run_tests(&config); +} diff --git a/tests/single.rs b/tests/single.rs new file mode 100644 index 0000000000..93addf6e02 --- /dev/null +++ b/tests/single.rs @@ -0,0 +1,17 @@ +use std::path::PathBuf; + +use compiletest_rs::{common::Mode, Config}; + +#[test] +fn ui() { + let mut config = Config::default(); + + config.mode = Mode::Ui; + config.src_base = PathBuf::from("ui/single"); + config.target_rustcflags = + Some("--edition=2018 -L target/debug/deps -Z unstable-options --extern rtfm --extern lm3s6965".to_owned()); + config.link_deps(); + config.clean_rmeta(); + + compiletest_rs::run_tests(&config); +} diff --git a/ui/single/exception-invalid.rs b/ui/single/exception-invalid.rs new file mode 100644 index 0000000000..426cb67342 --- /dev/null +++ b/ui/single/exception-invalid.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtfm::app(device = lm3s6965)] +const APP: () = { + #[exception] + fn NonMaskableInt(_: NonMaskableInt::Context) {} +}; diff --git a/ui/single/exception-invalid.stderr b/ui/single/exception-invalid.stderr new file mode 100644 index 0000000000..f7fc2922f7 --- /dev/null +++ b/ui/single/exception-invalid.stderr @@ -0,0 +1,8 @@ +error: only exceptions with configurable priority can be used as hardware tasks + --> $DIR/exception-invalid.rs:6:8 + | +6 | fn NonMaskableInt(_: NonMaskableInt::Context) {} + | ^^^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/ui/single/exception-systick-used.rs b/ui/single/exception-systick-used.rs new file mode 100644 index 0000000000..d30da1bd72 --- /dev/null +++ b/ui/single/exception-systick-used.rs @@ -0,0 +1,10 @@ +#![no_main] + +#[rtfm::app(device = lm3s6965)] +const APP: () = { + #[exception] + fn SysTick(_: SysTick::Context) {} + + #[task(schedule = [foo])] + fn foo(_: foo::Context) {} +}; diff --git a/ui/single/exception-systick-used.stderr b/ui/single/exception-systick-used.stderr new file mode 100644 index 0000000000..47786c6c31 --- /dev/null +++ b/ui/single/exception-systick-used.stderr @@ -0,0 +1,8 @@ +error: this exception can't be used because it's being used by the runtime + --> $DIR/exception-systick-used.rs:6:8 + | +6 | fn SysTick(_: SysTick::Context) {} + | ^^^^^^^ + +error: aborting due to previous error + diff --git a/ui/single/extern-interrupt-not-enough.rs b/ui/single/extern-interrupt-not-enough.rs new file mode 100644 index 0000000000..39c5d8eaf3 --- /dev/null +++ b/ui/single/extern-interrupt-not-enough.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtfm::app(device = lm3s6965)] +const APP: () = { + #[task] + fn a(_: a::Context) {} +}; diff --git a/ui/single/extern-interrupt-not-enough.stderr b/ui/single/extern-interrupt-not-enough.stderr new file mode 100644 index 0000000000..43249c4954 --- /dev/null +++ b/ui/single/extern-interrupt-not-enough.stderr @@ -0,0 +1,8 @@ +error: not enough `extern` interrupts to dispatch all software tasks (need: 1; given: 0) + --> $DIR/extern-interrupt-not-enough.rs:6:8 + | +6 | fn a(_: a::Context) {} + | ^ + +error: aborting due to previous error + diff --git a/ui/single/extern-interrupt-used.rs b/ui/single/extern-interrupt-used.rs new file mode 100644 index 0000000000..25f34b3627 --- /dev/null +++ b/ui/single/extern-interrupt-used.rs @@ -0,0 +1,11 @@ +#![no_main] + +#[rtfm::app(device = lm3s6965)] +const APP: () = { + #[interrupt(binds = UART0)] + fn a(_: a::Context) {} + + extern "C" { + fn UART0(); + } +}; diff --git a/ui/single/extern-interrupt-used.stderr b/ui/single/extern-interrupt-used.stderr new file mode 100644 index 0000000000..8707b1d2e7 --- /dev/null +++ b/ui/single/extern-interrupt-used.stderr @@ -0,0 +1,8 @@ +error: `extern` interrupts can't be used as hardware tasks + --> $DIR/extern-interrupt-used.rs:5:25 + | +5 | #[interrupt(binds = UART0)] + | ^^^^^ + +error: aborting due to previous error + diff --git a/tests/cfail/cfg-static.rs b/ui/single/locals-cfg.rs similarity index 60% rename from tests/cfail/cfg-static.rs rename to ui/single/locals-cfg.rs index 91465a1e7a..bcce5ca99c 100644 --- a/tests/cfail/cfg-static.rs +++ b/ui/single/locals-cfg.rs @@ -1,20 +1,13 @@ #![no_main] -#![no_std] -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(_: init::Context) { #[cfg(never)] static mut FOO: u32 = 0; - FOO; //~ ERROR cannot find value `FOO` in this scope + FOO; } #[idle] @@ -22,7 +15,7 @@ const APP: () = { #[cfg(never)] static mut FOO: u32 = 0; - FOO; //~ ERROR cannot find value `FOO` in this scope + FOO; loop {} } @@ -32,7 +25,7 @@ const APP: () = { #[cfg(never)] static mut FOO: u32 = 0; - FOO; //~ ERROR cannot find value `FOO` in this scope + FOO; } #[interrupt] @@ -40,7 +33,7 @@ const APP: () = { #[cfg(never)] static mut FOO: u32 = 0; - FOO; //~ ERROR cannot find value `FOO` in this scope + FOO; } #[task] @@ -48,7 +41,7 @@ const APP: () = { #[cfg(never)] static mut FOO: u32 = 0; - FOO; //~ ERROR cannot find value `FOO` in this scope + FOO; } extern "C" { diff --git a/ui/single/locals-cfg.stderr b/ui/single/locals-cfg.stderr new file mode 100644 index 0000000000..fc324f139a --- /dev/null +++ b/ui/single/locals-cfg.stderr @@ -0,0 +1,33 @@ +error[E0425]: cannot find value `FOO` in this scope + --> $DIR/locals-cfg.rs:10:9 + | +10 | FOO; + | ^^^ not found in this scope + +error[E0425]: cannot find value `FOO` in this scope + --> $DIR/locals-cfg.rs:18:9 + | +18 | FOO; + | ^^^ not found in this scope + +error[E0425]: cannot find value `FOO` in this scope + --> $DIR/locals-cfg.rs:28:9 + | +28 | FOO; + | ^^^ not found in this scope + +error[E0425]: cannot find value `FOO` in this scope + --> $DIR/locals-cfg.rs:36:9 + | +36 | FOO; + | ^^^ not found in this scope + +error[E0425]: cannot find value `FOO` in this scope + --> $DIR/locals-cfg.rs:44:9 + | +44 | FOO; + | ^^^ not found in this scope + +error: aborting due to 5 previous errors + +For more information about this error, try `rustc --explain E0425`. diff --git a/tests/cfail/cfg-resources.rs b/ui/single/resources-cfg.rs similarity index 50% rename from tests/cfail/cfg-resources.rs rename to ui/single/resources-cfg.rs index 5e20c4de6c..f8c36729e3 100644 --- a/tests/cfail/cfg-resources.rs +++ b/ui/single/resources-cfg.rs @@ -1,13 +1,6 @@ #![no_main] -#![no_std] -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[cfg(never)] static mut O1: u32 = 0; // init @@ -31,34 +24,34 @@ const APP: () = { #[init(resources = [O1, O4, O5, O6, 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` + c.resources.O1; + c.resources.O4; + c.resources.O5; + c.resources.O6; + c.resources.S3; } #[idle(resources = [O2, O4, S1, 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` + c.resources.O2; + c.resources.O4; + c.resources.S1; + c.resources.S3; loop {} } #[interrupt(resources = [O3, S1, S2, 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` + c.resources.O3; + c.resources.S1; + c.resources.S2; + c.resources.S3; } #[interrupt(resources = [S2, O5])] fn UART1(c: UART1::Context) { - c.resources.S2; //~ ERROR no field `S2` - c.resources.O5; //~ ERROR no field `O5` + c.resources.S2; + c.resources.O5; } }; diff --git a/ui/single/resources-cfg.stderr b/ui/single/resources-cfg.stderr new file mode 100644 index 0000000000..88c34d2092 --- /dev/null +++ b/ui/single/resources-cfg.stderr @@ -0,0 +1,123 @@ +error[E0609]: no field `O1` on type `initResources<'_>` + --> $DIR/resources-cfg.rs:27:21 + | +27 | c.resources.O1; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `O4` on type `initResources<'_>` + --> $DIR/resources-cfg.rs:28:21 + | +28 | c.resources.O4; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `O5` on type `initResources<'_>` + --> $DIR/resources-cfg.rs:29:21 + | +29 | c.resources.O5; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `O6` on type `initResources<'_>` + --> $DIR/resources-cfg.rs:30:21 + | +30 | c.resources.O6; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `S3` on type `initResources<'_>` + --> $DIR/resources-cfg.rs:31:21 + | +31 | c.resources.S3; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `O2` on type `idleResources<'_>` + --> $DIR/resources-cfg.rs:36:21 + | +36 | c.resources.O2; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `O4` on type `idleResources<'_>` + --> $DIR/resources-cfg.rs:37:21 + | +37 | c.resources.O4; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `S1` on type `idleResources<'_>` + --> $DIR/resources-cfg.rs:38:21 + | +38 | c.resources.S1; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `S3` on type `idleResources<'_>` + --> $DIR/resources-cfg.rs:39:21 + | +39 | c.resources.S3; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `O3` on type `UART0Resources<'_>` + --> $DIR/resources-cfg.rs:46:21 + | +46 | c.resources.O3; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `S1` on type `UART0Resources<'_>` + --> $DIR/resources-cfg.rs:47:21 + | +47 | c.resources.S1; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `S2` on type `UART0Resources<'_>` + --> $DIR/resources-cfg.rs:48:21 + | +48 | c.resources.S2; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `S3` on type `UART0Resources<'_>` + --> $DIR/resources-cfg.rs:49:21 + | +49 | c.resources.S3; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `S2` on type `UART1Resources<'_>` + --> $DIR/resources-cfg.rs:54:21 + | +54 | c.resources.S2; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `O5` on type `UART1Resources<'_>` + --> $DIR/resources-cfg.rs:55:21 + | +55 | c.resources.O5; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error: aborting due to 15 previous errors + +For more information about this error, try `rustc --explain E0609`. diff --git a/ui/single/task-priority-too-high.rs b/ui/single/task-priority-too-high.rs new file mode 100644 index 0000000000..c7c9dc9bf9 --- /dev/null +++ b/ui/single/task-priority-too-high.rs @@ -0,0 +1,38 @@ +#![no_main] + +use rtfm::app; + +#[rtfm::app(device = lm3s6965)] +const APP: () = { + #[init] + fn init(_: init::Context) {} + + #[interrupt(priority = 1)] + fn GPIOA(_: GPIOA::Context) {} + + #[interrupt(priority = 2)] + fn GPIOB(_: GPIOB::Context) {} + + #[interrupt(priority = 3)] + fn GPIOC(_: GPIOC::Context) {} + + #[interrupt(priority = 4)] + fn GPIOD(_: GPIOD::Context) {} + + #[interrupt(priority = 5)] + fn GPIOE(_: GPIOE::Context) {} + + #[interrupt(priority = 6)] + fn UART0(_: UART0::Context) {} + + #[interrupt(priority = 7)] + fn UART1(_: UART1::Context) {} + + // OK, this is the maximum priority supported by the device + #[interrupt(priority = 8)] + fn SSI0(_: SSI0::Context) {} + + // this value is too high! + #[interrupt(priority = 9)] + fn I2C0(_: I2C0::Context) {} +}; diff --git a/ui/single/task-priority-too-high.stderr b/ui/single/task-priority-too-high.stderr new file mode 100644 index 0000000000..b402a95ca4 --- /dev/null +++ b/ui/single/task-priority-too-high.stderr @@ -0,0 +1,9 @@ +error[E0080]: evaluation of constant value failed + --> $DIR/task-priority-too-high.rs:5:1 + | +5 | #[rtfm::app(device = lm3s6965)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ attempt to subtract with overflow + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0080`.