151: make builds reproducible r=japaric a=japaric

This is a rebased and augmented version of #132. With this PR both dev and release builds that do not use the owned-singleton stuff become reproducible. (I haven't really bothered to make owned-singleton reproducible since [lifo] is way more ergonomic than [alloc-singleton] and will eventually make its way into heapless).

[lifo]: https://github.com/japaric/lifo
[alloc-singleton]: https://crates.io/crates/alloc-singleton

Thanks @hugwijst for doing the bulk of the work!

closes #132

Co-authored-by: Hugo van der Wijst <hvanderwijst@tesla.com>
Co-authored-by: Hugo van der Wijst <hugo@wij.st>
Co-authored-by: Jorge Aparicio <jorge@japaric.io>
This commit is contained in:
bors[bot] 2019-02-15 23:39:28 +00:00
commit c91b14bcd4
9 changed files with 307 additions and 177 deletions

1
.gitignore vendored
View file

@ -4,3 +4,4 @@
/book/*/book /book/*/book
/target /target
Cargo.lock Cargo.lock
*.hex

View file

@ -23,7 +23,10 @@ matrix:
rust: nightly rust: nightly
if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
before_install: set -e before_install:
- set -e
- sudo apt-get update
- sudo apt-get install -y binutils-arm-none-eabi
install: install:
- bash ci/install.sh - bash ci/install.sh

View file

