Skip to content

Commit 9feaf99

Browse files
committed
Do not require attribute for 'local tasks'
1 parent 7f7c0d4 commit 9feaf99

File tree

12 files changed

+67
-74
lines changed

12 files changed

+67
-74
lines changed

examples/lm3s6965/examples/spawn_local.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ mod app {
1717
#[init]
1818
fn init(_cx: init::Context) -> (Shared, Local) {
1919
task1::spawn().unwrap();
20-
//task2::spawn(Default::default()).ok(); <--- This is rejected since it is a local task
20+
//task2::spawn(Default::default()).ok(); <--- This is rejected since not all args are Send and Sync
2121
(Shared {}, Local {})
2222
}
2323

@@ -27,7 +27,8 @@ mod app {
2727
cx.local_spawner.task2(Default::default()).unwrap();
2828
}
2929

30-
#[task(priority = 1, local_task = true)]
30+
// Task where some args are !Send/!Sync
31+
#[task(priority = 1)]
3132
async fn task2(_cx: task2::Context, _nsns: NotSendNotSync) {
3233
hprintln!("Hello from task2!");
3334
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator

rtic-macros/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ For each category, *Added*, *Changed*, *Fixed* add new entries at the top!
1010
### Added
1111

1212
- Outer attributes applied to RTIC app module are now forwarded to the generated code.
13-
- Add attribute `local_task` for tasks that may take args that are !Send/!Sync and can only be spawned from same executor
13+
- Allow tasks with !Send/!Sync to be spawned from the same priority level task using local spawner
1414

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

rtic-macros/src/codegen.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,23 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 {
7777

7878
#async_dispatchers_codegen
7979

80+
// TODO: What should we name this and where should we place it?
81+
/// Dummy trait which will ever be implemented as where type T is Self
82+
pub trait Dummy {
83+
/// This should always be same as Self
84+
type T;
85+
86+
/// Convert to Self::T
87+
fn to(self) -> Self::T;
88+
}
89+
90+
impl<T> Dummy for T {
91+
type T = T;
92+
fn to(self) -> T {
93+
self
94+
}
95+
}
96+
8097
#main
8198
}
8299
)

rtic-macros/src/codegen/module.rs

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::syntax::{ast::App, Context};
22
use crate::{analyze::Analysis, codegen::bindings::interrupt_mod, codegen::util};
33

44
use proc_macro2::TokenStream as TokenStream2;
5-
use quote::quote;
5+
use quote::{format_ident, quote};
66

77
#[allow(clippy::too_many_lines)]
88
pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
@@ -129,26 +129,29 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
129129
};
130130

131131
let internal_spawn_ident = util::internal_task_ident(name, "spawn");
132+
let internal_spawn_helper_ident = util::internal_task_ident(name, "spawn_helper");
132133
let internal_waker_ident = util::internal_task_ident(name, "waker");
133134
let from_ptr_n_args = util::from_ptr_n_args_ident(spawnee.inputs.len());
134135
let (input_args, input_tupled, input_untupled, input_ty) =
135136
util::regroup_inputs(&spawnee.inputs);
136137

