Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions examples/embassy-stm32g4/src/bin/spawn_local.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#![no_main]
#![no_std]

use core::marker::PhantomData;
use rtic::app;
use {defmt_rtt as _, panic_probe as _};
pub mod pac {
pub use embassy_stm32::pac::Interrupt as interrupt;
pub use embassy_stm32::pac::*;
}

#[app(device = pac, peripherals = false, dispatchers = [SPI1])]
mod app {
use super::*;

#[shared]
struct Shared {}

#[local]
struct Local {}

#[init]
fn init(_cx: init::Context) -> (Shared, Local) {
task1::spawn().ok();
//task2::spawn(Default::default()).ok(); <--- This is rejected since it is a local task
(Shared {}, Local {})
}

#[task(priority = 1)]
async fn task1(cx: task1::Context) {
defmt::info!("Hello from task1!");
cx.local_spawner.task2(Default::default()).ok();
}

#[task(priority = 1, is_local_task = true)]
async fn task2(_cx: task2::Context, _nsns: super::NotSendNotSync) {
defmt::info!("Hello from task1!");
}
}

#[derive(Default)]
struct NotSendNotSync(PhantomData<*mut u8>);
1 change: 1 addition & 0 deletions rtic-macros/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ For each category, *Added*, *Changed*, *Fixed* add new entries at the top!
### Added

- Outer attributes applied to RTIC app module are now forwarded to the generated code.
- Local spawner for spawning tasks with !Send/!Sync args on the same executor

## [v2.2.0] - 2025-06-22

Expand Down
142 changes: 105 additions & 37 deletions rtic-macros/src/codegen/module.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::syntax::{ast::App, Context};
use crate::{analyze::Analysis, codegen::bindings::interrupt_mod, codegen::util};

use proc_macro2::TokenStream as TokenStream2;
use quote::quote;

Expand Down Expand Up @@ -112,37 +113,7 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
let internal_context_name = util::internal_task_ident(name, "Context");
let exec_name = util::internal_task_ident(name, "EXEC");

items.push(quote!(
#(#cfgs)*
/// Execution context
#[allow(non_snake_case)]
#[allow(non_camel_case_types)]
pub struct #internal_context_name<'a> {
#[doc(hidden)]
__rtic_internal_p: ::core::marker::PhantomData<&'a ()>,
#(#fields,)*
}

#(#cfgs)*
impl<'a> #internal_context_name<'a> {
#[inline(always)]
#[allow(missing_docs)]
pub unsafe fn new(#core) -> Self {
#internal_context_name {
__rtic_internal_p: ::core::marker::PhantomData,
#(#values,)*
}
}
}
));

module_items.push(quote!(
#(#cfgs)*
#[doc(inline)]
pub use super::#internal_context_name as Context;
));

if let Context::SoftwareTask(..) = ctxt {
if let Context::SoftwareTask(t) = ctxt {
let spawnee = &app.software_tasks[name];
let priority = spawnee.args.priority;
let cfgs = &spawnee.cfgs;
Expand All @@ -163,13 +134,21 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
let (input_args, input_tupled, input_untupled, input_ty) =
util::regroup_inputs(&spawnee.inputs);

let is_local_task = app.software_tasks[t].args.is_local_task;
let unsafety = if is_local_task {
// local tasks are only safe to call from the same executor
quote! { unsafe }
} else {
quote! {}
};

// Spawn caller
items.push(quote!(
#(#cfgs)*
/// Spawns the task directly
#[allow(non_snake_case)]
#[doc(hidden)]
pub fn #internal_spawn_ident(#(#input_args,)*) -> ::core::result::Result<(), #input_ty> {
pub #unsafety fn #internal_spawn_ident(#(#input_args,)*) -> ::core::result::Result<(), #input_ty> {
// SAFETY: If `try_allocate` succeeds one must call `spawn`, which we do.
unsafe {
let exec = rtic::export::executor::AsyncTaskExecutor::#from_ptr_n_args(#name, &#exec_name);
Expand Down Expand Up @@ -204,11 +183,70 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
}
));

module_items.push(quote!(
#(#cfgs)*
#[doc(inline)]
pub use super::#internal_spawn_ident as spawn;
));
if !is_local_task {
module_items.push(quote!(
#(#cfgs)*
#[doc(inline)]
pub use super::#internal_spawn_ident as spawn;
));
}

let local_tasks_on_same_executor: Vec<_> = app
.software_tasks
.iter()
.filter(|(_, t)| t.args.is_local_task && t.args.priority == priority)
.collect();

if !local_tasks_on_same_executor.is_empty() {
let local_spawner = util::internal_task_ident(t, "LocalSpawner");
fields.push(quote! {
/// Used to spawn tasks on the same executor
///
/// This is useful for tasks that take args which are !Send/!Sync.
///
/// NOTE: This only works with tasks marked `is_local_task = true`
/// and which have the same priority and thus will run on the
/// same executor.
pub local_spawner: #local_spawner
});
let tasks = local_tasks_on_same_executor
.iter()
.map(|(ident, task)| {
// Copied mostly from software_tasks.rs
let internal_spawn_ident = util::internal_task_ident(ident, "spawn");
let attrs = &task.attrs;
let cfgs = &task.cfgs;
let inputs = &task.inputs;
let generics = if task.is_bottom {
quote!()
} else {
quote!(<'a>)
};
let input_vals = inputs.iter().map(|i| &i.pat).collect::<Vec<_>>();
let (_input_args, _input_tupled, _input_untupled, input_ty) = util::regroup_inputs(&task.inputs);
quote! {
#(#attrs)*
#(#cfgs)*
#[allow(non_snake_case)]
pub(super) fn #ident #generics(&self #(,#inputs)*) -> ::core::result::Result<(), #input_ty> {
// SAFETY: This is safe to call since this can only be called
// from the same executor
unsafe { #internal_spawn_ident(#(#input_vals,)*) }
}
}
})
.collect::<Vec<_>>();
values.push(quote!(local_spawner: #local_spawner { _p: core::marker::PhantomData }));
items.push(quote! {
struct #local_spawner {
_p: core::marker::PhantomData<*mut ()>,
}

impl #local_spawner {
#(#tasks)*
}
});
}

module_items.push(quote!(
#(#cfgs)*
Expand All @@ -217,6 +255,36 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
));
}

items.push(quote!(
#(#cfgs)*
/// Execution context
#[allow(non_snake_case)]
#[allow(non_camel_case_types)]
pub struct #internal_context_name<'a> {
#[doc(hidden)]
__rtic_internal_p: ::core::marker::PhantomData<&'a ()>,
#(#fields,)*
}

#(#cfgs)*
impl<'a> #internal_context_name<'a> {
#[inline(always)]
#[allow(missing_docs)]
pub unsafe fn new(#core) -> Self {
#internal_context_name {
__rtic_internal_p: ::core::marker::PhantomData,
#(#values,)*
}
}
}
));