@ -1,8 +1,41 @@
set -euxo pipefail set -euxo pipefail
arm_example() {
local COMMAND=$1
local EXAMPLE=$2
local BUILD_MODE=$3
local FEATURES=$4
local BUILD_NUM=$5
if [ $BUILD_MODE = "release" ]; then
local RELEASE_FLAG="--release"
else
local RELEASE_FLAG=""
fi
if [ -n "$FEATURES" ]; then
local FEATURES_FLAG="--features $FEATURES"
local FEATURES_STR=${FEATURES/,/_}_
else
local FEATURES_FLAG=""
local FEATURES_STR=""
fi
local CARGO_FLAGS="--example $EXAMPLE --target $TARGET $RELEASE_FLAG $FEATURES_FLAG"
if [ $COMMAND = "run" ]; then
cargo $COMMAND $CARGO_FLAGS | diff -u ci/expected/$EXAMPLE.run -
else
cargo $COMMAND $CARGO_FLAGS
fi
arm-none-eabi-objcopy -O ihex target/$TARGET/$BUILD_MODE/examples/$EXAMPLE ci/builds/${EXAMPLE}_${FEATURES_STR}${BUILD_MODE}_${BUILD_NUM}.hex
}
main() { main() {
local T=$TARGET local T=$TARGET
mkdir -p ci/builds
if [ $T = x86_64-unknown-linux-gnu ]; then if [ $T = x86_64-unknown-linux-gnu ]; then
# compile-fail and compile-pass tests # compile-fail and compile-pass tests
case $TRAVIS_RUST_VERSION in case $TRAVIS_RUST_VERSION in
@ -82,19 +115,41 @@ main() {
fi fi
if [ $ex != types ]; then if [ $ex != types ]; then
cargo run --example $ex --target $T | \ arm_example "run" $ex "debug" "" "1"
diff -u ci/expected/$ex.run - arm_example "run" $ex "release" "" "1"
cargo run --example $ex --target $T --release | \
diff -u ci/expected/$ex.run -
fi fi
if [ $TARGET != thumbv6m-none-eabi ]; then if [ $TARGET != thumbv6m-none-eabi ]; then
cargo run --features timer-queue --example $ex --target $T | \ arm_example "run" $ex "debug" "timer-queue" "1"
diff -u ci/expected/$ex.run - arm_example "run" $ex "release" "timer-queue" "1"
fi
done
cargo run --features timer-queue --example $ex --target $T --release | \ cargo clean
diff -u ci/expected/$ex.run - 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 = singleton ]; then
# singleton build is currently not reproducible due to
# https://github.com/japaric/owned-singleton/issues/2
continue
fi
if [ $ex != types ]; then
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
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 fi
done done
esac esac

View file

@ -1,6 +1,6 @@
use std::{ use std::{
cmp, cmp,
collections::{HashMap, HashSet}, collections::{BTreeMap, HashMap, HashSet},
}; };
use syn::{Attribute, Ident, Type}; use syn::{Attribute, Ident, Type};
@ -65,7 +65,7 @@ pub struct Dispatcher {
} }
/// Priority -> Dispatcher /// Priority -> Dispatcher
pub type Dispatchers = HashMap<u8, Dispatcher>; pub type Dispatchers = BTreeMap<u8, Dispatcher>;
pub type Capacities = HashMap<Ident, u8>; pub type Capacities = HashMap<Ident, u8>;

View file

@ -38,8 +38,8 @@ pub fn app(app: &App) -> parse::Result<()> {
// Check that all late resources have been initialized in `#[init]` if `init` has signature // Check that all late resources have been initialized in `#[init]` if `init` has signature
// `fn()` // `fn()`
if !app.init.returns_late_resources { if !app.init.returns_late_resources {
for res in app for res in
.resources app.resources
.iter() .iter()
.filter_map(|(name, res)| if res.expr.is_none() { Some(name) } else { None }) .filter_map(|(name, res)| if res.expr.is_none() { Some(name) } else { None })
{ {

View file

@ -2,8 +2,7 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use std::{ use std::{
collections::HashMap, collections::{BTreeMap, HashMap},
sync::atomic::{AtomicUsize, Ordering},
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };
@ -20,13 +19,13 @@ use crate::{
// NOTE to avoid polluting the user namespaces we map some identifiers to pseudo-hygienic names. // NOTE to avoid polluting the user namespaces we map some identifiers to pseudo-hygienic names.
// In some instances we also use the pseudo-hygienic names for safety, for example the user should // In some instances we also use the pseudo-hygienic names for safety, for example the user should
// not modify the priority field of resources. // not modify the priority field of resources.
type Aliases = HashMap<Ident, Ident>; type Aliases = BTreeMap<Ident, Ident>;
struct Context { struct Context {
// Alias // Alias
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
baseline: Ident, baseline: Ident,
dispatchers: HashMap<u8, Dispatcher>, dispatchers: BTreeMap<u8, Dispatcher>,
// Alias (`fn`) // Alias (`fn`)
idle: Ident, idle: Ident,
// Alias (`fn`) // Alias (`fn`)
@ -41,9 +40,11 @@ struct Context {
schedule_enum: Ident, schedule_enum: Ident,
// Task -> Alias (`fn`) // Task -> Alias (`fn`)
schedule_fn: Aliases, schedule_fn: Aliases,
tasks: HashMap<Ident, Task>, tasks: BTreeMap<Ident, Task>,
// Alias (`struct` / `static mut`) // Alias (`struct` / `static mut`)
timer_queue: Ident, timer_queue: Ident,
// Generator of Ident names or suffixes
ident_gen: IdentGenerator,
} }
struct Dispatcher { struct Dispatcher {
@ -63,19 +64,22 @@ struct Task {
impl Default for Context { impl Default for Context {
fn default() -> Self { fn default() -> Self {
let mut ident_gen = IdentGenerator::new();
Context { Context {
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
baseline: mk_ident(None), baseline: ident_gen.mk_ident(None, false),
dispatchers: HashMap::new(), dispatchers: BTreeMap::new(),
idle: mk_ident(Some("idle")), idle: ident_gen.mk_ident(Some("idle"), false),
init: mk_ident(Some("init")), init: ident_gen.mk_ident(Some("init"), false),
priority: mk_ident(None), priority: ident_gen.mk_ident(None, false),
statics: Aliases::new(), statics: Aliases::new(),
resources: HashMap::new(), resources: HashMap::new(),
schedule_enum: mk_ident(None), schedule_enum: ident_gen.mk_ident(None, false),
schedule_fn: Aliases::new(), schedule_fn: Aliases::new(),
tasks: HashMap::new(), tasks: BTreeMap::new(),
timer_queue: mk_ident(None), timer_queue: ident_gen.mk_ident(None, false),
ident_gen,
} }
} }
} }
@ -164,13 +168,13 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream {
() => quote!(), () => quote!(),
}; };
let timer_queue = timer_queue(&ctxt, app, analysis); let timer_queue = timer_queue(&mut ctxt, app, analysis);
let pre_init = pre_init(&ctxt, &app, analysis); let pre_init = pre_init(&ctxt, &app, analysis);
let assertions = assertions(app, analysis); let assertions = assertions(app, analysis);
let main = mk_ident(None); let main = ctxt.ident_gen.mk_ident(None, false);
quote!( quote!(
#resources #resources
@ -236,7 +240,7 @@ fn resources(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2:
pub static #mut_ #name: #ty = #expr; pub static #mut_ #name: #ty = #expr;
)); ));
let alias = mk_ident(None); let alias = ctxt.ident_gen.mk_ident(None, true); // XXX is randomness required?
if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) {
items.push(mk_resource( items.push(mk_resource(
ctxt, ctxt,
@ -252,7 +256,7 @@ fn resources(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2:
ctxt.statics.insert(name.clone(), alias); ctxt.statics.insert(name.clone(), alias);
} else { } else {
let alias = mk_ident(None); let alias = ctxt.ident_gen.mk_ident(None, false);
let symbol = format!("{}::{}", name, alias); let symbol = format!("{}::{}", name, alias);
items.push( items.push(
@ -360,7 +364,7 @@ fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> (proc_macro2::Tok
let (late_resources, late_resources_ident, ret) = if app.init.returns_late_resources { let (late_resources, late_resources_ident, ret) = if app.init.returns_late_resources {
// create `LateResources` struct in the root of the crate // create `LateResources` struct in the root of the crate
let ident = mk_ident(None); let ident = ctxt.ident_gen.mk_ident(None, false);
let fields = app let fields = app
.resources .resources
@ -405,7 +409,7 @@ fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> (proc_macro2::Tok
let baseline = &ctxt.baseline; let baseline = &ctxt.baseline;
let baseline_let = match () { let baseline_let = match () {
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
() => quote!(let #baseline = rtfm::Instant::artificial(0);), () => quote!(let ref #baseline = rtfm::Instant::artificial(0);),
#[cfg(not(feature = "timer-queue"))] #[cfg(not(feature = "timer-queue"))]
() => quote!(), () => quote!(),
@ -415,7 +419,7 @@ fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> (proc_macro2::Tok
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
() => quote!( () => quote!(
#[allow(unused_variables)] #[allow(unused_variables)]
let start = #baseline; let start = *#baseline;
), ),
#[cfg(not(feature = "timer-queue"))] #[cfg(not(feature = "timer-queue"))]
() => quote!(), () => quote!(),
@ -430,8 +434,11 @@ fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> (proc_macro2::Tok
#module #module
// unsafe trampoline to deter end-users from calling this non-reentrant function
#(#attrs)* #(#attrs)*
#unsafety fn #init(mut core: rtfm::Peripherals) #ret { unsafe fn #init(core: rtfm::Peripherals) #ret {
#[inline(always)]
#unsafety fn init(mut core: rtfm::Peripherals) #ret {
#(#locals)* #(#locals)*
#baseline_let #baseline_let
@ -446,6 +453,9 @@ fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> (proc_macro2::Tok
#(#assigns)* #(#assigns)*
} }
init(core)
}
), ),
has_late_resources, has_late_resources,
) )
@ -563,7 +573,7 @@ fn module(
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct Schedule<'a> { pub struct Schedule<'a> {
#[doc(hidden)] #[doc(hidden)]
pub #priority: &'a core::cell::Cell<u8>, pub #priority: &'a rtfm::export::Priority,
} }
)); ));
} }
@ -582,7 +592,7 @@ fn module(
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct Spawn<'a> { pub struct Spawn<'a> {
#[doc(hidden)] #[doc(hidden)]
pub #priority: &'a core::cell::Cell<u8>, pub #priority: &'a rtfm::export::Priority,
} }
)); ));
} else { } else {
@ -591,8 +601,10 @@ fn module(
() => { () => {
let baseline = &ctxt.baseline; let baseline = &ctxt.baseline;
quote!( quote!(
// NOTE this field is visible so we use a shared reference to make it
// immutable
#[doc(hidden)] #[doc(hidden)]
pub #baseline: rtfm::Instant, pub #baseline: &'a rtfm::Instant,
) )
} }
#[cfg(not(feature = "timer-queue"))] #[cfg(not(feature = "timer-queue"))]
@ -605,7 +617,7 @@ fn module(
pub struct Spawn<'a> { pub struct Spawn<'a> {
#baseline_field #baseline_field
#[doc(hidden)] #[doc(hidden)]
pub #priority: &'a core::cell::Cell<u8>, pub #priority: &'a rtfm::export::Priority,
} }
)); ));
} }
@ -687,7 +699,7 @@ fn prelude(
let mut exprs = vec![]; let mut exprs = vec![];
// NOTE This field is just to avoid unused type parameter errors around `'a` // NOTE This field is just to avoid unused type parameter errors around `'a`
defs.push(quote!(#[allow(dead_code)] #priority: &'a core::cell::Cell<u8>)); defs.push(quote!(#[allow(dead_code)] pub #priority: &'a rtfm::export::Priority));
exprs.push(parse_quote!(#priority)); exprs.push(parse_quote!(#priority));
let mut may_call_lock = false; let mut may_call_lock = false;
@ -826,7 +838,8 @@ fn prelude(
#(#cfgs)* #(#cfgs)*
pub #name: &'a #mut_ #name pub #name: &'a #mut_ #name
)); ));
let alias = mk_ident(None); // XXX is randomness required?
let alias = ctxt.ident_gen.mk_ident(None, true);
items.push(quote!( items.push(quote!(
#(#cfgs)* #(#cfgs)*
let #mut_ #alias = unsafe { let #mut_ #alias = unsafe {
@ -843,7 +856,8 @@ fn prelude(
#(#cfgs)* #(#cfgs)*
pub #name: rtfm::Exclusive<'a, #name> pub #name: rtfm::Exclusive<'a, #name>
)); ));
let alias = mk_ident(None); // XXX is randomness required?
let alias = ctxt.ident_gen.mk_ident(None, true);
items.push(quote!( items.push(quote!(
#(#cfgs)* #(#cfgs)*
let #mut_ #alias = unsafe { let #mut_ #alias = unsafe {
@ -910,7 +924,7 @@ fn prelude(
} }
} }
let alias = mk_ident(None); let alias = ctxt.ident_gen.mk_ident(None, false);
let unsafety = if needs_unsafe { let unsafety = if needs_unsafe {
Some(quote!(unsafe)) Some(quote!(unsafe))
} else { } else {
@ -971,7 +985,8 @@ fn prelude(
continue; continue;
} }
ctxt.schedule_fn.insert(task.clone(), mk_ident(None)); ctxt.schedule_fn
.insert(task.clone(), ctxt.ident_gen.mk_ident(None, false));
} }
items.push(quote!( items.push(quote!(
@ -987,7 +1002,7 @@ fn prelude(
quote!() quote!()
} else { } else {
quote!( quote!(
let ref #priority = core::cell::Cell::new(#logical_prio); let ref #priority = unsafe { rtfm::export::Priority::new(#logical_prio) };
#(#items)* #(#items)*
) )
@ -1031,14 +1046,20 @@ fn idle(
quote!( quote!(
#module #module
// unsafe trampoline to deter end-users from calling this non-reentrant function
#(#attrs)* #(#attrs)*
#unsafety fn #idle() -> ! { unsafe fn #idle() -> ! {
#[inline(always)]
#unsafety fn idle() -> ! {
#(#locals)* #(#locals)*
#prelude #prelude
#(#stmts)* #(#stmts)*
} }
idle()
}
), ),
quote!(#idle()), quote!(#idle()),
) )
@ -1083,7 +1104,7 @@ fn exceptions(ctxt: &mut Context, app: &App, analysis: &Analysis) -> Vec<proc_ma
let baseline = &ctxt.baseline; let baseline = &ctxt.baseline;
let baseline_let = match () { let baseline_let = match () {
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
() => quote!(let #baseline = rtfm::Instant::now();), () => quote!(let ref #baseline = rtfm::Instant::now();),
#[cfg(not(feature = "timer-queue"))] #[cfg(not(feature = "timer-queue"))]
() => quote!(), () => quote!(),
}; };
@ -1092,7 +1113,7 @@ fn exceptions(ctxt: &mut Context, app: &App, analysis: &Analysis) -> Vec<proc_ma
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
() => quote!( () => quote!(
#[allow(unused_variables)] #[allow(unused_variables)]
let start = #baseline; let start = *#baseline;
), ),
#[cfg(not(feature = "timer-queue"))] #[cfg(not(feature = "timer-queue"))]
() => quote!(), () => quote!(),
@ -1100,15 +1121,17 @@ fn exceptions(ctxt: &mut Context, app: &App, analysis: &Analysis) -> Vec<proc_ma
let locals = mk_locals(&exception.statics, false); let locals = mk_locals(&exception.statics, false);
let symbol = ident.to_string(); let symbol = ident.to_string();
let alias = mk_ident(None); let alias = ctxt.ident_gen.mk_ident(None, false);
let unsafety = &exception.unsafety; let unsafety = &exception.unsafety;
quote!( quote!(
#module #module
#[doc(hidden)] // unsafe trampoline to deter end-users from calling this non-reentrant function
#[export_name = #symbol] #[export_name = #symbol]
#(#attrs)* #(#attrs)*
#unsafety fn #alias() { unsafe fn #alias() {
#[inline(always)]
#unsafety fn exception() {
#(#locals)* #(#locals)*
#baseline_let #baseline_let
@ -1121,6 +1144,9 @@ fn exceptions(ctxt: &mut Context, app: &App, analysis: &Analysis) -> Vec<proc_ma
#(#stmts)* #(#stmts)*
}) })
} }
exception()
}
) )
}) })
.collect() .collect()
@ -1163,7 +1189,7 @@ fn interrupts(
let baseline = &ctxt.baseline; let baseline = &ctxt.baseline;
let baseline_let = match () { let baseline_let = match () {
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
() => quote!(let #baseline = rtfm::Instant::now();), () => quote!(let ref #baseline = rtfm::Instant::now();),
#[cfg(not(feature = "timer-queue"))] #[cfg(not(feature = "timer-queue"))]
() => quote!(), () => quote!(),
}; };
@ -1172,20 +1198,23 @@ fn interrupts(
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
() => quote!( () => quote!(
#[allow(unused_variables)] #[allow(unused_variables)]
let start = #baseline; let start = *#baseline;
), ),
#[cfg(not(feature = "timer-queue"))] #[cfg(not(feature = "timer-queue"))]
() => quote!(), () => quote!(),
}; };
let locals = mk_locals(&interrupt.statics, false); let locals = mk_locals(&interrupt.statics, false);
let alias = mk_ident(None); let alias = ctxt.ident_gen.mk_ident(None, false);
let symbol = ident.to_string(); let symbol = ident.to_string();
let unsafety = &interrupt.unsafety; let unsafety = &interrupt.unsafety;
scoped.push(quote!( scoped.push(quote!(
// unsafe trampoline to deter end-users from calling this non-reentrant function
#(#attrs)* #(#attrs)*
#[export_name = #symbol] #[export_name = #symbol]
#unsafety fn #alias() { unsafe fn #alias() {
#[inline(always)]
#unsafety fn interrupt() {
// check that this interrupt exists // check that this interrupt exists
let _ = #device::interrupt::#ident; let _ = #device::interrupt::#ident;
@ -1201,6 +1230,9 @@ fn interrupts(
#(#stmts)* #(#stmts)*
}) })
} }
interrupt()
}
)); ));
} }
@ -1213,10 +1245,10 @@ fn tasks(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::Tok
// first pass to generate buffers (statics and resources) and spawn aliases // first pass to generate buffers (statics and resources) and spawn aliases
for (name, task) in &app.tasks { for (name, task) in &app.tasks {
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
let scheduleds_alias = mk_ident(None); let scheduleds_alias = ctxt.ident_gen.mk_ident(None, false);
let free_alias = mk_ident(None); let free_alias = ctxt.ident_gen.mk_ident(None, false);
let inputs_alias = mk_ident(None); let inputs_alias = ctxt.ident_gen.mk_ident(None, false);
let task_alias = mk_ident(Some(&name.to_string())); let task_alias = ctxt.ident_gen.mk_ident(Some(&name.to_string()), false);
let inputs = &task.inputs; let inputs = &task.inputs;
@ -1277,7 +1309,7 @@ fn tasks(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::Tok
alias: task_alias, alias: task_alias,
free_queue: free_alias, free_queue: free_alias,
inputs: inputs_alias, inputs: inputs_alias,
spawn_fn: mk_ident(None), spawn_fn: ctxt.ident_gen.mk_ident(None, false),
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
scheduleds: scheduleds_alias, scheduleds: scheduleds_alias,
@ -1296,7 +1328,7 @@ fn tasks(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::Tok
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
() => { () => {
let baseline = &ctxt.baseline; let baseline = &ctxt.baseline;
quote!(let scheduled = #baseline;) quote!(let scheduled = *#baseline;)
} }
#[cfg(not(feature = "timer-queue"))] #[cfg(not(feature = "timer-queue"))]
() => quote!(), () => quote!(),
@ -1325,19 +1357,23 @@ fn tasks(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::Tok
let attrs = &task.attrs; let attrs = &task.attrs;
let cfgs = &task.cfgs; let cfgs = &task.cfgs;
let task_alias = &ctxt.tasks[name].alias; let task_alias = &ctxt.tasks[name].alias;
let baseline_arg = match () { let (baseline, baseline_arg) = match () {
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
() => { () => {
let baseline = &ctxt.baseline; let baseline = &ctxt.baseline;
quote!(#baseline: rtfm::Instant,) (quote!(#baseline,), quote!(#baseline: &rtfm::Instant,))
} }
#[cfg(not(feature = "timer-queue"))] #[cfg(not(feature = "timer-queue"))]
() => quote!(), () => (quote!(), quote!()),
}; };
let pats = tuple_pat(inputs);
items.push(quote!( items.push(quote!(
// unsafe trampoline to deter end-users from calling this non-reentrant function
#(#attrs)* #(#attrs)*
#(#cfgs)* #(#cfgs)*
#unsafety fn #task_alias(#baseline_arg #(#inputs,)*) { unsafe fn #task_alias(#baseline_arg #(#inputs,)*) {
#[inline(always)]
#unsafety fn task(#baseline_arg #(#inputs,)*) {
#(#locals)* #(#locals)*
#prelude #prelude
@ -1346,6 +1382,9 @@ fn tasks(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::Tok
#(#stmts)* #(#stmts)*
} }
task(#baseline #pats)
}
)); ));
} }
@ -1362,8 +1401,8 @@ fn dispatchers(
let device = &app.args.device; let device = &app.args.device;
for (level, dispatcher) in &analysis.dispatchers { for (level, dispatcher) in &analysis.dispatchers {
let ready_alias = mk_ident(None); let ready_alias = ctxt.ident_gen.mk_ident(None, false);
let enum_alias = mk_ident(None); let enum_alias = ctxt.ident_gen.mk_ident(None, false);
let capacity = mk_typenum_capacity(dispatcher.capacity, true); let capacity = mk_typenum_capacity(dispatcher.capacity, true);
let variants = dispatcher let variants = dispatcher
@ -1427,7 +1466,7 @@ fn dispatchers(
let baseline = let baseline =
ptr::read(#scheduleds.get_ref().get_unchecked(usize::from(index))); ptr::read(#scheduleds.get_ref().get_unchecked(usize::from(index)));
); );
call = quote!(#alias(baseline, #pats)); call = quote!(#alias(&baseline, #pats));
} }
#[cfg(not(feature = "timer-queue"))] #[cfg(not(feature = "timer-queue"))]
() => { () => {
@ -1452,7 +1491,7 @@ fn dispatchers(
let attrs = &dispatcher.attrs; let attrs = &dispatcher.attrs;
let interrupt = &dispatcher.interrupt; let interrupt = &dispatcher.interrupt;
let symbol = interrupt.to_string(); let symbol = interrupt.to_string();
let alias = mk_ident(None); let alias = ctxt.ident_gen.mk_ident(None, false);
dispatchers.push(quote!( dispatchers.push(quote!(
#(#attrs)* #(#attrs)*
#[export_name = #symbol] #[export_name = #symbol]
@ -1534,7 +1573,7 @@ fn spawn(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenSt
#(#cfgs)* #(#cfgs)*
unsafe fn #alias( unsafe fn #alias(
#baseline_arg #baseline_arg
#priority: &core::cell::Cell<u8>, #priority: &rtfm::export::Priority,
#(#args,)* #(#args,)*
) -> Result<(), #ty> { ) -> Result<(), #ty> {
use core::ptr; use core::ptr;
@ -1583,7 +1622,7 @@ fn spawn(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenSt
if is_idle { if is_idle {
quote!(rtfm::Instant::now(),) quote!(rtfm::Instant::now(),)
} else { } else {
quote!(self.#baseline,) quote!(*self.#baseline,)
} }
} }
#[cfg(not(feature = "timer-queue"))] #[cfg(not(feature = "timer-queue"))]
@ -1632,7 +1671,7 @@ fn schedule(ctxt: &Context, app: &App) -> proc_macro2::TokenStream {
#[inline(always)] #[inline(always)]
#(#cfgs)* #(#cfgs)*
unsafe fn #alias( unsafe fn #alias(
#priority: &core::cell::Cell<u8>, #priority: &rtfm::export::Priority,
instant: rtfm::Instant, instant: rtfm::Instant,
#(#args,)* #(#args,)*
) -> Result<(), #ty> { ) -> Result<(), #ty> {
@ -1703,7 +1742,7 @@ fn schedule(ctxt: &Context, app: &App) -> proc_macro2::TokenStream {
quote!(#(#items)*) quote!(#(#items)*)
} }
fn timer_queue(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { fn timer_queue(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
let tasks = &analysis.timer_queue.tasks; let tasks = &analysis.timer_queue.tasks;
if tasks.is_empty() { if tasks.is_empty() {
@ -1778,14 +1817,14 @@ fn timer_queue(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::T
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let logical_prio = analysis.timer_queue.priority; let logical_prio = analysis.timer_queue.priority;
let alias = mk_ident(None); let alias = ctxt.ident_gen.mk_ident(None, false);
items.push(quote!( items.push(quote!(
#[export_name = "SysTick"] #[export_name = "SysTick"]
#[doc(hidden)] #[doc(hidden)]
unsafe fn #alias() { unsafe fn #alias() {
use rtfm::Mutex; use rtfm::Mutex;
let ref #priority = core::cell::Cell::new(#logical_prio); let ref #priority = rtfm::export::Priority::new(#logical_prio);
rtfm::export::run(|| { rtfm::export::run(|| {
rtfm::export::sys_tick(#tq { #priority }, |task, index| { rtfm::export::sys_tick(#tq { #priority }, |task, index| {
@ -1929,7 +1968,7 @@ fn mk_resource(
#(#cfgs)* #(#cfgs)*
pub struct #struct_<'a> { pub struct #struct_<'a> {
#[doc(hidden)] #[doc(hidden)]
pub #priority: &'a core::cell::Cell<u8>, pub #priority: &'a rtfm::export::Priority,
} }
)); ));
@ -1938,7 +1977,7 @@ fn mk_resource(
items.push(quote!( items.push(quote!(
#(#cfgs)* #(#cfgs)*
struct #struct_<'a> { struct #struct_<'a> {
#priority: &'a core::cell::Cell<u8>, #priority: &'a rtfm::export::Priority,
} }
)); ));
@ -1989,15 +2028,18 @@ fn mk_typenum_capacity(capacity: u8, power_of_two: bool) -> proc_macro2::TokenSt
quote!(rtfm::export::consts::#ident) quote!(rtfm::export::consts::#ident)
} }
fn mk_ident(name: Option<&str>) -> Ident { struct IdentGenerator {
static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); call_count: u32,
rng: rand::rngs::SmallRng,
}
impl IdentGenerator {
fn new() -> IdentGenerator {
let elapsed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); let elapsed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
let secs = elapsed.as_secs(); let secs = elapsed.as_secs();
let nanos = elapsed.subsec_nanos(); let nanos = elapsed.subsec_nanos();
let count = CALL_COUNT.fetch_add(1, Ordering::SeqCst) as u32;
let mut seed: [u8; 16] = [0; 16]; let mut seed: [u8; 16] = [0; 16];
for (i, v) in seed.iter_mut().take(8).enumerate() { for (i, v) in seed.iter_mut().take(8).enumerate() {
@ -2008,33 +2050,39 @@ fn mk_ident(name: Option<&str>) -> Ident {
*v = ((nanos >> (i * 8)) & 0xFF) as u8 *v = ((nanos >> (i * 8)) & 0xFF) as u8
} }
for (i, v) in seed.iter_mut().skip(12).enumerate() { let rng = rand::rngs::SmallRng::from_seed(seed);
*v = ((count >> (i * 8)) & 0xFF) as u8
IdentGenerator { call_count: 0, rng }
} }
let n; fn mk_ident(&mut self, name: Option<&str>, random: bool) -> Ident {
let mut s = if let Some(name) = name { let s = if let Some(name) = name {
n = 4;
format!("{}_", name) format!("{}_", name)
} else { } else {
n = 16; "__rtfm_internal_".to_string()
String::new()
}; };
let mut rng = rand::rngs::SmallRng::from_seed(seed); let mut s = format!("{}{}", s, self.call_count);
for i in 0..n { self.call_count += 1;
if i == 0 || rng.gen() {
s.push(('a' as u8 + rng.gen::<u8>() % 25) as char) if random {
s.push('_');
for i in 0..4 {
if i == 0 || self.rng.gen() {
s.push(('a' as u8 + self.rng.gen::<u8>() % 25) as char)
} else { } else {
s.push(('0' as u8 + rng.gen::<u8>() % 10) as char) s.push(('0' as u8 + self.rng.gen::<u8>() % 10) as char)
}
} }
} }
Ident::new(&s, Span::call_site()) Ident::new(&s, Span::call_site())
}
} }
// `once = true` means that these locals will be called from a function that will run *once* // `once = true` means that these locals will be called from a function that will run *once*
fn mk_locals(locals: &HashMap<Ident, Static>, once: bool) -> proc_macro2::TokenStream { fn mk_locals(locals: &BTreeMap<Ident, Static>, once: bool) -> proc_macro2::TokenStream {
let lt = if once { Some(quote!('static)) } else { None }; let lt = if once { Some(quote!('static)) } else { None };
let locals = locals let locals = locals

View file

@ -1,5 +1,5 @@
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{BTreeMap, BTreeSet},
iter, u8, iter, u8,
}; };
@ -120,10 +120,10 @@ impl App {
pub fn parse(items: Vec<Item>, args: AppArgs) -> parse::Result<Self> { pub fn parse(items: Vec<Item>, args: AppArgs) -> parse::Result<Self> {
let mut idle = None; let mut idle = None;
let mut init = None; let mut init = None;
let mut exceptions = HashMap::new(); let mut exceptions = BTreeMap::new();
let mut interrupts = HashMap::new(); let mut interrupts = BTreeMap::new();
let mut resources = HashMap::new(); let mut resources = BTreeMap::new();
let mut tasks = HashMap::new(); let mut tasks = BTreeMap::new();
let mut free_interrupts = None; let mut free_interrupts = None;
for item in items { for item in items {
@ -418,25 +418,25 @@ impl App {
} }
} }
pub type Idents = HashSet<Ident>; pub type Idents = BTreeSet<Ident>;
pub type Exceptions = HashMap<Ident, Exception>; pub type Exceptions = BTreeMap<Ident, Exception>;
pub type Interrupts = HashMap<Ident, Interrupt>; pub type Interrupts = BTreeMap<Ident, Interrupt>;
pub type Resources = HashMap<Ident, Resource>; pub type Resources = BTreeMap<Ident, Resource>;
pub type Statics = Vec<ItemStatic>; pub type Statics = Vec<ItemStatic>;
pub type Tasks = HashMap<Ident, Task>; pub type Tasks = BTreeMap<Ident, Task>;
pub type FreeInterrupts = HashMap<Ident, FreeInterrupt>; pub type FreeInterrupts = BTreeMap<Ident, FreeInterrupt>;
pub struct Idle { pub struct Idle {
pub args: IdleArgs, pub args: IdleArgs,
pub attrs: Vec<Attribute>, pub attrs: Vec<Attribute>,
pub unsafety: Option<Token![unsafe]>, pub unsafety: Option<Token![unsafe]>,
pub statics: HashMap<Ident, Static>, pub statics: BTreeMap<Ident, Static>,
pub stmts: Vec<Stmt>, pub stmts: Vec<Stmt>,
} }
@ -607,7 +607,7 @@ pub struct Init {
pub args: InitArgs, pub args: InitArgs,
pub attrs: Vec<Attribute>, pub attrs: Vec<Attribute>,
pub unsafety: Option<Token![unsafe]>, pub unsafety: Option<Token![unsafe]>,
pub statics: HashMap<Ident, Static>, pub statics: BTreeMap<Ident, Static>,
pub stmts: Vec<Stmt>, pub stmts: Vec<Stmt>,
// TODO remove in v0.5.x // TODO remove in v0.5.x
pub assigns: Vec<Assign>, pub assigns: Vec<Assign>,
@ -703,7 +703,7 @@ pub struct Exception {
pub args: ExceptionArgs, pub args: ExceptionArgs,
pub attrs: Vec<Attribute>, pub attrs: Vec<Attribute>,
pub unsafety: Option<Token![unsafe]>, pub unsafety: Option<Token![unsafe]>,
pub statics: HashMap<Ident, Static>, pub statics: BTreeMap<Ident, Static>,
pub stmts: Vec<Stmt>, pub stmts: Vec<Stmt>,
} }
@ -791,7 +791,7 @@ pub struct Interrupt {
pub args: InterruptArgs, pub args: InterruptArgs,
pub attrs: Vec<Attribute>, pub attrs: Vec<Attribute>,
pub unsafety: Option<Token![unsafe]>, pub unsafety: Option<Token![unsafe]>,
pub statics: HashMap<Ident, Static>, pub statics: BTreeMap<Ident, Static>,
pub stmts: Vec<Stmt>, pub stmts: Vec<Stmt>,
} }
@ -1070,8 +1070,8 @@ pub struct Static {
} }
impl Static { impl Static {
fn parse(items: Vec<ItemStatic>) -> parse::Result<HashMap<Ident, Static>> { fn parse(items: Vec<ItemStatic>) -> parse::Result<BTreeMap<Ident, Static>> {
let mut statics = HashMap::new(); let mut statics = BTreeMap::new();
for item in items { for item in items {
if statics.contains_key(&item.ident) { if statics.contains_key(&item.ident) {
@ -1104,7 +1104,7 @@ pub struct Task {
pub attrs: Vec<Attribute>, pub attrs: Vec<Attribute>,
pub unsafety: Option<Token![unsafe]>, pub unsafety: Option<Token![unsafe]>,
pub inputs: Vec<ArgCaptured>, pub inputs: Vec<ArgCaptured>,
pub statics: HashMap<Ident, Static>, pub statics: BTreeMap<Ident, Static>,
pub stmts: Vec<Stmt>, pub stmts: Vec<Stmt>,
} }

View file

@ -39,6 +39,31 @@ where
f(); f();
} }
// Newtype over `Cell` that forbids mutation through a shared reference
pub struct Priority {
inner: Cell<u8>,
}
impl Priority {
#[inline(always)]
pub unsafe fn new(value: u8) -> Self {
Priority {
inner: Cell::new(value),
}
}
// these two methods are used by claim (see below) but can't be used from the RTFM application
#[inline(always)]
fn set(&self, value: u8) {
self.inner.set(value)
}
#[inline(always)]
fn get(&self) -> u8 {
self.inner.get()
}
}
// TODO(MaybeUninit) Until core::mem::MaybeUninit is stabilized we use our own (inefficient) // TODO(MaybeUninit) Until core::mem::MaybeUninit is stabilized we use our own (inefficient)
// implementation // implementation
pub struct MaybeUninit<T> { pub struct MaybeUninit<T> {
@ -102,7 +127,7 @@ where
#[inline(always)] #[inline(always)]
pub unsafe fn claim<T, R, F>( pub unsafe fn claim<T, R, F>(
ptr: *mut T, ptr: *mut T,
priority: &Cell<u8>, priority: &Priority,
ceiling: u8, ceiling: u8,
nvic_prio_bits: u8, nvic_prio_bits: u8,
f: F, f: F,
@ -135,7 +160,7 @@ where
#[inline(always)] #[inline(always)]
pub unsafe fn claim<T, R, F>( pub unsafe fn claim<T, R, F>(
ptr: *mut T, ptr: *mut T,
priority: &Cell<u8>, priority: &Priority,
ceiling: u8, ceiling: u8,
_nvic_prio_bits: u8, _nvic_prio_bits: u8,
f: F, f: F,

View file

@ -38,8 +38,7 @@ fn cfail() {
let f = f.unwrap().path(); let f = f.unwrap().path();
let name = f.file_stem().unwrap().to_str().unwrap(); let name = f.file_stem().unwrap().to_str().unwrap();
assert!( assert!(Command::new("rustc")
Command::new("rustc")
.args(s.split_whitespace()) .args(s.split_whitespace())
.arg(f.display().to_string()) .arg(f.display().to_string())
.arg("-o") .arg("-o")
@ -48,8 +47,7 @@ fn cfail() {
.arg("linker=true") .arg("linker=true")
.status() .status()
.unwrap() .unwrap()
.success() .success());
);
} }
config.target_rustcflags = Some(s); config.target_rustcflags = Some(s);