Skip to content

Commit 0eec79e

Browse files
committed
riscv-macros: post_init macro
1 parent d0893ea commit 0eec79e

File tree

9 files changed

+277
-6
lines changed

9 files changed

+277
-6
lines changed

riscv-macros/CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

8-
## [Unreleased]
8+
## v0.4.1 - Unreleased
9+
10+
### Added
11+
12+
- `post_init` macro for the `riscv-rt` crate (migrated from `riscv-rt-macros`).
913

1014
## v0.4.0 - 2025-12-19
1115

riscv-macros/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ keywords = ["riscv", "register", "peripheral"]
99
license = "MIT OR Apache-2.0"
1010
name = "riscv-macros"
1111
repository = "https://github.com/rust-embedded/riscv"
12-
version = "0.4.0"
12+
version = "0.4.1"
1313
edition = "2021"
1414

1515
[lib]
@@ -18,6 +18,7 @@ proc-macro = true
1818
[features]
1919
rt = []
2020
rt-v-trap = ["rt"]
21+
riscv-rt = ["rt", "syn/extra-traits", "syn/full"]
2122

2223
[dependencies]
2324
proc-macro2 = "1.0"

riscv-macros/src/lib.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ use syn::{parse_macro_input, DeriveInput};
44

55
mod riscv;
66

7+
#[cfg(feature = "riscv-rt")]
8+
mod riscv_rt;
9+
710
/// Attribute-like macro that implements the traits of the `riscv-types` crate for a given enum.
811
///
912
/// As these traits are unsafe, the macro must be called with the `unsafe` keyword followed by the trait name.
@@ -59,3 +62,31 @@ pub fn pac_enum(attr: TokenStream, item: TokenStream) -> TokenStream {
5962
}
6063
.into()
6164
}
65+
66+
/// Attribute to mark which function will be called before jumping to the entry point.
67+
/// You must enable the `post-init` feature in the `riscv-rt` crate to use this macro.
68+
///
69+
/// In contrast with `__pre_init`, this function is called after the static variables
70+
/// are initialized, so it is safe to access them. It is also safe to run Rust code.
71+
///
72+
/// The function must have the signature of `[unsafe] fn([usize])`, where the argument
73+
/// corresponds to the hart ID of the current hart. This is useful for multi-hart systems
74+
/// to perform hart-specific initialization.
75+
///
76+
/// # IMPORTANT
77+
///
78+
/// This attribute can appear at most *once* in the dependency graph.
79+
///
80+
/// # Examples
81+
///
82+
/// ```
83+
/// #[riscv_macros::post_init]
84+
/// unsafe fn before_main(hart_id: usize) {
85+
/// // do something here
86+
/// }
87+
/// ```
88+
#[cfg(feature = "riscv-rt")]
89+
#[proc_macro_attribute]
90+
pub fn post_init(args: TokenStream, input: TokenStream) -> TokenStream {
91+
riscv_rt::Fn::post_init(args, input)
92+
}

