767: allow #[init] and #[idle] to be externed r=korken89 a=wiktorwieclaw

I updated `rtic-macros` to a allow init and idle to be externally defined.

## Design notes
* Updated `extern_binds` example to include external #[init] and #[idle] functions.
* Added docs to Local and Shared structs. The `extern_binds` example has a `#![deny(missing_docs)]` which caused some issues.

## Testing
Apart from building the example, I also used this feature in one of my projects and ran it on a MCU [here](98ca7bd42e/crates/cansat-stm32f4/src/main.rs (L59-L74))

## Related issues
* https://github.com/rtic-rs/rtic/issues/505

## Related PRs
* https://github.com/rtic-rs/rtic-syntax/pull/71

Co-authored-by: Vixu <lonevixu@gmail.com>
This commit is contained in:
bors[bot] 2023-06-14 18:29:29 +00:00 committed by GitHub
commit 08c0065c02
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 173 additions and 47 deletions

View file

@ -34,16 +34,20 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
let attrs = &idle.attrs;
let context = &idle.context;
let stmts = &idle.stmts;
let user_idle = Some(quote!(
#(#attrs)*
#[allow(non_snake_case)]
fn #name(#context: #name::Context) -> ! {
use rtic::Mutex as _;
use rtic::mutex::prelude::*;
let user_idle = if !idle.is_extern {
Some(quote!(
#(#attrs)*
#[allow(non_snake_case)]
fn #name(#context: #name::Context) -> ! {
use rtic::Mutex as _;
use rtic::mutex::prelude::*;
#(#stmts)*
}
));
#(#stmts)*
}
))
} else {
None
};
quote!(
#(#mod_app)*

View file

@ -54,10 +54,12 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
.collect();
root_init.push(quote! {
#[doc = r"Shared resources"]
#shared_vis struct #shared {
#(#shared_resources)*
}
#[doc = r"Local resources"]
#local_vis struct #local {
#(#local_resources)*
}
@ -67,14 +69,18 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
let user_init_return = quote! {#shared, #local};
let user_init = quote!(
#(#attrs)*
#[inline(always)]
#[allow(non_snake_case)]
fn #name(#context: #name::Context) -> (#user_init_return) {
#(#stmts)*
}
);
let user_init = if !init.is_extern {
Some(quote!(
#(#attrs)*
#[inline(always)]
#[allow(non_snake_case)]
fn #name(#context: #name::Context) -> (#user_init_return) {
#(#stmts)*
}
))
} else {
None
};
let mut mod_app = None;

View file

@ -91,6 +91,9 @@ pub struct Init {
/// The name of the user provided local resources struct
pub user_local_struct: Ident,
/// The init function is declared externally
pub is_extern: bool,
}
/// `init` context metadata
@ -127,6 +130,9 @@ pub struct Idle {
/// The statements that make up this `idle` function
pub stmts: Vec<Stmt>,
/// The idle function is declared externally
pub is_extern: bool,
}
/// `idle` context metadata

View file

