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
2 changes: 2 additions & 0 deletions ci/expected/lm3s6965/spawn_local.run
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Hello from task1!
Hello from task2!
39 changes: 39 additions & 0 deletions examples/lm3s6965/examples/spawn_local.rs
Original file line number Diff line number Diff line change
@@ -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>);
4 changes: 2 additions & 2 deletions examples/lm3s6965/examples/wait-queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Comment on lines -31 to +32
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change. For some reason the compiler can not auto demote &mut T to &T when generics are involved (not sure of the rules here).

Without the above fix, we get

error[E0271]: type mismatch resolving `<&mut DoublyLinkedList<Waker> as Dummy>::T == &DoublyLinkedList<Waker>`
  --> examples/wait-queue.rs:31:28
   |
31 |         incrementer::spawn(cx.local.wait_queue).ok().unwrap();
   |         ------------------ ^^^^^^^^^^^^^^^^^^^ type mismatch resolving `<&mut DoublyLinkedList<Waker> as Dummy>::T == &DoublyLinkedList<Waker>`
   |         |
   |         required by a bound introduced by this call
   |
note: expected this to be `&'static DoublyLinkedList<Waker>`
  --> examples/wait-queue.rs:11:1

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


let count = 0;

Expand Down
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.
- Allow tasks with !Send/!Sync to be spawned from the same priority level task using local spawner

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

Expand Down
74 changes: 70 additions & 4 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,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
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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)]
Expand Down
8 changes: 7 additions & 1 deletion rtic-macros/src/codegen/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)`
Expand All @@ -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![];
Expand All @@ -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));
Expand All @@ -74,7 +80,7 @@ pub fn regroup_inputs(
quote!((#(#pats,)*))
};
let ty = quote!((#(#tys,)*));
(args, tupled, pats, ty)
(generic_args, args, tupled, pats, ty)
}
}

Expand Down
5 changes: 0 additions & 5 deletions rtic-macros/src/syntax/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions rtic/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions rtic/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
22 changes: 22 additions & 0 deletions rtic/src/export/task_arg.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
28 changes: 28 additions & 0 deletions rtic/ui/spawn-non-send-different-exec.rs
Original file line number Diff line number Diff line change
@@ -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>);
8 changes: 8 additions & 0 deletions rtic/ui/spawn-non-send-different-exec.stderr
Original file line number Diff line number Diff line change
@@ -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`
24 changes: 24 additions & 0 deletions rtic/ui/spawn-non-send-from-init.rs
Original file line number Diff line number Diff line change
@@ -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>);
51 changes: 51 additions & 0 deletions rtic/ui/spawn-non-send-from-init.stderr
Original file line number Diff line number Diff line change
@@ -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)
Loading
Loading