Skip to content

Commit 08c0065

Browse files
Merge #767
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](https://github.com/grupacosmo/cansat/blob/98ca7bd42e737adfb140a548da1ade01beb495da/crates/cansat-stm32f4/src/main.rs#L59-L74) ## Related issues * #505 ## Related PRs * rtic-rs/rtic-syntax#71 Co-authored-by: Vixu <[email protected]>
2 parents bd67d2a + 7c9cbda commit 08c0065

File tree

8 files changed

+173
-47
lines changed

8 files changed

+173
-47
lines changed

rtic-macros/src/codegen/idle.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,20 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
3434
let attrs = &idle.attrs;
3535
let context = &idle.context;
3636
let stmts = &idle.stmts;
37-
let user_idle = Some(quote!(
38-
#(#attrs)*
39-
#[allow(non_snake_case)]
40-
fn #name(#context: #name::Context) -> ! {
41-
use rtic::Mutex as _;
42-
use rtic::mutex::prelude::*;
43-
44-
#(#stmts)*
45-
}
46-
));
37+
let user_idle = if !idle.is_extern {
38+
Some(quote!(
39+
#(#attrs)*
40+
#[allow(non_snake_case)]
41+
fn #name(#context: #name::Context) -> ! {
42+
use rtic::Mutex as _;
43+
use rtic::mutex::prelude::*;
44+
45+
#(#stmts)*
46+
}
47+
))
48+
} else {
49+
None
50+
};
4751

4852
quote!(
4953
#(#mod_app)*

rtic-macros/src/codegen/init.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,12 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
5454
.collect();
5555

5656
root_init.push(quote! {
57+
#[doc = r"Shared resources"]
5758
#shared_vis struct #shared {
5859
#(#shared_resources)*
5960
}
6061

62+
#[doc = r"Local resources"]
6163
#local_vis struct #local {
6264
#(#local_resources)*
6365
}
@@ -67,14 +69,18 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
6769

6870
let user_init_return = quote! {#shared, #local};
6971

70-
let user_init = quote!(
71-
#(#attrs)*
72-
#[inline(always)]
73-
#[allow(non_snake_case)]
74-
fn #name(#context: #name::Context) -> (#user_init_return) {
75-
#(#stmts)*
76-
}
77-
);
72+
let user_init = if !init.is_extern {
73+
Some(quote!(
74+
#(#attrs)*
75+
#[inline(always)]
76+
#[allow(non_snake_case)]
77+
fn #name(#context: #name::Context) -> (#user_init_return) {
78+
#(#stmts)*
79+
}
80+
))
81+
} else {
82+
None
83+
};
7884

7985
let mut mod_app = None;
8086

rtic-macros/src/syntax/ast.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ pub struct Init {
9191

9292
/// The name of the user provided local resources struct
9393
pub user_local_struct: Ident,
94+
95+
/// The init function is declared externally
96+
pub is_extern: bool,
9497
}
9598

9699
/// `init` context metadata
@@ -127,6 +130,9 @@ pub struct Idle {
127130

128131
/// The statements that make up this `idle` function
129132
pub stmts: Vec<Stmt>,
133+
134+
/// The idle function is declared externally
135+
pub is_extern: bool,
130136
}
131137

132138
/// `idle` context metadata

