diff --git a/rtic-macros/src/codegen/idle.rs b/rtic-macros/src/codegen/idle.rs index 0c833ef391..9e608cb924 100644 --- a/rtic-macros/src/codegen/idle.rs +++ b/rtic-macros/src/codegen/idle.rs @@ -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)* diff --git a/rtic-macros/src/codegen/init.rs b/rtic-macros/src/codegen/init.rs index b667ae0fa5..f6bb43d661 100644 --- a/rtic-macros/src/codegen/init.rs +++ b/rtic-macros/src/codegen/init.rs @@ -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; diff --git a/rtic-macros/src/syntax/ast.rs b/rtic-macros/src/syntax/ast.rs index 3f4956cc9b..f0067b8ece 100644 --- a/rtic-macros/src/syntax/ast.rs +++ b/rtic-macros/src/syntax/ast.rs @@ -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, + + /// The idle function is declared externally + pub is_extern: bool, } /// `idle` context metadata diff --git a/rtic-macros/src/syntax/parse/app.rs b/rtic-macros/src/syntax/parse/app.rs index 2f2d816522..d75c8c60fa 100644 --- a/rtic-macros/src/syntax/parse/app.rs +++ b/rtic-macros/src/syntax/parse/app.rs @@ -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 { diff --git a/rtic-macros/src/syntax/parse/idle.rs b/rtic-macros/src/syntax/parse/idle.rs index 124c136684..a3d971559c 100644 --- a/rtic-macros/src/syntax/parse/idle.rs +++ b/rtic-macros/src/syntax/parse/idle.rs @@ -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 { + 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::::new(), + is_extern: true, }); } } diff --git a/rtic-macros/src/syntax/parse/init.rs b/rtic-macros/src/syntax/parse/init.rs index 0aea20bd32..59d00937ce 100644 --- a/rtic-macros/src/syntax/parse/init.rs +++ b/rtic-macros/src/syntax/parse/init.rs @@ -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 { + 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::::new(), + user_shared_struct, + user_local_struct, + is_extern: true, }); } } diff --git a/rtic/CHANGELOG.md b/rtic/CHANGELOG.md index 2d0a392f1a..b500188a31 100644 --- a/rtic/CHANGELOG.md +++ b/rtic/CHANGELOG.md @@ -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 diff --git a/rtic/examples/extern_binds.rs b/rtic/examples/extern_binds.rs index f03a8a9c45..45939d29ec 100644 --- a/rtic/examples/extern_binds.rs +++ b/rtic/examples/extern_binds.rs @@ -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); }