mirror of
https://github.com/rtic-rs/rtic.git
synced 2025-12-16 12:55:23 +01:00
Merge ee5f77326c into 1b49d0d3b5
This commit is contained in:
commit
1217eb768d
15 changed files with 314 additions and 12 deletions
2
ci/expected/lm3s6965/spawn_local.run
Normal file
2
ci/expected/lm3s6965/spawn_local.run
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Hello from task1!
|
||||
Hello from task2!
|
||||
39
examples/lm3s6965/examples/spawn_local.rs
Normal file
39
examples/lm3s6965/examples/spawn_local.rs
Normal 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>);
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
22
rtic/src/export/task_arg.rs
Normal file
22
rtic/src/export/task_arg.rs
Normal 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
|
||||
}
|
||||
}
|
||||
28
rtic/ui/spawn-non-send-different-exec.rs
Normal file
28
rtic/ui/spawn-non-send-different-exec.rs
Normal 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>);
|
||||
8
rtic/ui/spawn-non-send-different-exec.stderr
Normal file
8
rtic/ui/spawn-non-send-different-exec.stderr
Normal 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`
|
||||
24
rtic/ui/spawn-non-send-from-init.rs
Normal file
24
rtic/ui/spawn-non-send-from-init.rs
Normal 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>);
|
||||
51
rtic/ui/spawn-non-send-from-init.stderr
Normal file
51
rtic/ui/spawn-non-send-from-init.stderr
Normal 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)
|
||||
|
|
@ -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) {
|
||||
| ++
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue