aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml58
-rw-r--r--ci/install.sh6
-rw-r--r--ci/script.sh161
-rw-r--r--examples/baseline.rs5
-rw-r--r--examples/binds.rs3
-rw-r--r--examples/capacity.rs3
-rw-r--r--examples/cfg.rs9
-rw-r--r--examples/generics.rs13
-rw-r--r--examples/idle.rs3
-rw-r--r--examples/init.rs7
-rw-r--r--examples/interrupt.rs3
-rw-r--r--examples/late.rs9
-rw-r--r--examples/lock.rs5
-rw-r--r--examples/message.rs3
-rw-r--r--examples/not-send.rs7
-rw-r--r--examples/not-sync.rs3
-rw-r--r--examples/periodic.rs7
-rw-r--r--examples/pool.rs3
-rw-r--r--examples/ramfunc.rs3
-rw-r--r--examples/resource.rs7
-rw-r--r--examples/schedule.rs7
-rw-r--r--examples/shared-with-init.rs3
-rw-r--r--examples/smallest.rs9
-rw-r--r--examples/static.rs7
-rw-r--r--examples/t-binds.rs (renamed from tests/cpass/binds.rs)7
-rw-r--r--examples/t-cfg.rs (renamed from tests/cpass/cfg.rs)10
-rw-r--r--examples/t-late-not-send.rs (renamed from tests/cpass/late-not-send.rs)16
-rw-r--r--examples/t-resource.rs (renamed from tests/cpass/resource.rs)14
-rw-r--r--examples/t-schedule.rs (renamed from tests/cpass/schedule.rs)11
-rw-r--r--examples/t-spawn.rs (renamed from tests/cpass/spawn.rs)7
-rw-r--r--examples/task.rs3
-rw-r--r--examples/types.rs9
-rw-r--r--macros/Cargo.toml12
-rw-r--r--macros/src/analyze.rs302
-rw-r--r--macros/src/check.rs287
-rw-r--r--macros/src/codegen.rs2467
-rw-r--r--macros/src/codegen/assertions.rs26
-rw-r--r--macros/src/codegen/dispatchers.rs178
-rw-r--r--macros/src/codegen/hardware_tasks.rs121
-rw-r--r--macros/src/codegen/idle.rs92
-rw-r--r--macros/src/codegen/init.rs112
-rw-r--r--macros/src/codegen/locals.rs94
-rw-r--r--macros/src/codegen/module.rs328
-rw-r--r--macros/src/codegen/post_init.rs139
-rw-r--r--macros/src/codegen/pre_init.rs150
-rw-r--r--macros/src/codegen/resources.rs115
-rw-r--r--macros/src/codegen/resources_struct.rs178
-rw-r--r--macros/src/codegen/schedule.rs95
-rw-r--r--macros/src/codegen/schedule_body.rs61
-rw-r--r--macros/src/codegen/software_tasks.rs169
-rw-r--r--macros/src/codegen/spawn.rs127
-rw-r--r--macros/src/codegen/spawn_body.rs81
-rw-r--r--macros/src/codegen/timer_queue.rs147
-rw-r--r--macros/src/codegen/util.rs253
-rw-r--r--macros/src/lib.rs312
-rw-r--r--macros/src/syntax.rs1382
-rw-r--r--macros/src/tests.rs5
-rw-r--r--macros/src/tests/multi.rs59
-rw-r--r--macros/src/tests/single.rs35
-rw-r--r--mc/Cargo.toml18
-rw-r--r--mc/README.md1
-rw-r--r--mc/examples/smallest.rs7
-rw-r--r--mc/examples/x-init-2.rs39
-rw-r--r--mc/examples/x-init.rs26
-rw-r--r--mc/examples/x-schedule.rs36
-rw-r--r--mc/examples/x-spawn.rs20
-rw-r--r--mc/src/lib.rs99
-rw-r--r--src/cyccnt.rs205
-rw-r--r--src/export.rs56
-rw-r--r--src/lib.rs308
-rw-r--r--src/tq.rs117
-rw-r--r--tests/cfail/duplicate-args-2.rs24
-rw-r--r--tests/cfail/duplicate-args.rs24
-rw-r--r--tests/cfail/exception-divergent.rs20
-rw-r--r--tests/cfail/exception-input.rs19
-rw-r--r--tests/cfail/exception-invalid.rs19
-rw-r--r--tests/cfail/exception-output.rs20
-rw-r--r--tests/cfail/exception-sys-tick.rs19
-rw-r--r--tests/cfail/idle-input.rs19
-rw-r--r--tests/cfail/idle-not-divergent.rs19
-rw-r--r--tests/cfail/init-divergent.rs17
-rw-r--r--tests/cfail/init-extra-late-resources.rs15
-rw-r--r--tests/cfail/init-input.rs16
-rw-r--r--tests/cfail/init-missing-late-resources.rs17
-rw-r--r--tests/cfail/init-not-send.rs29
-rw-r--r--tests/cfail/init-output.rs17
-rw-r--r--tests/cfail/insufficient-free-interrupts.rs17
-rw-r--r--tests/cfail/interrupt-divergent.rs20
-rw-r--r--tests/cfail/interrupt-input.rs19
-rw-r--r--tests/cfail/interrupt-output.rs20
-rw-r--r--tests/cfail/late-assigned-to-init.rs16
-rw-r--r--tests/cfail/late-not-send.rs32
-rw-r--r--tests/cfail/needs-send.rs29
-rw-r--r--tests/cfail/needs-sync.rs35
-rw-r--r--tests/cfail/priority-too-high.rs22
-rw-r--r--tests/cfail/priority-too-low.rs22
-rw-r--r--tests/cfail/resource-not-declared.rs14
-rw-r--r--tests/cfail/resource-pub.rs17
-rw-r--r--tests/cfail/task-divergent.rs22
-rw-r--r--tests/cfail/task-idle.rs23
-rw-r--r--tests/cfail/task-not-declared.rs14
-rw-r--r--tests/cfail/unsafe-exception.rs18
-rw-r--r--tests/cfail/unsafe-idle.rs20
-rw-r--r--tests/cfail/unsafe-init.rs15
-rw-r--r--tests/cfail/unsafe-interrupt.rs18
-rw-r--r--tests/cfail/unsafe-task.rs22
-rw-r--r--tests/cfail/used-free-interrupt-2.rs21
-rw-r--r--tests/cfail/used-free-interrupt.rs22
-rw-r--r--tests/compiletest.rs57
-rw-r--r--tests/cpass/late-resource.rs20
-rw-r--r--tests/cpass/peripheral.rs18
-rw-r--r--tests/multi.rs16
-rw-r--r--tests/single.rs17
-rw-r--r--ui/single/exception-invalid.rs7
-rw-r--r--ui/single/exception-invalid.stderr8
-rw-r--r--ui/single/exception-systick-used.rs10
-rw-r--r--ui/single/exception-systick-used.stderr8
-rw-r--r--ui/single/extern-interrupt-not-enough.rs7
-rw-r--r--ui/single/extern-interrupt-not-enough.stderr8
-rw-r--r--ui/single/extern-interrupt-used.rs11
-rw-r--r--ui/single/extern-interrupt-used.stderr8
-rw-r--r--ui/single/locals-cfg.rs (renamed from tests/cfail/cfg-static.rs)19
-rw-r--r--ui/single/locals-cfg.stderr33
-rw-r--r--ui/single/resources-cfg.rs (renamed from tests/cfail/cfg-resources.rs)39
-rw-r--r--ui/single/resources-cfg.stderr123
-rw-r--r--ui/single/task-priority-too-high.rs38
-rw-r--r--ui/single/task-priority-too-high.stderr9
127 files changed, 4056 insertions, 5832 deletions
diff --git a/Cargo.toml b/Cargo.toml
index ef6ac654..81ca256c 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 90007723..fea846b6 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 2292d474..a6485cf7 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
- cargo check --target $T
- if [ $TARGET != thumbv6m-none-eabi ]; then
- cargo check --features "timer-queue" --target $T
- fi
+ popd
- if [ $TRAVIS_RUST_VERSION != nightly ]; then
- rm -f .cargo/config
- if [ $TARGET != thumbv6m-none-eabi ]; then
- cargo doc --features timer-queue
- else
+ 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
- ( 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
+ cargo check --target $T
+ ( 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"
- fi
+ 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"
- if [ $TARGET != thumbv6m-none-eabi ]; then
- arm_example "run" $ex "debug" "timer-queue" "1"
- arm_example "run" $ex "release" "timer-queue" "1"
+ continue
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 d743107d..cc9b412c 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 3d2d9b54..1959d759 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 07edd9b8..e1a835ca 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 03f9dbdc..b1f65cfd 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 e624da39..a35ba237 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 d10cc43e..c6f676b0 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 df687794..361db4b7 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 dd6efa0d..3fe8ff35 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 0074fb32..4d48a6a2 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 814c7364..b7d36b41 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 1fd3b9d4..8bfed523 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 c1b6bcdd..f240e511 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 bc714065..6b499111 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 f7841183..b8910db2 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 0b594b19..db321b53 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 4b0d69c7..c38635ff 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 06bdf395..82689504 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 eaafb4c9..1cf2b1ea 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 0fb9191c..1640ca9b 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 c1537168..e4228061 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 2e3b5b41..eeb522f5 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
index 897e083a..b4693a47 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
index a0b6a870..158eef55 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
index 0f690967..55a053df 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<NotSend> = 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
index 4e92a032..40dc2a65 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<u32> = c.resources.S1;
+ let _: &mut u32 = c.resources.S1;
// no `Mutex` proxy when co-owned by cooperative (same priority) tasks
- let _: Exclusive<u32> = 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<u32> = c.resources.S2;
+ let _: &mut u32 = c.resources.S2;
}
};
diff --git a/tests/cpass/schedule.rs b/examples/t-schedule.rs
index 346f9124..67ff358b 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 rtfm::Instant;
+use panic_halt as _;
+use rtfm::cyccnt::{Instant, U32Ext as _};
-#[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
index 0a27c4f6..6bb9b31f 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 5bb32acb..43f7e569 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 c3dd89ca..2e72f0a0 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<u32> = 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 df20f8c0..2854dad4 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 a47be779..e3ed7781 100644
--- a/macros/src/analyze.rs
+++ b/macros/src/analyze.rs
@@ -1,265 +1,59 @@
-use std::{
- cmp,
- collections::{BTreeMap, HashMap, HashSet},
-};
-
-use syn::{Attribute, Ident, Type};
+use core::ops;
+use std::collections::{BTreeMap, BTreeSet};
-use crate::syntax::{App, Idents};
-
-pub type Ownerships = HashMap<Ident, Ownership>;
+use rtfm_syntax::{
+ analyze::{self, Priority},
+ ast::App,
+ Core, P,
+};
+use syn::Ident;
+/// 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<Ident, u8>,
- pub resources_assert_send: HashSet<Box<Type>>,
- pub tasks_assert_send: HashSet<Ident>,
- /// Types of RO resources that need to be Sync
- pub assert_sync: HashSet<Box<Type>>,
- // Resource ownership
- pub ownerships: Ownerships,
- // Ceilings of ready queues
- pub ready_queues: HashMap<u8, u8>,
- pub timer_queue: TimerQueue,
+ parent: P<analyze::Analysis>,
+ pub interrupts: BTreeMap<Core, BTreeMap<Priority, Ident>>,
}
-#[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 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
- }
- }
- }
+impl ops::Deref for Analysis {
+ type Target = analyze::Analysis;
- 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<Attribute>,
- pub interrupt: Ident,
- /// Tasks dispatched at this priority level
- pub tasks: Vec<Ident>,
- // Queue capacity
- pub capacity: u8,
-}
-
-/// Priority -> Dispatcher
-pub type Dispatchers = BTreeMap<u8, Dispatcher>;
-
-pub type Capacities = HashMap<Ident, u8>;
-
-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<analyze::Analysis>, app: &App) -> P<Analysis> {
+ 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 };
- }
- _ => {}
- }
-
- 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;
- }
- }
-
- // 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::<Vec<_>>();
- 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<T> = 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,
+ })
+ .chain(analysis.timer_queues.get(&core).map(|tq| tq.priority))
+ .collect::<BTreeSet<_>>();
+
+ if !priorities.is_empty() {
+ interrupts.insert(
+ core,
+ priorities
+ .iter()
+ .cloned()
+ .rev()
+ .zip(app.extern_interrupts[&core].keys().cloned())
+ .collect(),
+ );
+ }
+ }
+
+ P::new(Analysis {
+ parent: analysis,
+ interrupts,
+ })
}
diff --git a/macros/src/check.rs b/macros/src/check.rs
index 8ad13f3c..c22a0f1f 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 crate::syntax::App;
-
-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<dyn Iterator<Item = _>> { 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))
+use rtfm_syntax::{
+ analyze::Analysis,
+ ast::{App, CustomArg, HardwareTaskKind},
+};
+use syn::{parse, Path};
+
+pub struct Extra<'a> {
+ pub device: &'a Path,
+ pub monotonic: Option<&'a Path>,
+ pub peripherals: Option<u8>,
+}
+
+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<Extra<'a>> {
+ // 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`",
- ));
+ // 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())
+ {
+ 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",
+ ));
+ }
+
+ _ => {}
}
}
- 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 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::<HashSet<_>>();
+
+ 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));
}
}
- // Check that all referenced tasks have been declared
- for task in app
- .idle
- .as_ref()
- .map(|idle| -> Box<dyn Iterator<Item = _>> {
- 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)),
- )
- {
- if !app.tasks.contains_key(task) {
- return Err(parse::Error::new(
- task.span(),
- "this task has NOT been declared",
- ));
+ 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"));
+ }
}
}
- // Check that there are enough free interrupts to dispatch all tasks
- let ndispatchers = app
- .tasks
- .values()
- .map(|t| t.args.priority)
- .collect::<HashSet<_>>()
- .len();
- if ndispatchers > app.free_interrupts.len() {
+ 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 88f11739..86b4a67e 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 crate::{
- analyze::{Analysis, Ownership},
- syntax::{App, Static},
-};
-
-pub fn app(name: &Ident, app: &App, analysis: &Analysis) -> TokenStream {
- let (const_app_resources, mod_resources) = resources(app, analysis);
-
- let (
- const_app_exceptions,
- exception_mods,
- exception_locals,
- exception_resources,
- user_exceptions,
- ) = exceptions(app, analysis);
-
- let (
- const_app_interrupts,
- interrupt_mods,
- interrupt_locals,
- interrupt_resources,
- user_interrupts,
- ) = interrupts(app, analysis);
-
- let (const_app_tasks, task_mods, task_locals, task_resources, user_tasks) =
- tasks(app, analysis);
-
- let const_app_dispatchers = dispatchers(&app, analysis);
-
- let const_app_spawn = spawn(app, analysis);
-
- let const_app_tq = timer_queue(app, analysis);
-
- let const_app_schedule = schedule(app);
-
- let assertion_stmts = assertions(app, analysis);
-
- let pre_init_stmts = pre_init(&app, analysis);
-
- let (
- const_app_init,
- mod_init,
- init_locals,
- init_resources,
- init_late_resources,
- user_init,
- call_init,
- ) = init(app, analysis);
-
- 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)*
+use rtfm_syntax::ast::App;
+
+use crate::{analyze::Analysis, check::Extra};
+
+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;
+
+// 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![];
- #(#exception_resources)*
+ // generate a `main` function for each core
+ for core in 0..app.args.cores {
+ let assertion_stmts = assertions::codegen(core, analysis);
- #(#exception_mods)*
+ let (const_app_pre_init, pre_init_stmts) = pre_init::codegen(core, &app, analysis, extra);
- #(#interrupt_locals)*
+ let (const_app_init, root_init, user_init, call_init) =
+ init::codegen(core, app, analysis, extra);
- #(#interrupt_resources)*
+ let (const_app_post_init, post_init_stmts) = post_init::codegen(core, analysis, extra);
- #(#interrupt_mods)*
+ let (const_app_idle, root_idle, user_idle, call_idle) =
+ idle::codegen(core, app, analysis, extra);
- #(#task_locals)*
+ user.push(quote!(
+ #user_init
- #(#task_resources)*
+ #user_idle
+ ));
- #(#task_mods)*
+ root.push(quote!(
+ #(#root_init)*
- /// Implementation details
- const #name: () = {
- // always include the device crate, which contains the vector table
- use #device as _;
+ #(#root_idle)*
+ ));
- #(#const_app_resources)*
+ const_app.push(quote!(
+ #(#const_app_pre_init)*
#const_app_init
- #const_app_idle
-
- #(#const_app_exceptions)*
-
- #(#const_app_interrupts)*
-
- #(#const_app_dispatchers)*
-
- #(#const_app_tasks)*
-
- #(#const_app_spawn)*
-
- #(#const_app_tq)*
+ #(#const_app_post_init)*
- #(#const_app_schedule)*
+ #const_app_idle
+ ));
+ 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<UserDefinedStruct> = 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<proc_macro2::TokenStream>,
- // 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<proc_macro2::TokenStream>,
- // exception_mods
- Vec<proc_macro2::TokenStream>,
- // exception_locals
- Vec<proc_macro2::TokenStream>,
- // exception_resources
- Vec<proc_macro2::TokenStream>,
- // user_exceptions
- Vec<proc_macro2::TokenStream>,
-) {
- 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,
- )
-}
-
-// 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<proc_macro2::TokenStream>,
- // interrupt_mods
- Vec<proc_macro2::TokenStream>,
- // interrupt_locals
- Vec<proc_macro2::TokenStream>,
- // interrupt_resources
- Vec<proc_macro2::TokenStream>,
- // user_exceptions
- Vec<proc_macro2::TokenStream>,
-) {
- 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 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_instant
-
- // check that this interrupt exists
- let _ = #device::Interrupt::#symbol;
-
- rtfm::export::run(PRIORITY, || {
- crate::#name(
- #name::Locals::new(),
- #name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant)
- )
- });
- }
- ));
-
- let mut needs_lt = false;
- if !interrupt.args.resources.is_empty() {
- let (item, constructor) = resources_struct(
- Kind::Interrupt(name.clone()),
- interrupt.args.priority,
- &mut needs_lt,
- app,
- analysis,
- );
-
- resources_structs.push(item);
-
- const_app.push(constructor);
- }
-
- mods.push(module(
- Kind::Interrupt(name.clone()),
- (!interrupt.args.resources.is_empty(), needs_lt),
- !interrupt.args.schedule.is_empty(),
- !interrupt.args.spawn.is_empty(),
- false,
- app,
- ));
-
- let attrs = &interrupt.attrs;
- let context = &interrupt.context;
- let use_u32ext = if cfg!(feature = "timer-queue") {
- Some(quote!(
- use rtfm::U32Ext as _;
- ))
- } else {
- None
- };
- let (locals, lets) = locals(Kind::Interrupt(name.clone()), &interrupt.statics);
- locals_structs.push(locals);
- let stmts = &interrupt.stmts;
- user_code.push(quote!(
- #(#attrs)*
- #[allow(non_snake_case)]
- fn #name(__locals: #name::Locals, #context: #name::Context) {
- #use_u32ext
- use rtfm::Mutex as _;
-
- #(#lets;)*
-
- #(#stmts)*
- }
- ));
- }
-
- (
- const_app,
- mods,
- locals_structs,
- resources_structs,
- user_code,
- )
-}
-
-// For each task we'll generate:
-//
-// - at the root of the crate:
-// - a ${name}Resources struct (maybe)
-// - a ${name}Locals struct
-//
-// - a module named after the task, see the `module` function for more details
-//
-// - hidden in `const APP`
-// - the ${name}Resources constructor
-// - an INPUTS buffer
-// - a free queue and a corresponding resource
-// - an INSTANTS buffer (if `timer-queue` is enabled)
-//
-// - the task handler specified by the user
-fn tasks(
- app: &App,
- analysis: &Analysis,
-) -> (
- // const_app
- Vec<proc_macro2::TokenStream>,
- // task_mods
- Vec<proc_macro2::TokenStream>,
- // task_locals
- Vec<proc_macro2::TokenStream>,
- // task_resources
- Vec<proc_macro2::TokenStream>,
- // user_tasks
- Vec<proc_macro2::TokenStream>,
-) {
- 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::<Vec<_>>();
-
- 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<rtfm::Instant>; #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<proc_macro2::TokenStream> {
- 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::<Vec<_>>();
-
- 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::<Vec<_>>();
-
- 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<proc_macro2::TokenStream> {
- 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<proc_macro2::TokenStream> {
- 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::<Vec<_>>();
-
- 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<T, #cap>);
- 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::<Vec<_>>();
-
- 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<proc_macro2::TokenStream> {
- 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<proc_macro2::TokenStream> {
- 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<proc_macro2::TokenStream> {
- 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<proc_macro2::TokenStream>,
- // mod_init
- proc_macro2::TokenStream,
- // init_locals
- proc_macro2::TokenStream,
- // init_resources
- Option<proc_macro2::TokenStream>,
- // init_late_resources
- Option<proc_macro2::TokenStream>,
- // 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::<Vec<_>>();
-
- 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<proc_macro2::TokenStream> {
- 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),
- );));
- }
+ let (const_app_resources, mod_resources) = resources::codegen(app, analysis, extra);
- // set the system timer priority
- if !analysis.timer_queue.tasks.is_empty() {
- let priority = analysis.timer_queue.priority;
+ let (const_app_hardware_tasks, root_hardware_tasks, user_hardware_tasks) =
+ hardware_tasks::codegen(app, analysis, extra);
- // compile time assert that this priority is supported by the device
- stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];));
+ let (const_app_software_tasks, root_software_tasks, user_software_tasks) =
+ software_tasks::codegen(app, analysis, extra);
- stmts.push(quote!(core.SCB.set_priority(
- rtfm::export::SystemHandler::SysTick,
- rtfm::export::logical2hw(#priority, #nvic_prio_bits),
- );));
- }
+ let const_app_dispatchers = dispatchers::codegen(app, analysis, extra);
- 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);));
- }
+ let const_app_spawn = spawn::codegen(app, analysis, extra);
- // 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<proc_macro2::TokenStream>,
- // mod_idle
- Option<proc_macro2::TokenStream>,
- // idle_locals
- Option<proc_macro2::TokenStream>,
- // idle_resources
- Option<proc_macro2::TokenStream>,
- // user_idle
- Option<proc_macro2::TokenStream>,
- // 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 const_app_timer_queue = timer_queue::codegen(app, analysis, extra);
- 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,
- );
+ let const_app_schedule = schedule::codegen(app, extra);
- (
- 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;
-
- #[inline(always)]
- fn lock<R>(&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,
- )
- }
- }
- }
- )
-}
-
-/// 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<Ident, Static>,
-) -> (
- // locals
- proc_macro2::TokenStream,
- // lets
- Vec<proc_macro2::TokenStream>,
-) {
- 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));
- }
+ #(#user)*
- let locals = quote!(
- #[allow(non_snake_case)]
- #[doc(hidden)]
- pub struct #ident<#lt> {
- #(#fields),*
- }
+ #(#user_hardware_tasks)*
- impl<#lt> #ident<#lt> {
- #[inline(always)]
- unsafe fn new() -> Self {
- #(#items;)*
+ #(#user_software_tasks)*
- #ident {
- #(#values),*
- }
- }
- }
- );
-
- (locals, lets)
-}
+ #(#root)*
-/// 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![];
+ #(#mod_resources)*
- let name = kind.ident();
+ #(#root_hardware_tasks)*
- 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
- ));
+ #(#root_software_tasks)*
- 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));
+ /// 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 _;
- needs_instant = true;
- }
- }
+ #(#const_app)*
- Kind::Task(_) => {
- if cfg!(feature = "timer-queue") {
- fields.push(quote!(
- /// The time at which this task was scheduled to run
- pub scheduled: rtfm::Instant
- ));
+ #(#const_app_resources)*
- values.push(quote!(scheduled: instant));
+ #(#const_app_hardware_tasks)*
- needs_instant = true;
- }
- }
- }
+ #(#const_app_software_tasks)*
- let ident = kind.locals_ident();
- items.push(quote!(
- #[doc(inline)]
- pub use super::#ident as Locals;
- ));
+ #(#const_app_dispatchers)*
- if resources.0 {
- let ident = kind.resources_ident();
- let lt = if resources.1 {
- lt = Some(quote!('a));
- Some(quote!('a))
- } else {
- None
- };
+ #(#const_app_spawn)*
- items.push(quote!(
- #[doc(inline)]
- pub use super::#ident as Resources;
- ));
+ #(#const_app_timer_queue)*
- fields.push(quote!(
- /// Resources this task has access to
- pub resources: Resources<#lt>
- ));
+ #(#const_app_schedule)*
- let priority = if kind.is_init() {
- None
- } else {
- Some(quote!(priority))
+ #(#mains)*
};
- 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<proc_macro2::TokenStream>,
- // tupled e.g. `_0`, `(_0, _1)`
- proc_macro2::TokenStream,
- // untupled e.g. &[`_0`], &[`_0`, `_1`]
- Vec<proc_macro2::TokenStream>,
- // 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 00000000..95268a2c
--- /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<TokenStream2> {
+ 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 00000000..65d25c78
--- /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<TokenStream2> {
+ 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::<Vec<_>>();
+
+ 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::<Vec<_>>();
+
+ 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 00000000..e65bad56
--- /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<TokenStream2>,
+ // root_hardware_tasks -- items that must be placed in the root of the crate:
+ // - `${task}Locals` structs
+ // - `${task}Resources` structs
+ // - `${task}` modules
+ Vec<TokenStream2>,
+ // user_hardware_tasks -- the `#[task]` functions written by the user
+ Vec<TokenStream2>,
+) {
+ 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 00000000..7af01c91
--- /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<TokenStream2>,
+ // 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<TokenStream2>,
+ // user_idle
+ Option<TokenStream2>,
+ // 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 00000000..271be94c
--- /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<TokenStream2>,
+ // 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<TokenStream2>,
+ // user_init -- the `#[init]` function written by the user
+ Option<TokenStream2>,
+ // call_init -- the call to the user `#[init]` if there's one
+ Option<TokenStream2>,
+) {
+ 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::<Vec<_>>()
+ })
+ .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 00000000..96635637
--- /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<Local>,
+ 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 00000000..5f077a22
--- /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 00000000..f492d31d
--- /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<TokenStream2>, Vec<TokenStream2>) {
+ 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 00000000..3ba17dcf
--- /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<TokenStream2>,
+ // `pre_init_stmts`
+ Vec<TokenStream2>,
+) {
+ 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 00000000..2dd10eac
--- /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<TokenStream2>,
+ // 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 00000000..0248f199
--- /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 00000000..57f01a2c
--- /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<TokenStream2> {
+ let mut items = vec![];
+
+ let mut seen = BTreeMap::<u8, HashSet<_>>::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 00000000..208fd0b7
--- /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 00000000..8b2c0cd5
--- /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<TokenStream2>,
+ // root_software_tasks -- items that must be placed in the root of the crate:
+ // - `${task}Locals` structs
+ // - `${task}Resources` structs
+ // - `${task}` modules
+ Vec<TokenStream2>,
+ // user_software_tasks -- the `#[task]` functions written by the user
+ Vec<TokenStream2>,
+) {
+ 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::<Vec<_>>();
+
+ 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 00000000..1539e277
--- /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<TokenStream2> {
+ let mut items = vec![];
+
+ let mut seen = BTreeMap::<u8, HashSet<_>>::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 00000000..83cb5c0a
--- /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 00000000..cb845774
--- /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<TokenStream2> {
+ 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::<Vec<_>>();
+
+ 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::<Vec<_>>();
+
+ 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 00000000..203fcee8
--- /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<TokenStream2> {
+ 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<R>(&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<TokenStream2>,
+ // tupled e.g. `_0`, `(_0, _1)`
+ TokenStream2,
+ // untupled e.g. &[`_0`], &[`_0`, `_1`]
+ Vec<TokenStream2>,
+ // 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 736289cb..6e1a7978 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 = <path>`. 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<Data = u32>` 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: <device-path>::Peripherals`. Exclusive access to device-specific peripherals.
-/// `<device-path>` 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 = <integer>`. This is the static priority of the exception handler. The value must
-/// be in the range `1..=(1 << <device-path>::NVIC_PRIO_BITS)` where `<device-path>` 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: <exception-name>::Schedule`. Same meaning / function as [`init.schedule`](#a-init).
-///
-/// - `spawn: <exception-name>::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: <interrupt-name>::Schedule`. Same meaning / function as [`init.schedule`](#a-init).
-///
-/// - `spawn: <interrupt-name>::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(<inputs>)`.
-///
-/// The `task` attribute accepts the following optional arguments.
-///
-/// - `capacity = <integer>`. 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 = <integer>`. 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: <interrupt-name>::Schedule`. Same meaning / function as [`init.schedule`](#a-init).
-///
-/// - `spawn: <interrupt-name>::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 c6814d5f..00000000
--- 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<Self> {
- 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<Item>,
- _semi_token: Token![;],
-}
-
-impl Parse for Input {
- fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
- fn parse_items(input: ParseStream<'_>) -> parse::Result<Vec<Item>> {
- 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<Idle>,
- 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<Item>, args: AppArgs) -> parse::Result<Self> {
- 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<Item = (u8, &Ident)> {
- self.idle
- .as_ref()
- .map(|idle| -> Box<dyn Iterator<Item = _>> {
- 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<Item = (Option<u8>, &Ident)> {
- self.init
- .args
- .spawn
- .iter()
- .map(|s| (None, s))
- .chain(
- self.idle
- .as_ref()
- .map(|idle| -> Box<dyn Iterator<Item = _>> {
- 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<Item = (Option<u8>, &Ident)> {
- self.init
- .args
- .schedule
- .iter()
- .map(|s| (None, s))
- .chain(
- self.idle
- .as_ref()
- .map(|idle| -> Box<dyn Iterator<Item = _>> {
- 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<Item = (Ident, &Idents)> {
- self.idle
- .as_ref()
- .map(|idle| -> Box<dyn Iterator<Item = _>> {
- 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<Item = (Ident, &Idents)> {
- self.idle
- .as_ref()
- .map(|idle| -> Box<dyn Iterator<Item = _>> {
- 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<Ident>;
-
-pub type Exceptions = BTreeMap<Ident, Exception>;
-
-pub type Interrupts = BTreeMap<Ident, Interrupt>;
-
-pub type Resources = BTreeMap<Ident, Resource>;
-
-pub type Statics = Vec<ItemStatic>;
-
-pub type Tasks = BTreeMap<Ident, Task>;
-
-pub type FreeInterrupts = BTreeMap<Ident, FreeInterrupt>;
-
-pub struct Idle {
- pub args: IdleArgs,
- pub attrs: Vec<Attribute>,
- pub context: Pat,
- pub statics: BTreeMap<Ident, Static>,
- pub stmts: Vec<Stmt>,
-}
-
-pub type IdleArgs = InitArgs;
-
-impl Idle {
- fn check(args: IdleArgs, item: ItemFn) -> parse::Result<Self> {
- 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<InitArgs> {
- 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<Attribute>,
- pub statics: BTreeMap<Ident, Static>,
- pub context: Pat,
- pub stmts: Vec<Stmt>,
- pub returns_late_resources: bool,
- pub span: Span,
-}
-
-impl Init {
- fn check(args: InitArgs, item: ItemFn) -> parse::Result<Self> {
- 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<Ident>,
- pub capacity: Option<u8>,
- 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<Attribute>,
- pub statics: BTreeMap<Ident, Static>,
- pub context: Pat,
- pub stmts: Vec<Stmt>,
-}
-
-pub struct ExceptionArgs {
- binds: Option<Ident>,
- 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<Self> {
- 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<Self> {
- 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<Attribute>,
- pub statics: BTreeMap<Ident, Static>,
- pub context: Pat,
- pub stmts: Vec<Stmt>,
-}
-
-pub type InterruptArgs = ExceptionArgs;
-
-impl Interrupt {
- fn check(args: InterruptArgs, item: ItemFn) -> parse::Result<Self> {
- 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<Attribute>,
- pub attrs: Vec<Attribute>,
- pub mutability: Option<Token![mut]>,
- pub ty: Box<Type>,
- pub expr: Option<Box<Expr>>,
-}
-
-impl Resource {
- fn check(item: ItemStatic) -> parse::Result<Resource> {
- 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<u8>,
- pub priority: u8,
- pub resources: Idents,
- pub spawn: Idents,
- pub schedule: Idents,
-}
-
-impl Parse for TaskArgs {
- fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
- 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<Args> {
- 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<Attribute>,
- /// Attributes that are not `#[cfg]`
- pub attrs: Vec<Attribute>,
- pub ty: Box<Type>,
- pub expr: Box<Expr>,
-}
-
-impl Static {
- fn parse(items: Vec<ItemStatic>) -> parse::Result<BTreeMap<Ident, Static>> {
- 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<Attribute>,
- pub attrs: Vec<Attribute>,
- pub inputs: Vec<ArgCaptured>,
- pub context: Pat,
- pub statics: BTreeMap<Ident, Static>,
- pub stmts: Vec<Stmt>,
-}
-
-impl Task {
- fn check(args: TaskArgs, item: ItemFn) -> parse::Result<Self> {
- 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<Attribute>,
-}
-
-impl FreeInterrupt {
- fn parse(mod_: ItemForeignMod) -> parse::Result<FreeInterrupts> {
- 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<Attribute>) -> (Vec<Attribute>, Vec<Attribute>) {
- 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<Stmt>) -> (Statics, Vec<Stmt>) {
- 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<FnArg, Token![,]>,
- name: &str,
-) -> Option<(Pat, Result<Vec<ArgCaptured>, 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::<Result<Vec<_>, _>>();
-
- 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 00000000..470c9058
--- /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 00000000..37fef53f
--- /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 00000000..fb2d430f
--- /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 00000000..7c75335d
--- /dev/null
+++ b/mc/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+authors = ["Jorge Aparicio <jorge@japaric.io>"]
+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 00000000..e1335bbf
--- /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 00000000..792935a8
--- /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 00000000..ff48b110
--- /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 00000000..3f26c5c9
--- /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 00000000..76e70acf
--- /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 00000000..749918fd
--- /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 00000000..d86c0e8e
--- /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<u32> 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<Ordering> {
+ 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 00000000..a2b216c1
--- /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<Duration> 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<Duration> for Instant {
+ type Output = Self;
+
+ fn add(mut self, dur: Duration) -> Self {
+ self += dur;
+ self
+ }
+}
+
+impl ops::SubAssign<Duration> 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<Duration> for Instant {
+ type Output = Self;
+
+ fn sub(mut self, dur: Duration) -> Self {
+ self -= dur;
+ self
+ }
+}
+
+impl ops::Sub<Instant> 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<Ordering> {
+ 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<u32> for Duration {
+ type Error = Infallible;
+
+ fn try_into(self) -> Result<u32, Infallible> {
+ Ok(self.as_cycles())
+ }
+}
+
+impl ops::AddAssign for Duration {
+ fn add_assign(&mut self, dur: Duration) {
+ self.inner += dur.inner;
+ }
+}
+
+impl ops::Add<Duration> 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<Duration> 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 afed9091..7646e3c5 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};
+use core::{
+ cell::Cell,
+ sync::atomic::{AtomicBool, Ordering},
+};
+pub use crate::tq::{NotReady, TimerQueue};
#[cfg(armv7m)]
-use cortex_m::register::basepri;
+pub use cortex_m::register::basepri;
pub use cortex_m::{
- asm::wfi, interrupt, peripheral::scb::SystemHandler, peripheral::syst::SystClkSource,
- peripheral::Peripherals,
+ asm::wfi,
+ interrupt,
+ peripheral::{scb::SystemHandler, syst::SystClkSource, DWT},
+ Peripherals,
};
-use heapless::spsc::SingleCore;
-pub use heapless::{consts, i, spsc::Queue};
-
-#[cfg(feature = "timer-queue")]
-pub use crate::tq::{NotReady, TimerQueue};
+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<N> = Queue<u8, N, u8, SingleCore>;
-pub type ReadyQueue<T, N> = Queue<(T, u8), N, u8, SingleCore>;
+pub type MCFQ<N> = Queue<u8, N, u8, MultiCore>;
+pub type MCRQ<T, N> = Queue<(T, u8), N, u8, MultiCore>;
+pub type SCFQ<N> = Queue<u8, N, u8, SingleCore>;
+pub type SCRQ<T, N> = 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<u8>,
@@ -95,7 +121,7 @@ pub unsafe fn lock<T, R>(
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<T, R>(
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 1fe88c47..73e6e200 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,
- /// System Control Block
- pub SCB: &'a mut SCB,
+ /// Nested Vector Interrupt Controller
+ pub NVIC: NVIC,
- /// SysTick: System Timer (not present if the `timer-queue` is enabled)
- #[cfg(not(feature = "timer-queue"))]
- pub SYST: SYST,
+ /// System Control Block
+ 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)
- }
-}
-
-#[cfg(feature = "timer-queue")]
-impl ops::AddAssign<Duration> 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);
+impl From<cortex_m::Peripherals> 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::Add<Duration> for Instant {
- type Output = Self;
+/// A monotonic clock / counter
+pub unsafe trait Monotonic {
+ /// A measurement of this clock
+ type Instant: Copy + Ord + Sub;
- fn add(mut self, dur: Duration) -> Self {
- self += dur;
- self
- }
-}
+ /// The ratio between the SysTick (system timer) frequency and this clock frequency
+ fn ratio() -> u32;
-#[cfg(feature = "timer-queue")]
-impl ops::SubAssign<Duration> 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);
- }
-}
+ /// Returns the current time
+ fn now() -> Self::Instant;
-#[cfg(feature = "timer-queue")]
-impl ops::Sub<Duration> for Instant {
- type Output = Self;
+ /// Resets the counter to *zero*
+ unsafe fn reset();
- fn sub(mut self, dur: Duration) -> Self {
- self -= dur;
- self
- }
-}
-
-#[cfg(feature = "timer-queue")]
-impl ops::Sub<Instant> 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<Ordering> {
- 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<Duration> 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<Duration> 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<R>(&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<R>(&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<R>(&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 8ca1bd3f..4f9b6e7e 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<T, N>
+pub struct TimerQueue<M, T, N>(pub BinaryHeap<NotReady<M, T>, N, Min>)
where
- N: ArrayLength<NotReady<T>>,
- T: Copy,
-{
- pub syst: SYST,
- pub queue: BinaryHeap<NotReady<T>, N, Min>,
-}
+ M: Monotonic,
+ <M::Instant as Sub>::Output: TryInto<u32>,
+ N: ArrayLength<NotReady<M, T>>,
+ T: Copy;
-impl<T, N> TimerQueue<T, N>
+impl<M, T, N> TimerQueue<M, T, N>
where
- N: ArrayLength<NotReady<T>>,
+ M: Monotonic,
+ <M::Instant as Sub>::Output: TryInto<u32>,
+ N: ArrayLength<NotReady<M, T>>,
T: Copy,
{
- pub fn new(syst: SYST) -> Self {
- TimerQueue {
- syst,
- queue: BinaryHeap::new(),
- }
- }
-
#[inline]
- pub unsafe fn enqueue_unchecked(&mut self, nr: NotReady<T>) {
+ pub unsafe fn enqueue_unchecked(&mut self, nr: NotReady<M, T>) {
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);
-
- if diff < 0 {
- // task became ready
- let nr = unsafe { self.queue.pop_unchecked() };
-
- Some((nr.task, nr.index))
+ unsafe {
+ if let Some(instant) = self.0.peek().map(|p| p.instant) {
+ let now = M::now();
+
+ if instant < now {
+ // task became ready
+ let nr = self.0.pop_unchecked();
+
+ 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<T>
+pub struct NotReady<M, T>
where
T: Copy,
+ M: Monotonic,
+ <M::Instant as Sub>::Output: TryInto<u32>,
{
pub index: u8,
- pub instant: Instant,
+ pub instant: M::Instant,
pub task: T,
}
-impl<T> Eq for NotReady<T> where T: Copy {}
+impl<M, T> Eq for NotReady<M, T>
+where
+ T: Copy,
+ M: Monotonic,
+ <M::Instant as Sub>::Output: TryInto<u32>,
+{
+}
-impl<T> Ord for NotReady<T>
+impl<M, T> Ord for NotReady<M, T>
where
T: Copy,
+ M: Monotonic,
+ <M::Instant as Sub>::Output: TryInto<u32>,
{
fn cmp(&self, other: &Self) -> Ordering {
self.instant.cmp(&other.instant)
}
}
-impl<T> PartialEq for NotReady<T>
+impl<M, T> PartialEq for NotReady<M, T>
where
T: Copy,
+ M: Monotonic,
+ <M::Instant as Sub>::Output: TryInto<u32>,
{
fn eq(&self, other: &Self) -> bool {
self.instant == other.instant
}
}
-impl<T> PartialOrd for NotReady<T>
+impl<M, T> PartialOrd for NotReady<M, T>
where
T: Copy,
+ M: Monotonic,
+ <M::Instant as Sub>::Output: TryInto<u32>,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
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 5bef79b5..00000000
--- 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 6938cd0d..00000000
--- 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 3fe9a365..00000000
--- 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 d1363fe5..00000000
--- 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 4bb8f1ec..00000000
--- 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 8f672985..00000000
--- 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 d5eae20b..00000000
--- 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 feb83e8b..00000000
--- 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 505fba14..00000000
--- 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 0e779ffc..00000000
--- 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 d2d4a6d7..00000000
--- 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 9063efe3..00000000
--- 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 cec18bab..00000000
--- 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 5a33fac7..00000000
--- 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<NotSend> = 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 f88d5340..00000000
--- 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 7148fbf3..00000000
--- 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 b67601ee..00000000
--- 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 f11b2d39..00000000
--- 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 69e4957f..00000000
--- 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 00d6c8ce..00000000
--- 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 04a4af15..00000000
--- 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 8dc9707f..00000000
--- 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 6025e7d5..00000000
--- 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 817462a3..00000000
--- 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 361156df..00000000
--- 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 a37be42d..00000000
--- 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 3fb21f46..00000000
--- 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 577f0e06..00000000
--- 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 963bf1ee..00000000
--- 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 04309f59..00000000
--- 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 353194a5..00000000
--- 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 fab1b0f1..00000000
--- 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 d8bb5605..00000000
--- 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 93225edf..00000000
--- 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 58c4d70c..00000000
--- 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 ba9424fd..00000000
--- 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 1a56741b..00000000
--- 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 58702eec..00000000
--- 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 37dcf331..00000000
--- 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 34352b84..00000000
--- 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 00000000..675fc5e9
--- /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 00000000..93addf6e
--- /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 00000000..426cb673
--- /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 00000000..f7fc2922
--- /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 00000000..d30da1bd
--- /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 00000000..47786c6c
--- /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 00000000..39c5d8ea
--- /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 00000000..43249c49
--- /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 00000000..25f34b36
--- /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 00000000..8707b1d2
--- /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
index 91465a1e..bcce5ca9 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 00000000..fc324f13
--- /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
index 5e20c4de..f8c36729 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 00000000..88c34d20
--- /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 00000000..c7c9dc9b
--- /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 00000000..b402a95c
--- /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`.