implement RFCs 147 and 155, etc.

This commit:

- Implements RFC 147: "all functions must be safe"

- Implements RFC 155: "explicit Context parameter"

- Implements the pending breaking change #141: reject assign syntax in `init`
  (which was used to initialize late resources)

- Refactors code generation to make it more readable -- there are no more random
  identifiers in the output -- and align it with the book description of RTFM
  internals.

- Makes the framework hard depend on `core::mem::MaybeUninit` and thus will
  require nightly until that API is stabilized.

- Fixes a ceiling analysis bug where the priority of the system timer was not
  considered in the analysis.

- Shrinks the size of all the internal queues by turning `AtomicUsize` indices
  into `AtomicU8`s.

- Removes the integration with `owned_singleton`.
This commit is contained in:
Jorge Aparicio 2019-04-21 20:02:59 +02:00
parent e6fb2f216f
commit a452700628
11 changed files with 2491 additions and 2331 deletions

View file

@ -5,6 +5,35 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased] ## [Unreleased]
## v0.5.0 - 2019-??-?? (ALPHA pre-release)
### Changed
- [breaking-change][] [RFC 155] "explicit `Context` parameter" has been
implemented.
[RFC 155]: https://github.com/japaric/cortex-m-rtfm/issues/155
- [breaking-change][] [RFC 147] "all functions must be safe" has been
implemented.
[RFC 147]: https://github.com/japaric/cortex-m-rtfm/issues/147
- All the queues internally used by the framework now use `AtomicU8` indices
instead of `AtomicUsize`; this reduces the static memory used by the
framework.
### Removed
- [breaking-change] the integration with the `owned_singleton` crate has been
removed. You can use `heapless::Pool` instead of `alloc_singleton`.
- [breaking-change] late resources can no longer be initialized using the assign
syntax. `init::LateResources` is the only method to initialize late resources.
See [PR #140] for more details.
[PR #140]: https://github.com/japaric/cortex-m-rtfm/pull/140
## [v0.4.3] - 2019-04-21 ## [v0.4.3] - 2019-04-21
### Changed ### Changed

View file

@ -12,7 +12,7 @@ license = "MIT OR Apache-2.0"
name = "cortex-m-rtfm" name = "cortex-m-rtfm"
readme = "README.md" readme = "README.md"
repository = "https://github.com/japaric/cortex-m-rtfm" repository = "https://github.com/japaric/cortex-m-rtfm"
version = "0.4.3" version = "0.5.0-alpha.1"
[lib] [lib]
name = "rtfm" name = "rtfm"
@ -36,12 +36,13 @@ required-features = ["timer-queue"]
[dependencies] [dependencies]
cortex-m = "0.5.8" cortex-m = "0.5.8"
cortex-m-rt = "0.6.7" cortex-m-rt = "0.6.7"
cortex-m-rtfm-macros = { path = "macros", version = "0.4.3" } cortex-m-rtfm-macros = { path = "macros", version = "0.5.0-alpha.1" }
heapless = "0.4.1"
owned-singleton = "0.1.0" [dependencies.heapless]
features = ["smaller-atomics"]
version = "0.4.1"
[dev-dependencies] [dev-dependencies]
alloc-singleton = "0.1.0"
cortex-m-semihosting = "0.3.2" cortex-m-semihosting = "0.3.2"
lm3s6965 = "0.1.3" lm3s6965 = "0.1.3"
panic-halt = "0.2.0" panic-halt = "0.2.0"

View file

@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0"
name = "cortex-m-rtfm-macros" name = "cortex-m-rtfm-macros"
readme = "../README.md" readme = "../README.md"
repository = "https://github.com/japaric/cortex-m-rtfm" repository = "https://github.com/japaric/cortex-m-rtfm"
version = "0.4.3" version = "0.5.0-alpha.1"
[lib] [lib]
proc-macro = true proc-macro = true
@ -22,10 +22,6 @@ proc-macro2 = "0.4.24"
features = ["extra-traits", "full"] features = ["extra-traits", "full"]
version = "0.15.23" version = "0.15.23"
[dependencies.rand]
default-features = false
version = "0.5.5"
[features] [features]
timer-queue = [] timer-queue = []
nightly = [] nightly = []

View file

@ -190,19 +190,20 @@ pub fn app(app: &App) -> Analysis {
} }
// Ceiling analysis of free queues (consumer end point) -- first pass // Ceiling analysis of free queues (consumer end point) -- first pass
// Ceiling analysis of ready queues (producer end point) // Ceiling analysis of ready queues (producer end point) -- first pass
// Also compute more Send-ness requirements // Also compute more Send-ness requirements
let mut free_queues: HashMap<_, _> = app.tasks.keys().map(|task| (task.clone(), 0)).collect(); let mut free_queues = HashMap::new();
let mut ready_queues: HashMap<_, _> = dispatchers.keys().map(|level| (*level, 0)).collect(); let mut ready_queues = HashMap::new();
for (priority, task) in app.spawn_calls() { for (priority, task) in app.spawn_calls() {
if let Some(priority) = priority { if let Some(priority) = priority {
// Users of `spawn` contend for the to-be-spawned task FREE_QUEUE // Users of `spawn` contend for the spawnee FREE_QUEUE
let c = free_queues.get_mut(task).expect("BUG: free_queue.get_mut"); let c = free_queues.entry(task.clone()).or_default();
*c = cmp::max(*c, priority); *c = cmp::max(*c, priority);
// Users of `spawn` contend for the spawnee's dispatcher READY_QUEUE
let c = ready_queues let c = ready_queues
.get_mut(&app.tasks[task].args.priority) .entry(app.tasks[task].args.priority)
.expect("BUG: ready_queues.get_mut"); .or_default();
*c = cmp::max(*c, priority); *c = cmp::max(*c, priority);
// Send is required when sending messages from a task whose priority doesn't match the // Send is required when sending messages from a task whose priority doesn't match the
@ -215,16 +216,23 @@ pub fn app(app: &App) -> Analysis {
} }
} }
// Ceiling analysis of ready queues (producer end point) -- second pass
// Ceiling analysis of free queues (consumer end point) -- second pass // Ceiling analysis of free queues (consumer end point) -- second pass
// Ceiling analysis of the timer queue // Ceiling analysis of the timer queue
let mut tq_ceiling = tq_priority; let mut tq_ceiling = tq_priority;
for (priority, task) in app.schedule_calls() { 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 { if let Some(priority) = priority {
// Users of `schedule` contend for the to-be-spawned task FREE_QUEUE (consumer end point) // Users of `schedule` contend for the spawnee task FREE_QUEUE
let c = free_queues.get_mut(task).expect("BUG: free_queue.get_mut"); let c = free_queues.entry(task.clone()).or_default();
*c = cmp::max(*c, priority); *c = cmp::max(*c, priority);
// Users of `schedule` contend for the timer queu // Users of `schedule` contend for the timer queue
tq_ceiling = cmp::max(tq_ceiling, priority); tq_ceiling = cmp::max(tq_ceiling, priority);
} else { } else {
// spawns from `init` are excluded from the ceiling analysis // spawns from `init` are excluded from the ceiling analysis

View file

@ -35,21 +35,12 @@ pub fn app(app: &App) -> parse::Result<()> {
} }
} }
// Check that all late resources have been initialized in `#[init]` if `init` has signature // Check that `init` returns `LateResources` if there's any declared late resource
// `fn()` if !app.init.returns_late_resources && app.resources.iter().any(|(_, res)| res.expr.is_none()) {
if !app.init.returns_late_resources { return Err(parse::Error::new(
for res in app.init.span,
app.resources "late resources have been specified so `init` must return `init::LateResources`",
.iter() ));
.filter_map(|(name, res)| if res.expr.is_none() { Some(name) } else { None })
{
if app.init.assigns.iter().all(|assign| assign.left != *res) {
return Err(parse::Error::new(
res.span(),
"late resources MUST be initialized at the end of `init`",
));
}
}
} }
// Check that all referenced tasks have been declared // Check that all referenced tasks have been declared

File diff suppressed because it is too large Load diff

View file

@ -288,9 +288,9 @@ mod syntax;
pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { pub fn app(args: TokenStream, input: TokenStream) -> TokenStream {
// Parse // Parse
let args = parse_macro_input!(args as syntax::AppArgs); let args = parse_macro_input!(args as syntax::AppArgs);
let items = parse_macro_input!(input as syntax::Input).items; let input = parse_macro_input!(input as syntax::Input);
let app = match syntax::App::parse(items, args) { let app = match syntax::App::parse(input.items, args) {
Err(e) => return e.to_compile_error().into(), Err(e) => return e.to_compile_error().into(),
Ok(app) => app, Ok(app) => app,
}; };
@ -304,5 +304,5 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream {
let analysis = analyze::app(&app); let analysis = analyze::app(&app);
// Code generation // Code generation
codegen::app(&app, &analysis).into() codegen::app(&input.ident, &app, &analysis).into()
} }

View file

@ -11,8 +11,8 @@ use syn::{
spanned::Spanned, spanned::Spanned,
token::Brace, token::Brace,
ArgCaptured, AttrStyle, Attribute, Expr, FnArg, ForeignItem, Ident, IntSuffix, Item, ItemFn, ArgCaptured, AttrStyle, Attribute, Expr, FnArg, ForeignItem, Ident, IntSuffix, Item, ItemFn,
ItemForeignMod, ItemStatic, LitInt, Path, PathArguments, PathSegment, ReturnType, Stmt, Token, ItemForeignMod, ItemStatic, LitInt, Pat, Path, PathArguments, ReturnType, Stmt, Token, Type,
Type, TypeTuple, Visibility, TypeTuple, Visibility,
}; };
pub struct AppArgs { pub struct AppArgs {
@ -70,7 +70,7 @@ impl Parse for AppArgs {
pub struct Input { pub struct Input {
_const_token: Token![const], _const_token: Token![const],
_ident: Ident, pub ident: Ident,
_colon_token: Token![:], _colon_token: Token![:],
_ty: TypeTuple, _ty: TypeTuple,
_eq_token: Token![=], _eq_token: Token![=],
@ -94,7 +94,7 @@ impl Parse for Input {
let content; let content;
Ok(Input { Ok(Input {
_const_token: input.parse()?, _const_token: input.parse()?,
_ident: input.parse()?, ident: input.parse()?,
_colon_token: input.parse()?, _colon_token: input.parse()?,
_ty: input.parse()?, _ty: input.parse()?,
_eq_token: input.parse()?, _eq_token: input.parse()?,
@ -435,7 +435,7 @@ 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 context: Pat,
pub statics: BTreeMap<Ident, Static>, pub statics: BTreeMap<Ident, Static>,
pub stmts: Vec<Stmt>, pub stmts: Vec<Stmt>,
} }
@ -444,34 +444,29 @@ pub type IdleArgs = InitArgs;
impl Idle { impl Idle {
fn check(args: IdleArgs, item: ItemFn) -> parse::Result<Self> { fn check(args: IdleArgs, item: ItemFn) -> parse::Result<Self> {
let valid_signature = item.vis == Visibility::Inherited let valid_signature =
&& item.constness.is_none() check_signature(&item) && item.decl.inputs.len() == 1 && is_bottom(&item.decl.output);
&& item.asyncness.is_none()
&& item.abi.is_none()
&& item.decl.generics.params.is_empty()
&& item.decl.generics.where_clause.is_none()
&& item.decl.inputs.is_empty()
&& item.decl.variadic.is_none()
&& is_bottom(&item.decl.output);
let span = item.span(); let span = item.span();
if !valid_signature { if valid_signature {
return Err(parse::Error::new( if let Some((context, _)) = check_inputs(item.decl.inputs, "idle") {
span, let (statics, stmts) = extract_statics(item.block.stmts);
"`idle` must have type signature `[unsafe] fn() -> !`",
)); return Ok(Idle {
args,
attrs: item.attrs,
context,
statics: Static::parse(statics)?,
stmts,
});
}
} }
let (statics, stmts) = extract_statics(item.block.stmts); Err(parse::Error::new(
span,
Ok(Idle { "`idle` must have type signature `fn(idle::Context) -> !`",
args, ))
attrs: item.attrs,
unsafety: item.unsafety,
statics: Static::parse(statics)?,
stmts,
})
} }
} }
@ -596,34 +591,21 @@ impl Parse for InitArgs {
} }
} }
// TODO remove in v0.5.x
pub struct Assign {
pub attrs: Vec<Attribute>,
pub left: Ident,
pub right: Box<Expr>,
}
pub struct Init { pub struct Init {
pub args: InitArgs, pub args: InitArgs,
pub attrs: Vec<Attribute>, pub attrs: Vec<Attribute>,
pub unsafety: Option<Token![unsafe]>,
pub statics: BTreeMap<Ident, Static>, pub statics: BTreeMap<Ident, Static>,
pub context: Pat,
pub stmts: Vec<Stmt>, pub stmts: Vec<Stmt>,
// TODO remove in v0.5.x
pub assigns: Vec<Assign>,
pub returns_late_resources: bool, pub returns_late_resources: bool,
pub span: Span,
} }
impl Init { impl Init {
fn check(args: InitArgs, item: ItemFn) -> parse::Result<Self> { fn check(args: InitArgs, item: ItemFn) -> parse::Result<Self> {
let mut valid_signature = item.vis == Visibility::Inherited let mut valid_signature = check_signature(&item) && item.decl.inputs.len() == 1;
&& item.constness.is_none()
&& item.asyncness.is_none() const DONT_CARE: bool = false;
&& item.abi.is_none()
&& item.decl.generics.params.is_empty()
&& item.decl.generics.where_clause.is_none()
&& item.decl.inputs.is_empty()
&& item.decl.variadic.is_none();
let returns_late_resources = match &item.decl.output { let returns_late_resources = match &item.decl.output {
ReturnType::Default => false, ReturnType::Default => false,
@ -636,36 +618,25 @@ impl Init {
} else { } else {
valid_signature = false; valid_signature = false;
false // don't care DONT_CARE
} }
} }
Type::Path(p) => { Type::Path(_) => {
let mut segments = p.path.segments.iter(); if is_path(ty, &["init", "LateResources"]) {
if p.qself.is_none()
&& p.path.leading_colon.is_none()
&& p.path.segments.len() == 2
&& segments.next().map(|s| {
s.arguments == PathArguments::None && s.ident.to_string() == "init"
}) == Some(true)
&& segments.next().map(|s| {
s.arguments == PathArguments::None
&& s.ident.to_string() == "LateResources"
}) == Some(true)
{
// -> init::LateResources // -> init::LateResources
true true
} else { } else {
valid_signature = false; valid_signature = false;
false // don't care DONT_CARE
} }
} }
_ => { _ => {
valid_signature = false; valid_signature = false;
false // don't care DONT_CARE
} }
} }
} }
@ -673,29 +644,26 @@ impl Init {
let span = item.span(); let span = item.span();
if !valid_signature { if valid_signature {
return Err(parse::Error::new( if let Some((context, _)) = check_inputs(item.decl.inputs, "init") {
span, let (statics, stmts) = extract_statics(item.block.stmts);
"`init` must have type signature `[unsafe] fn() [-> init::LateResources]`",
)); return Ok(Init {
args,
attrs: item.attrs,
statics: Static::parse(statics)?,
context,
stmts,
returns_late_resources,
span,
});
}
} }
let (statics, stmts) = extract_statics(item.block.stmts); Err(parse::Error::new(
let (stmts, assigns) = if returns_late_resources { span,
(stmts, vec![]) "`init` must have type signature `fn(init::Context) [-> init::LateResources]`",
} else { ))
extract_assignments(stmts)
};
Ok(Init {
args,
attrs: item.attrs,
unsafety: item.unsafety,
statics: Static::parse(statics)?,
stmts,
assigns,
returns_late_resources,
})
} }
} }
@ -725,8 +693,8 @@ impl Default for Args {
pub struct Exception { pub struct Exception {
pub args: ExceptionArgs, pub args: ExceptionArgs,
pub attrs: Vec<Attribute>, pub attrs: Vec<Attribute>,
pub unsafety: Option<Token![unsafe]>,
pub statics: BTreeMap<Ident, Static>, pub statics: BTreeMap<Ident, Static>,
pub context: Pat,
pub stmts: Vec<Stmt>, pub stmts: Vec<Stmt>,
} }
@ -770,61 +738,67 @@ impl Parse for ExceptionArgs {
impl Exception { impl Exception {
fn check(args: ExceptionArgs, item: ItemFn) -> parse::Result<Self> { fn check(args: ExceptionArgs, item: ItemFn) -> parse::Result<Self> {
let valid_signature = item.vis == Visibility::Inherited let valid_signature =
&& item.constness.is_none() check_signature(&item) && item.decl.inputs.len() == 1 && is_unit(&item.decl.output);
&& item.asyncness.is_none()
&& item.abi.is_none()
&& item.decl.generics.params.is_empty()
&& item.decl.generics.where_clause.is_none()
&& item.decl.inputs.is_empty()
&& item.decl.variadic.is_none()
&& is_unit(&item.decl.output);
if !valid_signature { let span = item.span();
return Err(parse::Error::new(
item.span(),
"`exception` handlers must have type signature `[unsafe] fn()`",
));
}
let span = item.ident.span(); let name = item.ident.to_string();
match &*args.binds.as_ref().unwrap_or(&item.ident).to_string() { if valid_signature {
"MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" if let Some((context, _)) = check_inputs(item.decl.inputs, &name) {
| "DebugMonitor" | "PendSV" => {} // OK let span = item.ident.span();
"SysTick" => { match &*args
if cfg!(feature = "timer-queue") { .binds
return Err(parse::Error::new( .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, span,
"the `SysTick` exception can't be used because it's used by \ "only exceptions with configurable priority can be used as hardware tasks",
the runtime when the `timer-queue` feature is enabled",
)); ));
}
} }
}
_ => { let (statics, stmts) = extract_statics(item.block.stmts);
return Err(parse::Error::new(
span, return Ok(Exception {
"only exceptions with configurable priority can be used as hardware tasks", args,
)); attrs: item.attrs,
statics: Static::parse(statics)?,
context,
stmts,
});
} }
} }
let (statics, stmts) = extract_statics(item.block.stmts); Err(parse::Error::new(
span,
Ok(Exception { &format!(
args, "this `exception` handler must have type signature `fn({}::Context)`",
attrs: item.attrs, name
unsafety: item.unsafety, ),
statics: Static::parse(statics)?, ))
stmts,
})
} }
} }
pub struct Interrupt { pub struct Interrupt {
pub args: InterruptArgs, pub args: InterruptArgs,
pub attrs: Vec<Attribute>, pub attrs: Vec<Attribute>,
pub unsafety: Option<Token![unsafe]>,
pub statics: BTreeMap<Ident, Static>, pub statics: BTreeMap<Ident, Static>,
pub context: Pat,
pub stmts: Vec<Stmt>, pub stmts: Vec<Stmt>,
} }
@ -832,49 +806,47 @@ pub type InterruptArgs = ExceptionArgs;
impl Interrupt { impl Interrupt {
fn check(args: InterruptArgs, item: ItemFn) -> parse::Result<Self> { fn check(args: InterruptArgs, item: ItemFn) -> parse::Result<Self> {
let valid_signature = item.vis == Visibility::Inherited let valid_signature =
&& item.constness.is_none() check_signature(&item) && item.decl.inputs.len() == 1 && is_unit(&item.decl.output);
&& item.asyncness.is_none()
&& item.abi.is_none()
&& item.decl.generics.params.is_empty()
&& item.decl.generics.where_clause.is_none()
&& item.decl.inputs.is_empty()
&& item.decl.variadic.is_none()
&& is_unit(&item.decl.output);
let span = item.span(); let span = item.span();
if !valid_signature { let name = item.ident.to_string();
return Err(parse::Error::new( if valid_signature {
span, if let Some((context, _)) = check_inputs(item.decl.inputs, &name) {
"`interrupt` handlers must have type signature `[unsafe] fn()`", match &*name {
)); "init" | "idle" | "resources" => {
} return Err(parse::Error::new(
span,
"`interrupt` handlers can NOT be named `idle`, `init` or `resources`",
));
}
_ => {}
}
match &*item.ident.to_string() { let (statics, stmts) = extract_statics(item.block.stmts);
"init" | "idle" | "resources" => {
return Err(parse::Error::new( return Ok(Interrupt {
span, args,
"`interrupt` handlers can NOT be named `idle`, `init` or `resources`", attrs: item.attrs,
)); statics: Static::parse(statics)?,
context,
stmts,
});
} }
_ => {}
} }
let (statics, stmts) = extract_statics(item.block.stmts); Err(parse::Error::new(
span,
Ok(Interrupt { format!(
args, "this `interrupt` handler must have type signature `fn({}::Context)`",
attrs: item.attrs, name
unsafety: item.unsafety, ),
statics: Static::parse(statics)?, ))
stmts,
})
} }
} }
pub struct Resource { pub struct Resource {
pub singleton: bool,
pub cfgs: Vec<Attribute>, pub cfgs: Vec<Attribute>,
pub attrs: Vec<Attribute>, pub attrs: Vec<Attribute>,
pub mutability: Option<Token![mut]>, pub mutability: Option<Token![mut]>,
@ -883,7 +855,7 @@ pub struct Resource {
} }
impl Resource { impl Resource {
fn check(mut item: ItemStatic) -> parse::Result<Resource> { fn check(item: ItemStatic) -> parse::Result<Resource> {
if item.vis != Visibility::Inherited { if item.vis != Visibility::Inherited {
return Err(parse::Error::new( return Err(parse::Error::new(
item.span(), item.span(),
@ -896,19 +868,9 @@ impl Resource {
_ => false, _ => false,
}; };
let pos = item.attrs.iter().position(|attr| eq(attr, "Singleton"));
if let Some(pos) = pos {
item.attrs[pos].path.segments.insert(
0,
PathSegment::from(Ident::new("owned_singleton", Span::call_site())),
);
}
let (cfgs, attrs) = extract_cfgs(item.attrs); let (cfgs, attrs) = extract_cfgs(item.attrs);
Ok(Resource { Ok(Resource {
singleton: pos.is_some(),
cfgs, cfgs,
attrs, attrs,
mutability: item.mutability, mutability: item.mutability,
@ -1177,66 +1139,61 @@ pub struct Task {
pub args: TaskArgs, pub args: TaskArgs,
pub cfgs: Vec<Attribute>, pub cfgs: Vec<Attribute>,
pub attrs: Vec<Attribute>, pub attrs: Vec<Attribute>,
pub unsafety: Option<Token![unsafe]>,
pub inputs: Vec<ArgCaptured>, pub inputs: Vec<ArgCaptured>,
pub context: Pat,
pub statics: BTreeMap<Ident, Static>, pub statics: BTreeMap<Ident, Static>,
pub stmts: Vec<Stmt>, pub stmts: Vec<Stmt>,
} }
impl Task { impl Task {
fn check(args: TaskArgs, item: ItemFn) -> parse::Result<Self> { fn check(args: TaskArgs, item: ItemFn) -> parse::Result<Self> {
let valid_signature = item.vis == Visibility::Inherited let valid_signature =
&& item.constness.is_none() check_signature(&item) && !item.decl.inputs.is_empty() && is_unit(&item.decl.output);
&& item.asyncness.is_none()
&& item.abi.is_none()
&& item.decl.generics.params.is_empty()
&& item.decl.generics.where_clause.is_none()
&& item.decl.variadic.is_none()
&& is_unit(&item.decl.output);
let span = item.span(); let span = item.span();
if !valid_signature { let name = item.ident.to_string();
return Err(parse::Error::new( if valid_signature {
span, if let Some((context, rest)) = check_inputs(item.decl.inputs, &name) {
"`task` handlers must have type signature `[unsafe] fn(..)`", let (statics, stmts) = extract_statics(item.block.stmts);
));
}
let (statics, stmts) = extract_statics(item.block.stmts); let inputs = rest.map_err(|arg| {
parse::Error::new(
arg.span(),
"inputs must be named arguments (e.f. `foo: u32`) and not include `self`",
)
})?;
let mut inputs = vec![]; match &*name {
for input in item.decl.inputs { "init" | "idle" | "resources" => {
if let FnArg::Captured(capture) = input { return Err(parse::Error::new(
inputs.push(capture); span,
} else { "`task` handlers can NOT be named `idle`, `init` or `resources`",
return Err(parse::Error::new( ));
span, }
"inputs must be named arguments (e.f. `foo: u32`) and not include `self`", _ => {}
)); }
let (cfgs, attrs) = extract_cfgs(item.attrs);
return Ok(Task {
args,
cfgs,
attrs,
inputs,
context,
statics: Static::parse(statics)?,
stmts,
});
} }
} }
match &*item.ident.to_string() { Err(parse::Error::new(
"init" | "idle" | "resources" => { span,
return Err(parse::Error::new( &format!(
span, "this `task` handler must have type signature `fn({}::Context, ..)`",
"`task` handlers can NOT be named `idle`, `init` or `resources`", name
)); ),
} ))
_ => {}
}
let (cfgs, attrs) = extract_cfgs(item.attrs);
Ok(Task {
args,
cfgs,
attrs,
unsafety: item.unsafety,
inputs,
statics: Static::parse(statics)?,
stmts,
})
} }
} }
@ -1335,38 +1292,69 @@ fn extract_statics(stmts: Vec<Stmt>) -> (Statics, Vec<Stmt>) {
(statics, stmts) (statics, stmts)
} }
// TODO remove in v0.5.x // checks that the list of arguments has the form `#pat: #name::Context, (..)`
fn extract_assignments(stmts: Vec<Stmt>) -> (Vec<Stmt>, Vec<Assign>) { //
let mut istmts = stmts.into_iter().rev(); // 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();
let mut assigns = vec![]; match inputs.next() {
let mut stmts = vec![]; Some(FnArg::Captured(first)) => {
while let Some(stmt) = istmts.next() { if is_path(&first.ty, &[name, "Context"]) {
match stmt { let rest = inputs
Stmt::Semi(Expr::Assign(assign), semi) => { .map(|arg| match arg {
if let Expr::Path(ref expr) = *assign.left { FnArg::Captured(arg) => Ok(arg),
if expr.path.segments.len() == 1 { _ => Err(arg),
assigns.push(Assign { })
attrs: assign.attrs, .collect::<Result<Vec<_>, _>>();
left: expr.path.segments[0].ident.clone(),
right: assign.right,
});
continue;
}
}
stmts.push(Stmt::Semi(Expr::Assign(assign), semi)); Some((first.pat, rest))
} } else {
_ => { None
stmts.push(stmt);
break;
} }
} }
_ => None,
} }
}
stmts.extend(istmts); /// checks that a function signature
///
/// - has no bounds (like where clauses)
/// - is not `async`
/// - is not `const`
/// - is not `unsafe`
/// - is not generic (has no type parametrs)
/// - is not variadic
/// - uses the Rust ABI (and not e.g. "C")
fn check_signature(item: &ItemFn) -> bool {
item.vis == Visibility::Inherited
&& item.constness.is_none()
&& item.asyncness.is_none()
&& item.abi.is_none()
&& item.unsafety.is_none()
&& item.decl.generics.params.is_empty()
&& item.decl.generics.where_clause.is_none()
&& item.decl.variadic.is_none()
}
(stmts.into_iter().rev().collect(), assigns) fn is_path(ty: &Type, segments: &[&str]) -> bool {
match ty {
Type::Path(tpath) if tpath.qself.is_none() => {
tpath.path.segments.len() == segments.len()
&& tpath
.path
.segments
.iter()
.zip(segments)
.all(|(lhs, rhs)| lhs.ident == **rhs)
}
_ => false,
}
} }
fn is_bottom(ty: &ReturnType) -> bool { fn is_bottom(ty: &ReturnType) -> bool {

View file

@ -1,7 +1,5 @@
//! IMPLEMENTATION DETAILS. DO NOT USE ANYTHING IN THIS MODULE //! IMPLEMENTATION DETAILS. DO NOT USE ANYTHING IN THIS MODULE
#[cfg(not(feature = "nightly"))]
use core::ptr;
use core::{cell::Cell, u8}; use core::{cell::Cell, u8};
#[cfg(armv7m)] #[cfg(armv7m)]
@ -14,25 +12,31 @@ pub use heapless::consts;
use heapless::spsc::{Queue, SingleCore}; use heapless::spsc::{Queue, SingleCore};
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
pub use crate::tq::{isr as sys_tick, NotReady, TimerQueue}; pub use crate::tq::{NotReady, TimerQueue};
pub type FreeQueue<N> = Queue<u8, N, usize, SingleCore>; pub type FreeQueue<N> = Queue<u8, N, u8, SingleCore>;
pub type ReadyQueue<T, N> = Queue<(T, u8), N, usize, SingleCore>; pub type ReadyQueue<T, N> = Queue<(T, u8), N, u8, SingleCore>;
#[cfg(armv7m)] #[cfg(armv7m)]
#[inline(always)] #[inline(always)]
pub fn run<F>(f: F) pub fn run<F>(priority: u8, f: F)
where where
F: FnOnce(), F: FnOnce(),
{ {
let initial = basepri::read(); if priority == 1 {
f(); // if the priority of this interrupt is `1` then BASEPRI can only be `0`
unsafe { basepri::write(initial) } f();
unsafe { basepri::write(0) }
} else {
let initial = basepri::read();
f();
unsafe { basepri::write(initial) }
}
} }
#[cfg(not(armv7m))] #[cfg(not(armv7m))]
#[inline(always)] #[inline(always)]
pub fn run<F>(f: F) pub fn run<F>(_priority: u8, f: F)
where where
F: FnOnce(), F: FnOnce(),
{ {
@ -52,7 +56,7 @@ impl Priority {
} }
} }
// these two methods are used by claim (see below) but can't be used from the RTFM application // these two methods are used by `lock` (see below) but can't be used from the RTFM application
#[inline(always)] #[inline(always)]
fn set(&self, value: u8) { fn set(&self, value: u8) {
self.inner.set(value) self.inner.set(value)
@ -64,13 +68,12 @@ impl Priority {
} }
} }
#[cfg(feature = "nightly")] // We newtype `core::mem::MaybeUninit` so the end-user doesn't need `#![feature(maybe_uninit)]` in
// their code
pub struct MaybeUninit<T> { pub struct MaybeUninit<T> {
// we newtype so the end-user doesn't need `#![feature(maybe_uninit)]` in their code
inner: core::mem::MaybeUninit<T>, inner: core::mem::MaybeUninit<T>,
} }
#[cfg(feature = "nightly")]
impl<T> MaybeUninit<T> { impl<T> MaybeUninit<T> {
pub const fn uninit() -> Self { pub const fn uninit() -> Self {
MaybeUninit { MaybeUninit {
@ -86,64 +89,15 @@ impl<T> MaybeUninit<T> {
self.inner.as_mut_ptr() self.inner.as_mut_ptr()
} }
pub unsafe fn read(&self) -> T {
self.inner.read()
}
pub fn write(&mut self, value: T) -> &mut T { pub fn write(&mut self, value: T) -> &mut T {
self.inner.write(value) self.inner.write(value)
} }
} }
#[cfg(not(feature = "nightly"))]
pub struct MaybeUninit<T> {
value: Option<T>,
}
#[cfg(not(feature = "nightly"))]
const MSG: &str =
"you have hit a bug (UB) in RTFM implementation; try enabling this crate 'nightly' feature";
#[cfg(not(feature = "nightly"))]
impl<T> MaybeUninit<T> {
pub const fn uninit() -> Self {
MaybeUninit { value: None }
}
pub fn as_ptr(&self) -> *const T {
if let Some(x) = self.value.as_ref() {
x
} else {
unreachable!(MSG)
}
}
pub fn as_mut_ptr(&mut self) -> *mut T {
if let Some(x) = self.value.as_mut() {
x
} else {
unreachable!(MSG)
}
}
pub unsafe fn get_ref(&self) -> &T {
if let Some(x) = self.value.as_ref() {
x
} else {
unreachable!(MSG)
}
}
pub unsafe fn get_mut(&mut self) -> &mut T {
if let Some(x) = self.value.as_mut() {
x
} else {
unreachable!(MSG)
}
}
pub fn write(&mut self, val: T) {
// NOTE(volatile) we have observed UB when this uses a plain `ptr::write`
unsafe { ptr::write_volatile(&mut self.value, Some(val)) }
}
}
#[inline(always)] #[inline(always)]
pub fn assert_send<T>() pub fn assert_send<T>()
where where
@ -160,19 +114,16 @@ where
#[cfg(armv7m)] #[cfg(armv7m)]
#[inline(always)] #[inline(always)]
pub unsafe fn claim<T, R, F>( pub unsafe fn lock<T, R>(
ptr: *mut T, ptr: *mut T,
priority: &Priority, priority: &Priority,
ceiling: u8, ceiling: u8,
nvic_prio_bits: u8, nvic_prio_bits: u8,
f: F, f: impl FnOnce(&mut T) -> R,
) -> R ) -> R {
where
F: FnOnce(&mut T) -> R,
{
let current = priority.get(); let current = priority.get();
if priority.get() < ceiling { if current < ceiling {
if ceiling == (1 << nvic_prio_bits) { if ceiling == (1 << nvic_prio_bits) {
priority.set(u8::MAX); priority.set(u8::MAX);
let r = interrupt::free(|_| f(&mut *ptr)); let r = interrupt::free(|_| f(&mut *ptr));
@ -193,19 +144,16 @@ where
#[cfg(not(armv7m))] #[cfg(not(armv7m))]
#[inline(always)] #[inline(always)]
pub unsafe fn claim<T, R, F>( pub unsafe fn lock<T, R>(
ptr: *mut T, ptr: *mut T,
priority: &Priority, priority: &Priority,
ceiling: u8, ceiling: u8,
_nvic_prio_bits: u8, _nvic_prio_bits: u8,
f: F, f: impl FnOnce(&mut T) -> R,
) -> R ) -> R {
where
F: FnOnce(&mut T) -> R,
{
let current = priority.get(); let current = priority.get();
if priority.get() < ceiling { if current < ceiling {
priority.set(u8::MAX); priority.set(u8::MAX);
let r = interrupt::free(|_| f(&mut *ptr)); let r = interrupt::free(|_| f(&mut *ptr));
priority.set(current); priority.set(current);
@ -215,8 +163,7 @@ where
} }
} }
#[cfg(armv7m)]
#[inline] #[inline]
fn logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { pub fn logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 {
((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits) ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits)
} }

View file

@ -1,5 +1,8 @@
//! Real Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollers //! Real Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollers
//! //!
//! **HEADS UP** This is an **alpha** pre-release; there may be breaking changes in the API and
//! semantics before a proper release is made.
//!
//! **IMPORTANT**: This crate is published as [`cortex-m-rtfm`] on crates.io but the name of the //! **IMPORTANT**: This crate is published as [`cortex-m-rtfm`] on crates.io but the name of the
//! library is `rtfm`. //! library is `rtfm`.
//! //!
@ -7,7 +10,7 @@
//! //!
//! The user level documentation can be found [here]. //! The user level documentation can be found [here].
//! //!
//! [here]: https://japaric.github.io/cortex-m-rtfm/book/en/ //! [here]: https://japaric.github.io/rtfm5/book/en/
//! //!
//! Don't forget to check the documentation of the [`#[app]`] attribute, which is the main component //! Don't forget to check the documentation of the [`#[app]`] attribute, which is the main component
//! of the framework. //! of the framework.
@ -16,7 +19,7 @@
//! //!
//! # Minimum Supported Rust Version (MSRV) //! # Minimum Supported Rust Version (MSRV)
//! //!
//! This crate is guaranteed to compile on stable Rust 1.31 (2018 edition) and up. It *might* //! This crate is guaranteed to compile on stable Rust 1.36 (2018 edition) and up. It *might*
//! compile on older versions but that may change in any new patch release. //! compile on older versions but that may change in any new patch release.
//! //!
//! # Semantic Versioning //! # Semantic Versioning
@ -36,12 +39,11 @@
//! [`Instant`]: struct.Instant.html //! [`Instant`]: struct.Instant.html
//! [`Duration`]: struct.Duration.html //! [`Duration`]: struct.Duration.html
//! //!
//! - `nightly`. Enabling this opt-in feature makes RTFM internally use the unstable //! - `nightly`. Enabling this opt-in feature makes RTFM internally use the unstable `const_fn`
//! `core::mem::MaybeUninit` API and unstable `const_fn` language feature to reduce static memory //! language feature to reduce static memory usage, runtime overhead and initialization overhead.
//! usage, runtime overhead and initialization overhead. This feature requires a nightly compiler //! This feature requires a nightly compiler and may stop working at any time!
//! and may stop working at any time!
#![cfg_attr(feature = "nightly", feature(maybe_uninit))] #![feature(maybe_uninit)]
#![deny(missing_docs)] #![deny(missing_docs)]
#![deny(warnings)] #![deny(warnings)]
#![no_std] #![no_std]
@ -132,7 +134,7 @@ pub struct Instant(i32);
impl Instant { impl Instant {
/// IMPLEMENTATION DETAIL. DO NOT USE /// IMPLEMENTATION DETAIL. DO NOT USE
#[doc(hidden)] #[doc(hidden)]
pub fn artificial(timestamp: i32) -> Self { pub unsafe fn artificial(timestamp: i32) -> Self {
Instant(timestamp) Instant(timestamp)
} }
@ -290,9 +292,7 @@ pub trait Mutex {
type T; type T;
/// Creates a critical section and grants temporary access to the protected data /// Creates a critical section and grants temporary access to the protected data
fn lock<R, F>(&mut self, f: F) -> R fn lock<R>(&mut self, f: impl FnOnce(&mut Self::T) -> R) -> R;
where
F: FnOnce(&mut Self::T) -> R;
} }
impl<'a, M> Mutex for &'a mut M impl<'a, M> Mutex for &'a mut M
@ -301,10 +301,7 @@ where
{ {
type T = M::T; type T = M::T;
fn lock<R, F>(&mut self, f: F) -> R fn lock<R>(&mut self, f: impl FnOnce(&mut M::T) -> R) -> R {
where
F: FnOnce(&mut Self::T) -> R,
{
(**self).lock(f) (**self).lock(f)
} }
} }
@ -317,10 +314,7 @@ pub struct Exclusive<'a, T>(pub &'a mut T);
impl<'a, T> Mutex for Exclusive<'a, T> { impl<'a, T> Mutex for Exclusive<'a, T> {
type T = T; type T = T;
fn lock<R, F>(&mut self, f: F) -> R fn lock<R>(&mut self, f: impl FnOnce(&mut T) -> R) -> R {
where
F: FnOnce(&mut Self::T) -> R,
{
f(self.0) f(self.0)
} }
} }

View file

@ -3,7 +3,7 @@ use core::cmp::{self, Ordering};
use cortex_m::peripheral::{SCB, SYST}; use cortex_m::peripheral::{SCB, SYST};
use heapless::{binary_heap::Min, ArrayLength, BinaryHeap}; use heapless::{binary_heap::Min, ArrayLength, BinaryHeap};
use crate::{Instant, Mutex}; use crate::Instant;
pub struct TimerQueue<T, N> pub struct TimerQueue<T, N>
where where
@ -43,11 +43,39 @@ where
} }
// set SysTick pending // set SysTick pending
(*SCB::ptr()).icsr.write(1 << 26); SCB::set_pendst();
} }
self.queue.push_unchecked(nr); self.queue.push_unchecked(nr);
} }
#[inline]
pub fn dequeue(&mut self) -> Option<(T, u8)> {
if let Some(instant) = self.queue.peek().map(|p| p.instant) {
let diff = instant.0.wrapping_sub(Instant::now().0);
if diff < 0 {
// task became ready
let nr = unsafe { self.queue.pop_unchecked() };
Some((nr.task, nr.index))
} else {
// set a new timeout
const MAX: u32 = 0x00ffffff;
self.syst.set_reload(cmp::min(MAX, diff as u32));
// start counting down from the new reload
self.syst.clear_current();
None
}
} else {
// the queue is empty
self.syst.disable_interrupt();
None
}
}
} }
pub struct NotReady<T> pub struct NotReady<T>
@ -87,49 +115,3 @@ where
Some(self.cmp(&other)) Some(self.cmp(&other))
} }
} }
#[inline(always)]
pub fn isr<TQ, T, N, F>(mut tq: TQ, mut f: F)
where
TQ: Mutex<T = TimerQueue<T, N>>,
T: Copy + Send,
N: ArrayLength<NotReady<T>>,
F: FnMut(T, u8),
{
loop {
// XXX does `#[inline(always)]` improve performance or not?
let next = tq.lock(#[inline(always)]
|tq| {
if let Some(instant) = tq.queue.peek().map(|p| p.instant) {
let diff = instant.0.wrapping_sub(Instant::now().0);
if diff < 0 {
// task became ready
let m = unsafe { tq.queue.pop_unchecked() };
Some((m.task, m.index))
} else {
// set a new timeout
const MAX: u32 = 0x00ffffff;
tq.syst.set_reload(cmp::min(MAX, diff as u32));
// start counting down from the new reload
tq.syst.clear_current();
None
}
} else {
// the queue is empty
tq.syst.disable_interrupt();
None
}
});
if let Some((task, index)) = next {
f(task, index)
} else {
return;
}
}
}