Skip to content

Commit eecec3f

Browse files
committed
Merge branch 'proc-task' into tasks-pre
2 parents b10d494 + 0a78a5e commit eecec3f

File tree

7 files changed

+666
-12
lines changed

7 files changed

+666
-12
lines changed

samples/philosophers/src/lib.rs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use alloc::boxed::Box;
1414
use alloc::vec::Vec;
1515
use zephyr::time::{sleep, Duration, Tick};
1616
use zephyr::{
17-
kobj_define, printkln,
17+
printkln,
1818
sync::{Arc, Mutex},
1919
sys::uptime_get,
2020
};
@@ -75,12 +75,7 @@ extern "C" fn rust_main() {
7575
printkln!("Pre fork");
7676

7777
for (i, syncer) in (0..NUM_PHIL).zip(syncers.into_iter()) {
78-
let thread = PHIL_THREADS[i]
79-
.init_once(PHIL_STACKS[i].init_once(()).unwrap())
80-
.unwrap();
81-
thread.spawn(move || {
82-
phil_thread(i, syncer, stats);
83-
});
78+
phil_thread(i, syncer, stats).start();
8479
}
8580

8681
let delay = Duration::secs_at_least(10);
@@ -129,6 +124,7 @@ fn get_syncer() -> Vec<Arc<dyn ForkSync>> {
129124
get_channel_syncer()
130125
}
131126

127+
#[zephyr::thread(stack_size = PHIL_STACK_SIZE, pool_size = NUM_PHIL)]
132128
fn phil_thread(n: usize, syncer: Arc<dyn ForkSync>, stats: &'static Mutex<Stats>) {
133129
printkln!("Child {} started: {:?}", n, syncer);
134130

@@ -219,8 +215,3 @@ impl Stats {
219215
}
220216

221217
static STAT_MUTEX: Mutex<Stats> = Mutex::new(Stats::new());
222-
223-
kobj_define! {
224-
static PHIL_THREADS: [StaticThread; NUM_PHIL];
225-
static PHIL_STACKS: [ThreadStack<PHIL_STACK_SIZE>; NUM_PHIL];
226-
}

zephyr-macros/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "zephyr-macros"
3+
version = "0.1.0"
4+
edition = "2024"
5+
license = "MIT OR Apache-2.0"
6+
descriptions = "Macros for managing tasks and work queues in Zephyr"
7+
8+
[lib]
9+
proc-macro = true
10+
11+
[dependencies]
12+
syn = { version = "2.0.85", features = ["full", "visit"] }
13+
quote = "1.0.37"
14+
proc-macro2 = "1.0.86"
15+
darling = "0.20.1"

zephyr-macros/src/lib.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//! Zephyr macros
2+
3+
use proc_macro::TokenStream;
4+
5+
mod task;
6+
7+
/// Declares a Zephyr thread (or pool of threads) that can be spawned.
8+
///
9+
/// There are some restrictions on this:
10+
/// - All arguments to the function must be Send.
11+
/// - The function must not use generics.
12+
/// - The optional `pool_size` attribute must be 1 or greater.
13+
/// - The `stack_size` must be specified, and will set the size of the pre-defined stack for _each_
14+
/// task in the pool.
15+
///
16+
/// ## Examples
17+
///
18+
/// Declaring a task with a simple argument:
19+
///
20+
/// ```rust
21+
/// #[zephyr::thread(stack_size = 1024)]
22+
/// fn mytask(arg: u32) {
23+
/// // Function body.
24+
/// }
25+
/// ```
26+
///
27+
/// The result will be a function `mytask` that takes this argument, and returns a `ReadyThread`. A
28+
/// simple use case is to call `.start()` on this, to start the Zephyr thread.
29+
///
30+
/// Threads can be reused after they have exited. Calling the `mytask` function before the thread
31+
/// has exited will result in a panic. The `RunningThread`'s `join` method can be used to wait for
32+
/// thread termination.
33+
#[proc_macro_attribute]
34+
pub fn thread(args: TokenStream, item: TokenStream) -> TokenStream {
35+
task::run(args.into(), item.into()).into()
36+
}

zephyr-macros/src/task.rs

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
//! Expansion of `#[zephyr::task(...)]`.
2+
3+
use std::{ffi::CString, fmt::Display};
4+
5+
use darling::FromMeta;
6+
use darling::export::NestedMeta;
7+
use proc_macro2::{Literal, Span, TokenStream};
8+
use quote::{ToTokens, format_ident, quote};
9+
use syn::{
10+
Expr, ExprLit, ItemFn, Lit, LitInt, ReturnType, Type,
11+
visit::{self, Visit},
12+
};
13+
14+
#[derive(Debug, FromMeta, Default)]
15+
struct Args {
16+
#[darling(default)]
17+
pool_size: Option<syn::Expr>,
18+
#[darling(default)]
19+
stack_size: Option<syn::Expr>,
20+
}
21+
22+
pub fn run(args: TokenStream, item: TokenStream) -> TokenStream {
23+
let mut errors = TokenStream::new();
24+
25+
// If any of the steps for this macro fail, we still want to expand to an item that is as close
26+
// to the expected output as possible. This helps out IDEs such that completions and other
27+
// related features keep working.
28+
let f: ItemFn = match syn::parse2(item.clone()) {
29+
Ok(x) => x,
30+
Err(e) => return token_stream_with_error(item, e),
31+
};
32+
33+
let args = match NestedMeta::parse_meta_list(args) {
34+
Ok(x) => x,
35+
Err(e) => return token_stream_with_error(item, e),
36+
};
37+
38+
let args = match Args::from_list(&args) {
39+
Ok(x) => x,
40+
Err(e) => {
41+
errors.extend(e.write_errors());
42+
Args::default()
43+
}
44+
};
45+
46+
let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit {
47+
attrs: vec![],
48+
lit: Lit::Int(LitInt::new("1", Span::call_site())),
49+
}));
50+
51+
let stack_size = args.stack_size.unwrap_or(Expr::Lit(ExprLit {
52+
attrs: vec![],
53+
// TODO: Instead of a default, require this.
54+
lit: Lit::Int(LitInt::new("2048", Span::call_site())),
55+
}));
56+
57+
if !f.sig.asyncness.is_none() {
58+
error(&mut errors, &f.sig, "thread function must not be async");
59+
}
60+
61+
if !f.sig.generics.params.is_empty() {
62+
error(&mut errors, &f.sig, "thread function must not be generic");
63+
}
64+
65+
if !f.sig.generics.where_clause.is_none() {
66+
error(
67+
&mut errors,
68+
&f.sig,
69+
"thread function must not have `where` clauses",
70+
);
71+
}
72+
73+
if !f.sig.abi.is_none() {
74+
error(
75+
&mut errors,
76+
&f.sig,
77+
"thread function must not have an ABI qualifier",
78+
);
79+
}
80+
81+
if !f.sig.variadic.is_none() {
82+
error(&mut errors, &f.sig, "thread function must not be variadic");
83+
}
84+
85+
match &f.sig.output {
86+
ReturnType::Default => {}
87+
ReturnType::Type(_, ty) => match &**ty {
88+
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
89+
Type::Never(_) => {}
90+
_ => error(
91+
&mut errors,
92+
&f.sig,
93+
"thread functions must either not return a value, return (), or return `!`",
94+
),
95+
},
96+
}
97+
98+
let mut args = Vec::new();
99+
let mut fargs = f.sig.inputs.clone();
100+
let mut inner_calling = Vec::new();
101+
let mut inner_args = Vec::new();
102+
103+
for arg in fargs.iter_mut() {
104+
match arg {
105+
syn::FnArg::Receiver(_) => {
106+
error(
107+
&mut errors,
108+
arg,
109+
"thread functions must not have `self` arguments",
110+
);
111+
}
112+
syn::FnArg::Typed(t) => {
113+
check_arg_ty(&mut errors, &t.ty);
114+
match t.pat.as_mut() {
115+
syn::Pat::Ident(id) => {
116+
id.mutability = None;
117+
args.push((id.clone(), t.attrs.clone()));
118+
inner_calling.push(quote! {
119+
data.#id,
120+
});
121+
inner_args.push(quote! {#id,});
122+
}
123+
_ => {
124+
error(
125+
&mut errors,
126+
arg,
127+
"pattern matching in task arguments is not yet supported",
128+
);
129+
}
130+
}
131+
}
132+
}
133+
}
134+
135+
let thread_ident = f.sig.ident.clone();
136+
let thread_inner_ident = format_ident!("__{}_thread", thread_ident);
137+
138+
let mut thread_inner = f.clone();
139+
let visibility = thread_inner.vis.clone();
140+
thread_inner.vis = syn::Visibility::Inherited;
141+
thread_inner.sig.ident = thread_inner_ident.clone();
142+
143+
// Assemble the original input arguments.
144+
let mut full_args = Vec::new();
145+
for (arg, cfgs) in &args {
146+
full_args.push(quote! {
147+
#(#cfgs)*
148+
#arg
149+
});
150+
}
151+
152+
let thread_name = Literal::c_string(&CString::new(thread_ident.to_string()).unwrap());
153+
154+
let mut thread_outer_body = quote! {
155+
const _ZEPHYR_INTERNAL_STACK_SIZE: usize = zephyr::thread::stack_len(#stack_size);
156+
const _ZEPHYR_INTERNAL_POOL_SIZE: usize = #pool_size;
157+
struct _ZephyrInternalArgs {
158+
// This depends on the argument syntax being valid as a struct definition, which should
159+
// be the case with the above constraints.
160+
#fargs
161+
}
162+
163+
static THREAD: [zephyr::thread::ThreadData<_ZephyrInternalArgs>; _ZEPHYR_INTERNAL_POOL_SIZE]
164+
= [const { zephyr::thread::ThreadData::new() }; _ZEPHYR_INTERNAL_POOL_SIZE];
165+
#[unsafe(link_section = ".noinit.TODO_STACK")]
166+
static STACK: [zephyr::thread::ThreadStack<_ZEPHYR_INTERNAL_STACK_SIZE>; _ZEPHYR_INTERNAL_POOL_SIZE]
167+
= [const { zephyr::thread::ThreadStack::new() }; _ZEPHYR_INTERNAL_POOL_SIZE];
168+
169+
extern "C" fn startup(
170+
arg0: *mut ::core::ffi::c_void,
171+
_: *mut ::core::ffi::c_void,
172+
_: *mut ::core::ffi::c_void,
173+
) {
174+
let init = unsafe { &mut *(arg0 as *mut ::zephyr::thread::InitData<_ZephyrInternalArgs>) };
175+
let init = init.0.get();
176+
match unsafe { init.replace(None) } {
177+
None => {
178+
::core::panic!("Incorrect thread initialization");
179+
}
180+
Some(data) => {
181+
#thread_inner_ident(#(#inner_calling)*);
182+
}
183+
}
184+
}
185+
186+
zephyr::thread::ThreadData::acquire(
187+
&THREAD,
188+
&STACK,
189+
_ZephyrInternalArgs { #(#inner_args)* },
190+
Some(startup),
191+
0,
192+
#thread_name,
193+
)
194+
};
195+
196+
let thread_outer_attrs = thread_inner.attrs.clone();
197+
198+
if !errors.is_empty() {
199+
thread_outer_body = quote! {
200+
#[allow(unused_variables, unreachable_code)]
201+
let _x: ::zephyr::thread::ReadyThread = ::core::todo!();
202+
_x
203+
};
204+
}
205+
206+
// Copy the generics + where clause to avoid more spurious errors.
207+
let generics = &f.sig.generics;
208+
let where_clause = &f.sig.generics.where_clause;
209+
210+
quote! {
211+
// This is the user's thread function, renamed.
212+
#[doc(hidden)]
213+
#thread_inner
214+
215+
#(#thread_outer_attrs)*
216+
#visibility fn #thread_ident #generics (#fargs) -> ::zephyr::thread::ReadyThread #where_clause {
217+
#thread_outer_body
218+
}
219+
220+
#errors
221+
}
222+
}
223+
224+
// Taken from embassy-executor-macros.
225+
fn check_arg_ty(errors: &mut TokenStream, ty: &Type) {
226+
struct Visitor<'a> {
227+
errors: &'a mut TokenStream,
228+
}
229+
230+
impl<'a, 'ast> Visit<'ast> for Visitor<'a> {
231+
fn visit_type_reference(&mut self, i: &'ast syn::TypeReference) {
232+
// only check for elided lifetime here. If not elided, it is checked by
233+
// `visit_lifetime`.
234+
if i.lifetime.is_none() {
235+
error(
236+
self.errors,
237+
i.and_token,
238+
"Arguments for threads must live forever. Try using the `'static` lifetime.",
239+
);
240+
}
241+
visit::visit_type_reference(self, i);
242+
}
243+
244+
fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) {
245+
if i.ident.to_string() != "static" {
246+
error(
247+
self.errors,
248+
i,
249+
"Arguments for threads must live forever. Try using the `'static` lifetime.",
250+
);
251+
}
252+
}
253+
254+
fn visit_type_impl_trait(&mut self, i: &'ast syn::TypeImplTrait) {
255+
error(
256+
self.errors,
257+
i,
258+
"`impl Trait` is not allowed in thread arguments. It is syntax sugar for generics, and threads cannot be generic.",
259+
);
260+
}
261+
}
262+
263+
Visit::visit_type(&mut Visitor { errors }, ty);
264+
}
265+
266+
// Utility borrowed from embassy-executor-macros.
267+
pub fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
268+
tokens.extend(error.into_compile_error());
269+
tokens
270+
}
271+
272+
pub fn error<A: ToTokens, T: Display>(s: &mut TokenStream, obj: A, msg: T) {
273+
s.extend(syn::Error::new_spanned(obj.into_token_stream(), msg).into_compile_error())
274+
}

zephyr/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Functionality for Rust-based applications that run on Zephyr.
1111

1212
[dependencies]
1313
zephyr-sys = { version = "0.1.0", path = "../zephyr-sys" }
14+
zephyr-macros = { version = "0.1.0", path = "../zephyr-macros" }
1415

1516
# Although paste is brought in, it is a compile-time macro, and is not linked into the application.
1617
paste = "1.0"

zephyr/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ pub mod object;
8888
pub mod simpletls;
8989
pub mod sync;
9090
pub mod sys;
91+
pub mod thread;
9192
pub mod time;
9293
#[cfg(CONFIG_RUST_ALLOC)]
9394
pub mod timer;
@@ -101,6 +102,9 @@ pub use logging::set_logger;
101102
/// Re-exported for local macro use.
102103
pub use paste::paste;
103104

105+
/// Re-export the proc macros.
106+
pub use zephyr_macros::thread;
107+
104108
// Bring in the generated kconfig module
105109
pub mod kconfig {
106110
//! Zephyr Kconfig values.

0 commit comments

Comments
 (0)