@ -365,6 +365,42 @@ impl App {
if let ForeignItem::Fn(mut item) = item {
let span = item.sig.ident.span();
if let Some(pos) = item
.attrs
.iter()
.position(|attr| util::attr_eq(attr, "init"))
{
let args = InitArgs::parse(item.attrs.remove(pos).tokens)?;
// If an init function already exists, error
if init.is_some() {
return Err(parse::Error::new(
span,
"`#[init]` function must appear at most once",
));
}
check_ident(&item.sig.ident)?;
init = Some(Init::parse_foreign(args, item)?);
} else if let Some(pos) = item
.attrs
.iter()
.position(|attr| util::attr_eq(attr, "idle"))
{
let args = IdleArgs::parse(item.attrs.remove(pos).tokens)?;
// If an idle function already exists, error
if idle.is_some() {
return Err(parse::Error::new(
span,
"`#[idle]` function must appear at most once",
));
}
check_ident(&item.sig.ident)?;
idle = Some(Idle::parse_foreign(args, item)?);
} else if let Some(pos) = item
.attrs
.iter()
.position(|attr| util::attr_eq(attr, "task"))
@ -408,7 +444,8 @@ impl App {
} else {
return Err(parse::Error::new(
span,
"`extern` task required `#[task(..)]` attribute",
"`extern` task, init or idle must have either `#[task(..)]`,
`#[init(..)]` or `#[idle(..)]` attribute",
));
}
} else {

View file

@ -1,5 +1,5 @@
use proc_macro2::TokenStream as TokenStream2;
use syn::{parse, ItemFn};
use syn::{parse, ForeignItemFn, ItemFn, Stmt};
use crate::syntax::{
ast::{Idle, IdleArgs},
@ -29,6 +29,35 @@ impl Idle {
context,
name: item.sig.ident,
stmts: item.block.stmts,
is_extern: false,
});
}
}
}
Err(parse::Error::new(
item.sig.ident.span(),
format!("this `#[idle]` function must have signature `fn({name}::Context) -> !`"),
))
}
pub(crate) fn parse_foreign(args: IdleArgs, item: ForeignItemFn) -> parse::Result<Self> {
let valid_signature = util::check_foreign_fn_signature(&item, false)
&& item.sig.inputs.len() == 1
&& util::type_is_bottom(&item.sig.output);
let name = item.sig.ident.to_string();
if valid_signature {
if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) {
if rest.is_empty() {
return Ok(Idle {
args,
attrs: item.attrs,
context,
name: item.sig.ident,
stmts: Vec::<Stmt>::new(),
is_extern: true,
});
}
}

View file

@ -1,6 +1,6 @@
use proc_macro2::TokenStream as TokenStream2;
use syn::{parse, ItemFn};
use syn::{parse, ForeignItemFn, ItemFn, Stmt};
use crate::syntax::{
ast::{Init, InitArgs},
@ -35,6 +35,44 @@ impl Init {
stmts: item.block.stmts,
user_shared_struct,
user_local_struct,
is_extern: false,
});
}
}
}
}
Err(parse::Error::new(
span,
format!(
"the `#[init]` function must have signature `fn({name}::Context) -> (Shared resources struct, Local resources struct)`"
),
))
}
pub(crate) fn parse_foreign(args: InitArgs, item: ForeignItemFn) -> parse::Result<Self> {
let valid_signature =
util::check_foreign_fn_signature(&item, false) && item.sig.inputs.len() == 1;
let span = item.sig.ident.span();
let name = item.sig.ident.to_string();
if valid_signature {
if let Ok((user_shared_struct, user_local_struct)) =
util::type_is_init_return(&item.sig.output)
{
if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) {
if rest.is_empty() {
return Ok(Init {
args,
attrs: item.attrs,
context,
name: item.sig.ident,
stmts: Vec::<Stmt>::new(),
user_shared_struct,
user_local_struct,
is_extern: true,
});
}
}

View file

@ -8,6 +8,7 @@ For each category, *Added*, *Changed*, *Fixed* add new entries at the top!
## [Unreleased]
### Added
- Allow #[init] and #[idle] to be defined externally
### Fixed

View file

@ -6,9 +6,31 @@
#![deny(unsafe_code)]
#![deny(missing_docs)]
use cortex_m_semihosting::hprintln;
use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
use panic_semihosting as _;
// Free function implementing `init`.
fn init(_: app::init::Context) -> (app::Shared, app::Local) {
rtic::pend(Interrupt::UART0);
hprintln!("init");
(app::Shared {}, app::Local {})
}
// Free function implementing `idle`.
fn idle(_: app::idle::Context) -> ! {
hprintln!("idle");
rtic::pend(Interrupt::UART0);
loop {
cortex_m::asm::nop();
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
}
}
// Free function implementing the interrupt bound task `foo`.
fn foo(_: app::foo::Context) {
hprintln!("foo called");
@ -16,38 +38,21 @@ fn foo(_: app::foo::Context) {
#[rtic::app(device = lm3s6965)]
mod app {
use crate::foo;
use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
use crate::{foo, idle, init};
#[shared]
struct Shared {}
pub struct Shared {}
#[local]
struct Local {}
#[init]
fn init(_: init::Context) -> (Shared, Local) {
rtic::pend(Interrupt::UART0);
hprintln!("init");
(Shared {}, Local {})
}
#[idle]
fn idle(_: idle::Context) -> ! {
hprintln!("idle");
rtic::pend(Interrupt::UART0);
loop {
cortex_m::asm::nop();
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
}
}
pub struct Local {}
extern "Rust" {
#[init]
fn init(_: init::Context) -> (Shared, Local);
#[idle]
fn idle(_: idle::Context) -> !;
#[task(binds = UART0)]
fn foo(_: foo::Context);
}