137-
let local_task = app.software_tasks[t].args.local_task;
138-
let unsafety = if local_task {
139-
// local tasks are only safe to call from the same executor
140-
quote! { unsafe }
141-
} else {
142-
quote! {}
143-
};
138+
let generics: Vec<_> = spawnee.inputs.iter().enumerate().map(|(i, _)| format_ident!("T{i}")).collect();
139+
let generic_input_args = generics.iter().enumerate().map(|(i, generic)| {
140+
let ident = format_ident!("_{i}");
141+
quote! { #ident: #generic }
142+
});
143+
let constraints = generics.iter().zip(&spawnee.inputs).map(|(generic, input)| {
144+
let ty = &input.ty;
145+
quote!{ #generic: Dummy<T=#ty> + Send + Sync }
146+
});
144147

145148
// Spawn caller
146149
items.push(quote!(
147150
#(#cfgs)*
148151
/// Spawns the task directly
149152
#[allow(non_snake_case)]
150153
#[doc(hidden)]
151-
pub #unsafety fn #internal_spawn_ident(#(#input_args,)*) -> ::core::result::Result<(), #input_ty> {
154+
pub fn #internal_spawn_helper_ident(#(#input_args,)*) -> ::core::result::Result<(), #input_ty> {
152155
// SAFETY: If `try_allocate` succeeds one must call `spawn`, which we do.
153156
unsafe {
154157
let exec = rtic::export::executor::AsyncTaskExecutor::#from_ptr_n_args(#name, &#exec_name);
@@ -162,6 +165,12 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
162165
}
163166
}
164167
}
168+
169+
pub fn #internal_spawn_ident<#(#generics),*>(#(#generic_input_args,)*) -> ::core::result::Result<(), #input_ty>
170+
where #(#constraints),*
171+
{
172+
unsafe { #internal_spawn_helper_ident(#(#input_untupled.to()),*) }
173+
}
165174
));
166175

167176
// Waker
@@ -183,37 +192,31 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
183192
}
184193
));
185194

186-
if !local_task {
187-
module_items.push(quote!(
188-
#(#cfgs)*
189-
#[doc(inline)]
190-
pub use super::#internal_spawn_ident as spawn;
191-
));
192-
}
195+
module_items.push(quote!{
196+
#(#cfgs)*
197+
#[doc(inline)]
198+
pub use super::#internal_spawn_ident as spawn;
199+
});
193200

194-
let local_tasks_on_same_executor: Vec<_> = app
201+
let tasks_on_same_executor: Vec<_> = app
195202
.software_tasks
196203
.iter()
197-
.filter(|(_, t)| t.args.local_task && t.args.priority == priority)
204+
.filter(|(_, t)| t.args.priority == priority)
198205
.collect();
199206