riscv-macros/src/riscv_rt.rs

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
use proc_macro::TokenStream;
2+
use proc_macro2::TokenStream as TokenStream2;
3+
use quote::quote;
4+
use syn::{
5+
parse_macro_input, punctuated::Punctuated, spanned::Spanned, token::Comma, Error, FnArg, Ident,
6+
ItemFn, Result, ReturnType, Type, Visibility,
7+
};
8+
9+
/// Enum representing the supported runtime function attributes
10+
pub enum Fn {
11+
PostInit,
12+
}
13+
14+
impl Fn {
15+
/// Convenience method to generate the token stream for the `post_init` attribute
16+
pub fn post_init(args: TokenStream, input: TokenStream) -> TokenStream {
17+
match Self::PostInit.check_args_empty(args) {
18+
Ok(_) => Self::PostInit.quote_fn(input),
19+
Err(e) => e.to_compile_error().into(),
20+
}
21+
}
22+
23+
/// Generate the token stream for the function with the given attribute
24+
fn quote_fn(&self, item: TokenStream) -> TokenStream {
25+
let mut func = parse_macro_input!(item as ItemFn);
26+
27+
if let Err(e) = self.check_fn(&func) {
28+
return e.to_compile_error().into();
29+
}
30+
31+
let export_name = self.export_name(&func);
32+
let link_section = self.link_section(&func);
33+
34+
// Append to function name the prefix __riscv_rt_ (to prevent users from calling it directly)
35+
// Note that we do not change the export name, only the internal function name in the Rust code.
36+
func.sig.ident = Ident::new(
37+
&format!("__riscv_rt_{}", func.sig.ident),
38+
func.sig.ident.span(),
39+
);
40+
41+
quote! {
42+
#export_name
43+
#link_section
44+
#func
45+
}
46+
.into()
47+
}
48+
49+
/// Check if the function signature is valid for the given attribute
50+
fn check_fn(&self, f: &ItemFn) -> Result<()> {
51+
// First, check that the function is private
52+
if f.vis != Visibility::Inherited {
53+
let attr = self.attr_name();
54+
return Err(Error::new(
55+
f.vis.span(),
56+
format!("`#[{attr}]` function must be private"),
57+
));
58+
}
59+
let sig = &f.sig;
60+
61+
// Next, check common aspects of the signature (constness, asyncness, generics, etc.)
62+
let valid_signature = sig.constness.is_none()
63+
&& sig.asyncness.is_none()
64+
&& sig.abi.is_none()
65+
&& sig.generics.params.is_empty()
66+
&& sig.generics.where_clause.is_none()
67+
&& sig.variadic.is_none();
68+
if !valid_signature {
69+
let attr = self.attr_name();
70+
let expected = self.expected_signature();
71+
return Err(Error::new(
72+
sig.span(),
73+
format!("`#[{attr}]` function signature must be `{expected}`"),
74+
));
75+
}
76+
77+
// Finally, check that input arguments and output type are valid
78+
self.check_inputs(&sig.inputs)?;
79+
self.check_output(&sig.output)
80+
}
81+
82+
/// Utility method for printing attribute name in error messages
83+
const fn attr_name(&self) -> &'static str {
84+
// Use this match to specify attribute names for different functions in the future
85+
match self {
86+
Self::PostInit => "post_init",
87+
}
88+
}
89+
90+
/// Utility method for printing expected function signature in error messages
91+
const fn expected_signature(&self) -> &'static str {
92+
// Use this match to specify expected signatures for different functions in the future
93+
match self {
94+
Self::PostInit => "[unsafe] fn([usize])",
95+
}
96+
}
97+
98+
/// Check if the function has valid input arguments for the given attribute
99+
fn check_inputs(&self, inputs: &Punctuated<FnArg, Comma>) -> Result<()> {
100+
// Use this match to specify expected input arguments for different functions in the future
101+
match self {
102+
Self::PostInit => self.check_fn_args(inputs, &["usize"]),
103+
}
104+
}
105+
106+
/// Check if the function has a valid output type for the given attribute
107+
fn check_output(&self, output: &ReturnType) -> Result<()> {
108+
// Use this match to specify expected output types for different functions in the future
109+
match self {
110+
Self::PostInit => match output {
111+
// post_init return type is ()
112+
ReturnType::Default => Ok(()),
113+
ReturnType::Type(_, ty) => match **ty {
114+
Type::Tuple(ref tuple) => {
115+
if tuple.elems.is_empty() {
116+
Ok(())
117+
} else {
118+
Err(Error::new(tuple.span(), "return type must be ()"))
119+
}
120+
}
121+
_ => Err(Error::new(ty.span(), "return type must be ()")),
122+
},
123+
},
124+
}
125+
}
126+
127+
/// The export name for the given attribute
128+
fn export_name(&self, _f: &ItemFn) -> Option<TokenStream2> {
129+
// Use this match to specify export names for different functions in the future
130+
let export_name = match self {
131+
Self::PostInit => Some("__post_init".to_string()),
132+
};
133+
134+
export_name.map(|name| quote! { #[export_name = #name] })
135+
}
136+
137+
/// The link section attribute for the given attribute (if any)
138+
fn link_section(&self, _f: &ItemFn) -> Option<TokenStream2> {
139+
// Use this match to specify section names for different functions in the future
140+
let section_name: Option<String> = match self {
141+
Self::PostInit => None,
142+
};
143+
144+
section_name.map(|section| quote! {
145+
#[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), link_section = #section)]
146+
})
147+
}
148+
149+
/// Check that no arguments were provided to the macro attribute
150+
fn check_args_empty(&self, args: TokenStream) -> Result<()> {
151+
if args.is_empty() {
152+
Ok(())
153+
} else {
154+
let args: TokenStream2 = args.into();
155+
let attr = self.attr_name();
156+
Err(Error::new(
157+
args.span(),
158+
format!("`#[{attr}]` function does not accept any arguments"),
159+
))
160+
}
161+
}
162+
163+
/// Iterates through the input arguments and checks that their types match the expected types
164+
fn check_fn_args(
165+
&self,
166+
inputs: &Punctuated<FnArg, Comma>,
167+
expected_types: &[&str],
168+
) -> Result<()> {
169+
let mut expected_iter = expected_types.iter();
170+
for arg in inputs.iter() {
171+
match expected_iter.next() {
172+
Some(expected) => check_arg_type(arg, expected)?,
173+
None => {
174+
let attr = self.attr_name();
175+
return Err(Error::new(
176+
arg.span(),
177+
format!("`#[{attr}]` function has too many input arguments"),
178+
));
179+
}
180+
}
181+
}
182+
Ok(())
183+
}
184+
}
185+
186+
/// Check if a function argument matches the expected type
187+
fn check_arg_type(arg: &FnArg, expected: &str) -> Result<()> {
188+
match arg {
189+
FnArg::Typed(argument) => {
190+
if !is_correct_type(&argument.ty, expected) {
191+
Err(Error::new(
192+
argument.ty.span(),
193+
format!("argument type must be {expected}"),
194+
))
195+
} else {
196+
Ok(())
197+
}
198+
}
199+
FnArg::Receiver(_) => Err(Error::new(arg.span(), "invalid argument")),
200+
}
201+
}
202+
203+
/// Check if a type matches the expected type name
204+
fn is_correct_type(ty: &Type, expected: &str) -> bool {
205+
let correct: Type = syn::parse_str(expected).unwrap();
206+
if let Some(ty) = strip_type_path(ty) {
207+
ty == correct
208+
} else {
209+
false
210+
}
211+
}
212+
213+
/// Strip the path of a type, returning only the last segment (e.g., `core::usize` -> `usize`)
214+
fn strip_type_path(ty: &Type) -> Option<Type> {
215+
match ty {
216+
Type::Ptr(ty) => {
217+
let mut ty = ty.clone();
218+
*ty.elem = strip_type_path(&ty.elem)?;
219+
Some(Type::Ptr(ty))
220+
}
221+
Type::Path(ty) => {
222+
let mut ty = ty.clone();
223+
let last_segment = ty.path.segments.last().unwrap().clone();
224+
ty.path.segments = Punctuated::new();
225+
ty.path.segments.push_value(last_segment);
226+
Some(Type::Path(ty))
227+
}
228+
_ => None,
229+
}
230+
}

riscv-rt/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
99

10+
### Changed
11+
12+
- Using `post_init` from `riscv-macros` instead of `riscv-rt-macros`.
13+
1014
## v0.17.0 - 2025-12-19
1115

1216
### Added

riscv-rt/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ riscv-target-parser = { path = "../riscv-target-parser", version = "0.1.2" }
2525

2626
[dependencies]
2727
riscv = { path = "../riscv", version = "0.16.0", features = ["rt"] }
28+
riscv-macros = { path = "../riscv-macros", version = "0.4.1", features = ["riscv-rt"] }
2829
riscv-types = { path = "../riscv-types", version = "0.1.0" }
2930
riscv-rt-macros = { path = "macros", version = "0.6.1" }
3031

@@ -40,7 +41,7 @@ pre-init = []
4041
post-init = []
4142
s-mode = ["riscv-rt-macros/s-mode"]
4243
single-hart = []
43-
v-trap = ["riscv-rt-macros/v-trap", "riscv/rt-v-trap"]
44+
v-trap = ["riscv-rt-macros/v-trap", "riscv/rt-v-trap", "riscv-macros/rt-v-trap"]
4445
u-boot = ["riscv-rt-macros/u-boot", "single-hart"]
4546
no-interrupts = []
4647
no-exceptions = []

riscv-rt/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,7 @@ pub use riscv_rt_macros::{core_interrupt, entry, exception, external_interrupt};
703703
pub use riscv_types::*;
704704

705705
#[cfg(feature = "post-init")]
706-
pub use riscv_rt_macros::post_init;
706+
pub use riscv_macros::post_init;
707707
#[cfg(feature = "pre-init")]
708708
pub use riscv_rt_macros::pre_init;
709709

tests-trybuild/tests/riscv-rt/post_init/fail_arg_count.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: `#[post_init]` function has too many arguments
1+
error: `#[post_init]` function has too many input arguments
22
--> tests/riscv-rt/post_init/fail_arg_count.rs:2:33
33
|
44
2 | fn before_main(_hart_id: usize, _dtb: usize) {}

tests-trybuild/tests/riscv-rt/post_init/fail_async.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: `#[post_init]` function must have signature `[unsafe] fn([usize])`
1+
error: `#[post_init]` function signature must be `[unsafe] fn([usize])`
22
--> tests/riscv-rt/post_init/fail_async.rs:2:1
33
|
44
2 | async fn before_main() {}

0 commit comments

Comments
 (0)