module_items.push(quote!(
#(#cfgs)*
#[doc(inline)]
pub use super::#internal_context_name as Context;
));

if items.is_empty() {
quote!()
} else {
Expand Down
12 changes: 10 additions & 2 deletions rtic-macros/src/codegen/software_tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,16 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
let cfgs = &task.cfgs;
let stmts = &task.stmts;
let inputs = &task.inputs;
let lifetime = if task.is_bottom { quote!('static) } else { quote!('a) };
let generics = if task.is_bottom { quote!() } else { quote!(<'a>) };
let lifetime = if task.is_bottom {
quote!('static)
} else {
quote!('a)
};
let generics = if task.is_bottom {
quote!()
} else {
quote!(<'a>)
};

user_tasks.push(quote!(
#(#attrs)*
Expand Down
11 changes: 7 additions & 4 deletions rtic-macros/src/syntax/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,13 +285,16 @@ pub(crate) fn app(app: &App) -> Result<Analysis, syn::Error> {
for (name, spawnee) in &app.software_tasks {
let spawnee_prio = spawnee.args.priority;

// TODO: What is this?
let channel = channels.entry(spawnee_prio).or_default();
channel.tasks.insert(name.clone());

// All inputs are send as we do not know from where they may be spawned.
spawnee.inputs.iter().for_each(|input| {
send_types.insert(input.ty.clone());
});
if !spawnee.args.is_local_task {
// All inputs are send as we do not know from where they may be spawned.
spawnee.inputs.iter().for_each(|input| {
send_types.insert(input.ty.clone());
});
}
}

// No channel should ever be empty
Expand Down
7 changes: 7 additions & 0 deletions rtic-macros/src/syntax/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,12 @@ pub struct SoftwareTaskArgs {

/// Shared resources that can be accessed from this context
pub shared_resources: SharedResources,

/// Local tasks
///
/// Local tasks can only be spawned from the same executor.
/// However they do not require Send and Sync
pub is_local_task: bool,
}

impl Default for SoftwareTaskArgs {
Expand All @@ -264,6 +270,7 @@ impl Default for SoftwareTaskArgs {
priority: 0,
local_resources: LocalResources::new(),
shared_resources: SharedResources::new(),
is_local_task: false,
}
}
}
Expand Down
21 changes: 17 additions & 4 deletions rtic-macros/src/syntax/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ mod util;

use proc_macro2::TokenStream as TokenStream2;
use syn::{
braced,
parse::{self, Parse, ParseStream, Parser},
token::Brace,
Attribute, Ident, Item, LitInt, Meta, Token,
braced, parse::{self, Parse, ParseStream, Parser}, token::Brace, Attribute, Ident, Item, LitBool, LitInt, Meta, Token
};

use crate::syntax::{
Expand Down Expand Up @@ -197,6 +194,7 @@ fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, Sof
let mut shared_resources = None;
let mut local_resources = None;
let mut prio_span = None;
let mut is_local_task = None;

loop {
if input.is_empty() {
Expand Down Expand Up @@ -277,6 +275,19 @@ fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, Sof
local_resources = Some(util::parse_local_resources(input)?);
}

"is_local_task" => {
if is_local_task.is_some() {
return Err(parse::Error::new(
ident.span(),
"argument appears more than once",
));
}

let lit: LitBool = input.parse()?;

is_local_task = Some(lit.value);
}

_ => {
return Err(parse::Error::new(ident.span(), "unexpected argument"));
}
Expand All @@ -291,6 +302,7 @@ fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, Sof
}
let shared_resources = shared_resources.unwrap_or_default();
let local_resources = local_resources.unwrap_or_default();
let is_local_task = is_local_task.unwrap_or(false);

Ok(if let Some(binds) = binds {
// Hardware tasks can't run at anything lower than 1
Expand All @@ -317,6 +329,7 @@ fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, Sof
priority,
shared_resources,
local_resources,
is_local_task,
})
})
})
Expand Down
Loading