200-
if !local_tasks_on_same_executor.is_empty() {
207+
if !tasks_on_same_executor.is_empty() {
201208
let local_spawner = util::internal_task_ident(t, "LocalSpawner");
202209
fields.push(quote! {
203210
/// Used to spawn tasks on the same executor
204211
///
205212
/// This is useful for tasks that take args which are !Send/!Sync.
206-
///
207-
/// NOTE: This only works with tasks marked `local_task = true`
208-
/// and which have the same priority and thus will run on the
209-
/// same executor.
210213
pub local_spawner: #local_spawner
211214
});
212-
let tasks = local_tasks_on_same_executor
215+
let tasks = tasks_on_same_executor
213216
.iter()
214217
.map(|(ident, task)| {
215218
// Copied mostly from software_tasks.rs
216-
let internal_spawn_ident = util::internal_task_ident(ident, "spawn");
219+
let internal_spawn_ident = util::internal_task_ident(ident, "spawn_helper");
217220
let attrs = &task.attrs;
218221
let cfgs = &task.cfgs;
219222
let inputs = &task.inputs;

rtic-macros/src/syntax/analyze.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -285,16 +285,8 @@ pub(crate) fn app(app: &App) -> Result<Analysis, syn::Error> {
285285
for (name, spawnee) in &app.software_tasks {
286286
let spawnee_prio = spawnee.args.priority;
287287

288-
// TODO: What is this?
289288
let channel = channels.entry(spawnee_prio).or_default();
290289
channel.tasks.insert(name.clone());
291-
292-
if !spawnee.args.local_task {
293-
// All inputs are send as we do not know from where they may be spawned.
294-
spawnee.inputs.iter().for_each(|input| {
295-
send_types.insert(input.ty.clone());
296-
});
297-
}
298290
}
299291

300292
// No channel should ever be empty

rtic-macros/src/syntax/ast.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -256,12 +256,6 @@ pub struct SoftwareTaskArgs {
256256

257257
/// Shared resources that can be accessed from this context
258258
pub shared_resources: SharedResources,
259-
260-
/// Local tasks
261-
///
262-
/// Local tasks can only be spawned from the same executor.
263-
/// However they do not require Send and Sync
264-
pub local_task: bool,
265259
}
266260

267261
impl Default for SoftwareTaskArgs {
@@ -270,7 +264,6 @@ impl Default for SoftwareTaskArgs {
270264
priority: 0,
271265
local_resources: LocalResources::new(),
272266
shared_resources: SharedResources::new(),
273-
local_task: false,
274267
}
275268
}
276269
}

rtic-macros/src/syntax/parse.rs

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use syn::{
1111
braced,
1212
parse::{self, Parse, ParseStream, Parser},
1313
token::Brace,
14-
Attribute, Ident, Item, LitBool, LitInt, Meta, Token,
14+
Attribute, Ident, Item, LitInt, Meta, Token,
1515
};
1616

1717
use crate::syntax::{
@@ -197,7 +197,6 @@ fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, Sof
197197
let mut shared_resources = None;
198198
let mut local_resources = None;
199199
let mut prio_span = None;
200-
let mut local_task = None;
201200

202201
loop {
203202
if input.is_empty() {
@@ -209,27 +208,7 @@ fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, Sof
209208
let ident_s = ident.to_string();
210209

211210
// Handle equal sign
212-
let eq = input.parse::<Token![=]>();
213-
214-
// Only local_task supports omitting the value
215-
if &*ident_s == "local_task" {
216-
if local_task.is_some() {
217-
return Err(parse::Error::new(
218-
ident.span(),
219-
"argument appears more than once",
220-
));
221-
}
222-
223-
if eq.is_ok() {
224-
let lit: LitBool = input.parse()?;
225-
local_task = Some(lit.value);
226-
} else {
227-
local_task = Some(true); // Default to true
228-
}
229-
break;
230-
} else if let Err(e) = eq {
231-
return Err(e);
232-
};
211+
let _: Token![=] = input.parse()?;
233212

234213
match &*ident_s {
235214
"binds" => {
@@ -312,7 +291,6 @@ fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, Sof
312291
}
313292
let shared_resources = shared_resources.unwrap_or_default();
314293
let local_resources = local_resources.unwrap_or_default();
315-
let local_task = local_task.unwrap_or(false);
316294

317295
Ok(if let Some(binds) = binds {
318296
// Hardware tasks can't run at anything lower than 1
@@ -339,7 +317,6 @@ fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, Sof
339317
priority,
340318
shared_resources,
341319
local_resources,
342-
local_task,
343320
})
344321
})
345322
})

rtic/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Example:
2323
### Added
2424

2525
- Outer attributes applied to RTIC app module are now forwarded to the generated code.
26-
- Add attribute `local_task` for tasks that may take args that are !Send/!Sync and can only be spawned from same executor
26+
- Allow tasks with !Send/!Sync to be spawned from the same priority level task using local spawner
2727

2828
### Changed
2929

rtic/ui/spawn-local-different-exec.rs renamed to rtic/ui/spawn-non-send-different-exec.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#[rtic::app(device = lm3s6965, dispatchers = [SSI0, GPIOA])]
44
mod app {
5+
use super::*;
6+
57
#[shared]
68
struct Shared {}
79

@@ -14,10 +16,13 @@ mod app {
1416
}
1517

1618
#[task(priority = 1, local_task)]
17-
async fn foo(_cx: foo::Context) {}
19+
async fn foo(_cx: foo::Context, _nsns: NotSendNotSync) {}
1820

1921
#[task(priority = 2)]
2022
async fn bar(cx: bar::Context) {
21-
cx.local_spawner.foo().ok();
23+
cx.local_spawner.foo(Default::default()).ok();
2224
}
2325
}
26+
27+
#[derive(Default, Debug)]
28+
struct NotSendNotSync(core::marker::PhantomData<*mut u8>);

0 commit comments

Comments
 (0)