This commit is contained in:
Albin Hedman 2025-12-03 21:05:34 +01:00 committed by GitHub
commit 1217eb768d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 314 additions and 12 deletions

View file

@ -0,0 +1,2 @@
Hello from task1!
Hello from task2!

View file

@ -0,0 +1,39 @@
#![no_main]
#![no_std]
use panic_semihosting as _;
#[rtic::app(device = lm3s6965, dispatchers = [SSI0])]
mod app {
use cortex_m_semihosting::{debug, hprintln};
use super::*;
#[shared]
struct Shared {}
#[local]
struct Local {}
#[init]
fn init(_cx: init::Context) -> (Shared, Local) {
task1::spawn().unwrap();
//task2::spawn(Default::default()).ok(); <--- This is rejected since not all args are Send and Sync
(Shared {}, Local {})
}
#[task(priority = 1)]
async fn task1(cx: task1::Context) {
hprintln!("Hello from task1!");
cx.local_spawner.task2(Default::default()).unwrap();
}
// Task where some args are !Send/!Sync
#[task(priority = 1)]
async fn task2(_cx: task2::Context, _nsns: NotSendNotSync) {
hprintln!("Hello from task2!");
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
}
}
#[derive(Default, Debug)]
struct NotSendNotSync(core::marker::PhantomData<*mut u8>);

View file

@ -28,8 +28,8 @@ mod app {
fn init(cx: init::Context) -> (Shared, Local) {
Mono::start(cx.core.SYST, 12_000_000);
incrementer::spawn(cx.local.wait_queue).ok().unwrap();
waiter::spawn(cx.local.wait_queue).ok().unwrap();
incrementer::spawn(&*cx.local.wait_queue).ok().unwrap();
waiter::spawn(&*cx.local.wait_queue).ok().unwrap();
let count = 0;

View file

@ -14,6 +14,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.
- Allow tasks with !Send/!Sync to be spawned from the same priority level task using local spawner
## [v2.2.0] - 2025-06-22

View file

@ -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;
@ -112,6 +113,17 @@ 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");
if let Context::SoftwareTask(t) = ctxt {
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.
pub local_spawner: #local_spawner
});
values.push(quote!(local_spawner: #local_spawner { _p: core::marker::PhantomData }));
}
items.push(quote!(
#(#cfgs)*
/// Execution context
@ -142,7 +154,7 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
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;
@ -158,18 +170,21 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
};
let internal_spawn_ident = util::internal_task_ident(name, "spawn");
let internal_spawn_helper_ident = util::internal_task_ident(name, "spawn_helper");
let internal_waker_ident = util::internal_task_ident(name, "waker");
let from_ptr_n_args = util::from_ptr_n_args_ident(spawnee.inputs.len());
let (input_args, input_tupled, input_untupled, input_ty) =
let (generic_input_args, input_args, input_tupled, input_untupled, input_ty) =
util::regroup_inputs(&spawnee.inputs);
// Spawn caller
items.push(quote!(
#(#cfgs)*
/// Spawns the task directly
/// Spawns the task without checking if the spawner and spawnee are the same priority
///
/// SAFETY: The caller needs to check that the spawner and spawnee are the same priority
#[allow(non_snake_case)]
#[doc(hidden)]
pub fn #internal_spawn_ident(#(#input_args,)*) -> ::core::result::Result<(), #input_ty> {
pub unsafe fn #internal_spawn_helper_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);
@ -183,6 +198,14 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
}
}
}
/// Spawns the task directly
#[allow(non_snake_case)]
#[doc(hidden)]
pub fn #internal_spawn_ident(#(#generic_input_args,)*) -> ::core::result::Result<(), #input_ty> {
// SAFETY: The generic args require Send + Sync
unsafe { #internal_spawn_helper_ident(#(#input_untupled.to()),*) }
}
));
// Waker
@ -210,6 +233,49 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
pub use super::#internal_spawn_ident as spawn;
));
let tasks_on_same_executor: Vec<_> = app
.software_tasks
.iter()
.filter(|(_, t)| t.args.priority == priority)
.collect();
let local_spawner = util::internal_task_ident(t, "LocalSpawner");
let tasks = tasks_on_same_executor
.iter()
.map(|(ident, task)| {
// Copied mostly from software_tasks.rs
let internal_spawn_ident = util::internal_task_ident(ident, "spawn_helper");
let attrs = &task.attrs;
let cfgs = &task.cfgs;
let generics = if task.is_bottom {
quote!()
} else {
quote!(<'a>)
};
let (_generic_input_args, 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 #(,#input_args)*) -> ::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_untupled,)*) }
}
}
})
.collect::<Vec<_>>();
items.push(quote! {
struct #local_spawner {
_p: core::marker::PhantomData<*mut ()>,
}
impl #local_spawner {
#(#tasks)*
}
});
module_items.push(quote!(
#(#cfgs)*
#[doc(inline)]

View file

@ -35,6 +35,8 @@ pub fn link_section_uninit() -> TokenStream2 {
pub fn regroup_inputs(
inputs: &[PatType],
) -> (
// Generic args e.g. &[`_0: impl TaskArg<T=i32>`, `_1: impl TaskArg<T=i64>`]
Vec<TokenStream2>,
// args e.g. &[`_0`], &[`_0: i32`, `_1: i64`]
Vec<TokenStream2>,
// tupled e.g. `_0`, `(_0, _1)`
@ -48,12 +50,14 @@ pub fn regroup_inputs(
let ty = &inputs[0].ty;
(
vec![quote!(_0: impl rtic::export::task_arg::TaskArg<T=#ty> + Send + Sync)],
vec![quote!(_0: #ty)],
quote!(_0),
vec![quote!(_0)],
quote!(#ty),
)
} else {
let mut generic_args = vec![];
let mut args = vec![];
let mut pats = vec![];
let mut tys = vec![];
@ -62,6 +66,8 @@ pub fn regroup_inputs(
let i = Ident::new(&format!("_{i}"), Span::call_site());
let ty = &input.ty;
generic_args.push(quote!(#i: impl rtic::export::task_arg::TaskArg<T=#ty> + Send + Sync));
args.push(quote!(#i: #ty));
pats.push(quote!(#i));
@ -74,7 +80,7 @@ pub fn regroup_inputs(
quote!((#(#pats,)*))
};
let ty = quote!((#(#tys,)*));
(args, tupled, pats, ty)
(generic_args, args, tupled, pats, ty)
}
}

View file

@ -287,11 +287,6 @@ pub(crate) fn app(app: &App) -> Result<Analysis, syn::Error> {
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());
});
}
// No channel should ever be empty

View file

@ -22,6 +22,7 @@ Example:
### Added
- Add attribute `local_task` for tasks that may take args that are !Send/!Sync and can only be spawned from same executor
- Outer attributes applied to RTIC app module are now forwarded to the generated code.
### Changed

View file

@ -2,6 +2,7 @@ pub use critical_section::CriticalSection;
pub use portable_atomic as atomic;
pub mod executor;
pub mod task_arg;
// Cortex-M target (any)
#[cfg(feature = "cortex-m")]

View file

@ -0,0 +1,22 @@
/// A trait for types that can be passed as arguments when spawning tasks
///
/// This trait will only ever be implemented where type `Self::T` is `Self`
///
/// The global `my_task::spawn` requires its args to be `Send`. This trait has to
/// be used because we can not have a function with a where clause which
/// requires a concrete type to be `Send` if that type is not `Send`. The compiler
/// will error out on us. However hiding that behind a dummy trait which is
/// only implemented for that same type enables us to defer the error to when
/// the user erroneously tries to call the function.
pub trait TaskArg {
/// This should always be same as `Self`
type T;
fn to(self) -> Self::T;
}
impl<T> TaskArg for T {
type T = T;
fn to(self) -> T {
self
}
}

View file

@ -0,0 +1,28 @@
#![no_main]
#[rtic::app(device = lm3s6965, dispatchers = [SSI0, GPIOA])]
mod app {
use super::*;
#[shared]
struct Shared {}
#[local]
struct Local {}
#[init]
fn init(_cx: init::Context) -> (Shared, Local) {
(Shared {}, Local {})
}
#[task(priority = 1)]
async fn foo(_cx: foo::Context, _nsns: NotSendNotSync) {}
#[task(priority = 2)]
async fn bar(cx: bar::Context) {
cx.local_spawner.foo(Default::default()).ok();
}
}
#[derive(Default, Debug)]
struct NotSendNotSync(core::marker::PhantomData<*mut u8>);

View file

@ -0,0 +1,8 @@
error[E0599]: no method named `foo` found for struct `__rtic_internal_bar_LocalSpawner` in the current scope
--> ui/spawn-non-send-different-exec.rs:23:26
|
3 | #[rtic::app(device = lm3s6965, dispatchers = [SSI0, GPIOA])]
| ------------------------------------------------------------ method `foo` not found for this struct
...
23 | cx.local_spawner.foo(Default::default()).ok();
| ^^^ method not found in `__rtic_internal_bar_LocalSpawner`

View file

@ -0,0 +1,24 @@
#![no_main]
#[rtic::app(device = lm3s6965, dispatchers = [SSI0])]
mod app {
use super::*;
#[shared]
struct Shared {}
#[local]
struct Local {}
#[init]
fn init(_cx: init::Context) -> (Shared, Local) {
foo::spawn(NotSendNotSync::default()).ok();
(Shared {}, Local {})
}
#[task(priority = 1)]
async fn foo(_cx: foo::Context, _nsns: NotSendNotSync) {}
}
#[derive(Default, Debug)]
struct NotSendNotSync(core::marker::PhantomData<*mut u8>);

View file

@ -0,0 +1,51 @@
error[E0277]: `*mut u8` cannot be sent between threads safely
--> ui/spawn-non-send-from-init.rs:15:20
|
15 | foo::spawn(NotSendNotSync::default()).ok();
| ---------- ^^^^^^^^^^^^^^^^^^^^^^^^^ `*mut u8` cannot be sent between threads safely
| |
| required by a bound introduced by this call
|
= help: within `NotSendNotSync`, the trait `Send` is not implemented for `*mut u8`
note: required because it appears within the type `PhantomData<*mut u8>`
--> $RUST/core/src/marker.rs
|
| pub struct PhantomData<T: PointeeSized>;
| ^^^^^^^^^^^
note: required because it appears within the type `NotSendNotSync`
--> ui/spawn-non-send-from-init.rs:24:8
|
24 | struct NotSendNotSync(core::marker::PhantomData<*mut u8>);
| ^^^^^^^^^^^^^^
note: required by a bound in `__rtic_internal_foo_spawn`
--> ui/spawn-non-send-from-init.rs:3:1
|
3 | #[rtic::app(device = lm3s6965, dispatchers = [SSI0])]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `__rtic_internal_foo_spawn`
= note: this error originates in the attribute macro `rtic::app` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: `*mut u8` cannot be shared between threads safely
--> ui/spawn-non-send-from-init.rs:15:20
|
15 | foo::spawn(NotSendNotSync::default()).ok();
| ---------- ^^^^^^^^^^^^^^^^^^^^^^^^^ `*mut u8` cannot be shared between threads safely
| |
| required by a bound introduced by this call
|
= help: within `NotSendNotSync`, the trait `Sync` is not implemented for `*mut u8`
note: required because it appears within the type `PhantomData<*mut u8>`
--> $RUST/core/src/marker.rs
|
| pub struct PhantomData<T: PointeeSized>;
| ^^^^^^^^^^^
note: required because it appears within the type `NotSendNotSync`
--> ui/spawn-non-send-from-init.rs:24:8
|
24 | struct NotSendNotSync(core::marker::PhantomData<*mut u8>);
| ^^^^^^^^^^^^^^
note: required by a bound in `__rtic_internal_foo_spawn`
--> ui/spawn-non-send-from-init.rs:3:1
|
3 | #[rtic::app(device = lm3s6965, dispatchers = [SSI0])]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `__rtic_internal_foo_spawn`
= note: this error originates in the attribute macro `rtic::app` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,3 +1,43 @@
error[E0106]: missing lifetime specifier
--> ui/task-reference-in-spawn.rs:26:68
|
26 | async fn high_prio_print(_: high_prio_print::Context, mut_ref: &mut usize) {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
|
26 | async fn high_prio_print(_: high_prio_print::Context, mut_ref: &'static mut usize) {
| +++++++
help: consider introducing a named lifetime parameter
|
3 ~ #[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0, GPIOA])]<'a>
4 | mod app {
...
25 | #[task(priority = 3)]
26 ~ async fn high_prio_print(_: high_prio_print::Context, mut_ref: &'a usize) {
|
help: alternatively, you might want to return an owned value
|
26 - async fn high_prio_print(_: high_prio_print::Context, mut_ref: &mut usize) {
26 + async fn high_prio_print(_: high_prio_print::Context, mut_ref: usize) {
|
error[E0658]: anonymous lifetimes in `impl Trait` are unstable
--> ui/task-reference-in-spawn.rs:26:69
|
26 | async fn high_prio_print(_: high_prio_print::Context, mut_ref: &mut usize) {
| ^ expected named lifetime parameter
|
help: consider introducing a named lifetime parameter
|
3 ~ #[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0, GPIOA])]<'a>
4 | mod app {
...
25 | #[task(priority = 3)]
26 ~ async fn high_prio_print(_: high_prio_print::Context, mut_ref: &'a mut usize) {
|
error[E0521]: borrowed data escapes outside of function
--> ui/task-reference-in-spawn.rs:3:1
|
@ -12,3 +52,21 @@ error[E0521]: borrowed data escapes outside of function
| - let's call the lifetime of this reference `'1`
|
= note: this error originates in the attribute macro `rtic::app` (in Nightly builds, run with -Z macro-backtrace for more info)
error: lifetime may not live long enough
--> ui/task-reference-in-spawn.rs:3:1
|
3 | #[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0, GPIOA])]
| -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| let's call the lifetime of this reference `'2`
| method was supposed to return data with lifetime `'2` but it is returning data with lifetime `'1`
...
26 | async fn high_prio_print(_: high_prio_print::Context, mut_ref: &mut usize) {
| - let's call the lifetime of this reference `'1`
|
= note: this error originates in the attribute macro `rtic::app` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider reusing a named lifetime parameter and update trait if needed
|
26 | async fn high_prio_print(_: high_prio_print::Context, mut_ref: &'a mut usize) {
| ++