rtic-macros/src/syntax/parse/app.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,42 @@ impl App {
365365
if let ForeignItem::Fn(mut item) = item {
366366
let span = item.sig.ident.span();
367367
if let Some(pos) = item
368+
.attrs
369+
.iter()
370+
.position(|attr| util::attr_eq(attr, "init"))
371+
{
372+
let args = InitArgs::parse(item.attrs.remove(pos).tokens)?;
373+
374+
// If an init function already exists, error
375+
if init.is_some() {
376+
return Err(parse::Error::new(
377+
span,
378+
"`#[init]` function must appear at most once",
379+
));
380+
}
381+
382+
check_ident(&item.sig.ident)?;
383+
384+
init = Some(Init::parse_foreign(args, item)?);
385+
} else if let Some(pos) = item
386+
.attrs
387+
.iter()
388+
.position(|attr| util::attr_eq(attr, "idle"))
389+
{
390+
let args = IdleArgs::parse(item.attrs.remove(pos).tokens)?;
391+
392+
// If an idle function already exists, error
393+
if idle.is_some() {
394+
return Err(parse::Error::new(
395+
span,
396+
"`#[idle]` function must appear at most once",
397+
));
398+
}
399+
400+
check_ident(&item.sig.ident)?;
401+
402+
idle = Some(Idle::parse_foreign(args, item)?);
403+
} else if let Some(pos) = item
368404
.attrs
369405
.iter()
370406
.position(|attr| util::attr_eq(attr, "task"))
@@ -408,7 +444,8 @@ impl App {
408444
} else {
409445
return Err(parse::Error::new(
410446
span,
411-
"`extern` task required `#[task(..)]` attribute",
447+
"`extern` task, init or idle must have either `#[task(..)]`,
448+
`#[init(..)]` or `#[idle(..)]` attribute",
412449
));
413450
}
414451
} else {

rtic-macros/src/syntax/parse/idle.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use proc_macro2::TokenStream as TokenStream2;
2-
use syn::{parse, ItemFn};
2+
use syn::{parse, ForeignItemFn, ItemFn, Stmt};
33

44
use crate::syntax::{
55
ast::{Idle, IdleArgs},
@@ -29,6 +29,35 @@ impl Idle {
2929
context,
3030
name: item.sig.ident,
3131
stmts: item.block.stmts,
32+
is_extern: false,
33+
});
34+
}
35+
}
36+
}
37+
38+
Err(parse::Error::new(
39+
item.sig.ident.span(),
40+
format!("this `#[idle]` function must have signature `fn({name}::Context) -> !`"),
41+
))
42+
}
43+
44+
pub(crate) fn parse_foreign(args: IdleArgs, item: ForeignItemFn) -> parse::Result<Self> {
45+
let valid_signature = util::check_foreign_fn_signature(&item, false)
46+
&& item.sig.inputs.len() == 1
47+
&& util::type_is_bottom(&item.sig.output);
48+
49+
let name = item.sig.ident.to_string();
50+
51+
if valid_signature {
52+
if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) {
53+
if rest.is_empty() {
54+
return Ok(Idle {
55+
args,
56+
attrs: item.attrs,
57+
context,
58+
name: item.sig.ident,
59+
stmts: Vec::<Stmt>::new(),
60+
is_extern: true,
3261
});
3362
}
3463
}

rtic-macros/src/syntax/parse/init.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use proc_macro2::TokenStream as TokenStream2;
22

3-
use syn::{parse, ItemFn};
3+
use syn::{parse, ForeignItemFn, ItemFn, Stmt};
44

55
use crate::syntax::{
66
ast::{Init, InitArgs},
@@ -35,6 +35,44 @@ impl Init {
3535
stmts: item.block.stmts,
3636
user_shared_struct,
3737
user_local_struct,
38+
is_extern: false,
39+
});
40+
}
41+
}
42+
}
43+
}
44+
45+
Err(parse::Error::new(
46+
span,
47+
format!(
48+
"the `#[init]` function must have signature `fn({name}::Context) -> (Shared resources struct, Local resources struct)`"
49+
),
50+
))
51+
}
52+
53+
pub(crate) fn parse_foreign(args: InitArgs, item: ForeignItemFn) -> parse::Result<Self> {
54+
let valid_signature =
55+
util::check_foreign_fn_signature(&item, false) && item.sig.inputs.len() == 1;
56+
57+
let span = item.sig.ident.span();
58+
59+
let name = item.sig.ident.to_string();
60+
61+
if valid_signature {
62+
if let Ok((user_shared_struct, user_local_struct)) =
63+
util::type_is_init_return(&item.sig.output)
64+
{
65+
if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) {
66+
if rest.is_empty() {
67+
return Ok(Init {
68+
args,
69+
attrs: item.attrs,
70+
context,
71+
name: item.sig.ident,
72+
stmts: Vec::<Stmt>::new(),
73+
user_shared_struct,
74+
user_local_struct,
75+
is_extern: true,
3876
});
3977
}
4078
}

rtic/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ For each category, *Added*, *Changed*, *Fixed* add new entries at the top!
88
## [Unreleased]
99

1010
### Added
11+
- Allow #[init] and #[idle] to be defined externally
1112

1213
### Fixed
1314

rtic/examples/extern_binds.rs

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,48 +6,53 @@
66
#![deny(unsafe_code)]
77
#![deny(missing_docs)]
88

9-
use cortex_m_semihosting::hprintln;
9+
use cortex_m_semihosting::{debug, hprintln};
10+
use lm3s6965::Interrupt;
1011
use panic_semihosting as _;
1112

13+
// Free function implementing `init`.
14+
fn init(_: app::init::Context) -> (app::Shared, app::Local) {
15+
rtic::pend(Interrupt::UART0);
16+
17+
hprintln!("init");
18+
19+
(app::Shared {}, app::Local {})
20+
}
21+
22+
// Free function implementing `idle`.
23+
fn idle(_: app::idle::Context) -> ! {
24+
hprintln!("idle");
25+
26+
rtic::pend(Interrupt::UART0);
27+
28+
loop {
29+
cortex_m::asm::nop();
30+
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
31+
}
32+
}
33+
1234
// Free function implementing the interrupt bound task `foo`.
1335
fn foo(_: app::foo::Context) {
1436
hprintln!("foo called");
1537
}
1638

1739
#[rtic::app(device = lm3s6965)]
1840
mod app {
19-
use crate::foo;
20-
use cortex_m_semihosting::{debug, hprintln};
21-
use lm3s6965::Interrupt;
41+
use crate::{foo, idle, init};
2242

2343
#[shared]
24-
struct Shared {}
44+
pub struct Shared {}
2545

2646
#[local]
27-
struct Local {}
47+
pub struct Local {}
2848

29-
#[init]
30-
fn init(_: init::Context) -> (Shared, Local) {
31-
rtic::pend(Interrupt::UART0);
32-
33-
hprintln!("init");
34-
35-
(Shared {}, Local {})
36-
}
37-
38-
#[idle]
39-
fn idle(_: idle::Context) -> ! {
40-
hprintln!("idle");
41-
42-
rtic::pend(Interrupt::UART0);
49+
extern "Rust" {
50+
#[init]
51+
fn init(_: init::Context) -> (Shared, Local);
4352

44-
loop {
45-
cortex_m::asm::nop();
46-
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
47-
}
48-
}
53+
#[idle]
54+
fn idle(_: idle::Context) -> !;
4955

50-
extern "Rust" {
5156
#[task(binds = UART0)]
5257
fn foo(_: foo::Context);
5358
}

0 commit comments

Comments
 (0)