Skip to content

Commit 4b6405d

Browse files
committed
Macro to implement riscv-pac traits
1 parent 7da7f63 commit 4b6405d

19 files changed

+389
-253
lines changed

riscv-pac/Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,10 @@ targets = [
1919
]
2020

2121
[dependencies]
22-
riscv-pac-macros = { path = "macros", version = "0.1.0" }
22+
riscv-pac-macros = { path = "macros", version = "0.1.0", optional = true }
23+
24+
[features]
25+
default = ["riscv-pac-macros"]
26+
27+
[dev-dependencies]
28+
trybuild = "1.0"

riscv-pac/macros/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ proc-macro = true
1717
[dependencies]
1818
proc-macro2 = "1.0"
1919
quote = "1.0"
20-
syn = { version = "2.0", default-features = false, features = ["derive", "parsing", "proc-macro"]}
20+
syn = { version = "2.0" }

riscv-pac/macros/src/lib.rs

Lines changed: 136 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,66 @@ extern crate syn;
66
use proc_macro::TokenStream;
77
use proc_macro2::TokenStream as TokenStream2;
88
use quote::quote;
9-
use std::{collections::HashMap, convert::TryFrom, ops::Range, str::FromStr};
10-
use syn::{parse_macro_input, Data, DeriveInput, Error, Ident};
9+
use std::{collections::HashMap, ops::Range, str::FromStr};
10+
use syn::{parse_macro_input, Data, DeriveInput, Ident};
1111

1212
struct PacNumberEnum {
1313
name: Ident,
1414
valid_ranges: Vec<Range<usize>>,
1515
}
1616

1717
impl PacNumberEnum {
18+
fn new(input: &DeriveInput) -> Self {
19+
let variants = match &input.data {
20+
Data::Enum(data) => &data.variants,
21+
_ => panic!("Input is not an enum"),
22+
};
23+
24+
// Collect the variants and their associated number discriminants
25+
let mut var_map = HashMap::new();
26+
let mut numbers = Vec::new();
27+
for variant in variants {
28+
let ident = &variant.ident;
29+
let value = match &variant.discriminant {
30+
Some(d) => match &d.1 {
31+
syn::Expr::Lit(expr_lit) => match &expr_lit.lit {
32+
syn::Lit::Int(lit_int) => match lit_int.base10_parse::<usize>() {
33+
Ok(num) => num,
34+
Err(_) => panic!("All variant discriminants must be unsigned integers"),
35+
},
36+
_ => panic!("All variant discriminants must be unsigned integers"),
37+
},
38+
_ => panic!("All variant discriminants must be unsigned integers"),
39+
},
40+
_ => panic!("Variant must have a discriminant"),
41+
};
42+
// check for duplicate discriminant values
43+
var_map.insert(value, ident);
44+
numbers.push(value);
45+
}
46+
47+
// sort the number discriminants and generate a list of valid ranges
48+
numbers.sort_unstable();
49+
let mut valid_ranges = Vec::new();
50+
let mut start = numbers[0];
51+
let mut end = start;
52+
for &number in &numbers[1..] {
53+
if number == end + 1 {
54+
end = number;
55+
} else {
56+
valid_ranges.push(start..end + 1);
57+
start = number;
58+
end = start;
59+
}
60+
}
61+
valid_ranges.push(start..end + 1);
62+
63+
Self {
64+
name: input.ident.clone(),
65+
valid_ranges,
66+
}
67+
}
68+
1869
fn valid_condition(&self) -> TokenStream2 {
1970
let mut arms = Vec::new();
2071
for range in &self.valid_ranges {
@@ -45,7 +96,7 @@ impl PacNumberEnum {
4596
let const_name = TokenStream2::from_str(const_name).unwrap();
4697

4798
quote! {
48-
unsafe impl #trait_name for #name {
99+
unsafe impl riscv_pac::#trait_name for #name {
49100
const #const_name: #num_type = #max_discriminant;
50101

51102
#[inline]
@@ -67,93 +118,88 @@ impl PacNumberEnum {
67118
}
68119
}
69120

70-
impl TryFrom<DeriveInput> for PacNumberEnum {
71-
type Error = Error;
72-
73-
fn try_from(input: DeriveInput) -> Result<Self, Self::Error> {
74-
let variants = match &input.data {
75-
Data::Enum(data) => &data.variants,
76-
_ => panic!("Input is not an enum"),
77-
};
78-
79-
// Collect the variants and their associated number discriminants
80-
let mut var_map = HashMap::new();
81-
let mut numbers = Vec::new();
82-
for variant in variants {
83-
let ident = &variant.ident;
84-
let value = match &variant.discriminant {
85-
Some(d) => match &d.1 {
86-
syn::Expr::Lit(expr_lit) => match &expr_lit.lit {
87-
syn::Lit::Int(lit_int) => match lit_int.base10_parse::<usize>() {
88-
Ok(num) => num,
89-
Err(_) => panic!("All variant discriminants must be unsigned integers"),
90-
},
91-
_ => panic!("All variant discriminants must be unsigned integers"),
92-
},
93-
_ => panic!("All variant discriminants must be unsigned integers"),
94-
},
95-
_ => panic!("Variant must have a discriminant"),
96-
};
97-
// check for duplicate discriminant values
98-
var_map.insert(value, ident);
99-
numbers.push(value);
100-
}
101-
102-
// sort the number discriminants and generate a list of valid ranges
103-
numbers.sort_unstable();
104-
let mut valid_ranges = Vec::new();
105-
let mut start = numbers[0];
106-
let mut end = start;
107-
for &number in &numbers[1..] {
108-
if number == end + 1 {
109-
end = number;
110-
} else {
111-
valid_ranges.push(start..end + 1);
112-
start = number;
113-
end = start;
114-
}
115-
}
116-
valid_ranges.push(start..end + 1);
117-
118-
Ok(PacNumberEnum {
119-
name: input.ident.clone(),
120-
valid_ranges,
121-
})
121+
/// Attribute-like macro that implements the traits of the `riscv-pac` crate for a given enum.
122+
///
123+
/// As these traits are unsafe, the macro must be called with the `unsafe` keyword followed by the trait name.
124+
/// In this way, we warn callers that they must comply with the requirements of the trait.
125+
///
126+
/// The trait name must be one of `ExceptionNumber`, `InterruptNumber`, `PriorityNumber`, or `HartIdNumber`.
127+
/// Marker traits `CoreInterruptNumber` and `ExternalInterruptNumber` cannot be implemented using this macro.
128+
///
129+
/// # Note
130+
///
131+
/// To implement number-to-enum operation, the macro works with ranges of valid discriminant numbers.
132+
/// If the number is within any of the valid ranges, the number is transmuted to the enum variant.
133+
/// In this way, the macro achieves better performance for enums with a large number of consecutive variants.
134+
/// Thus, the enum must comply with the following requirements:
135+
///
136+
/// - All the enum variants must have a valid discriminant number (i.e., a number that is within the valid range of the enum).
137+
/// - For the `ExceptionNumber`, `InterruptNumber`, and `HartIdNumber` traits, the enum must be annotated as `#[repr(u16)]`
138+
/// - For the `PriorityNumber` trait, the enum must be annotated as `#[repr(u8)]`
139+
///
140+
/// If the enum does not meet these requirements, you will have to implement the traits manually (e.g., `riscv::mcause::Interrupt`).
141+
/// For enums with a small number of consecutive variants, it might be better to implement the traits manually.
142+
///
143+
/// # Safety
144+
///
145+
/// The struct to be implemented must comply with the requirements of the specified trait.
146+
///
147+
/// # Example
148+
///
149+
/// ```rust
150+
/// use riscv_pac::*;
151+
///
152+
/// #[repr(u16)]
153+
/// #[pac_enum(unsafe ExceptionNumber)]
154+
/// #[derive(Clone, Copy, Debug, Eq, PartialEq)]
155+
/// enum Exception {
156+
/// E1 = 1,
157+
/// E3 = 3,
158+
/// }
159+
///
160+
/// fn main() {
161+
/// assert_eq!(Exception::E1.number(), 1);
162+
/// assert_eq!(Exception::E3.number(), 3);
163+
///
164+
/// assert_eq!(Exception::from_number(1), Ok(Exception::E1));
165+
/// assert_eq!(Exception::from_number(2), Err(2));
166+
/// assert_eq!(Exception::from_number(3), Ok(Exception::E3));
167+
///
168+
/// assert_eq!(Exception::MAX_EXCEPTION_NUMBER, 3);
169+
/// }
170+
///```
171+
#[proc_macro_attribute]
172+
pub fn pac_enum(attr: TokenStream, item: TokenStream) -> TokenStream {
173+
let input = parse_macro_input!(item as DeriveInput);
174+
let pac_enum = PacNumberEnum::new(&input);
175+
176+
// attr should be unsafe ExceptionNumber, unsafe InterruptNumber, unsafe PriorityNumber, or unsafe HartIdNumber
177+
// assert that attribute starts with the unsafe token. If not, raise a panic error
178+
let attr = attr.to_string();
179+
// split string into words and check if the first word is "unsafe"
180+
let attrs = attr.split_whitespace().collect::<Vec<&str>>();
181+
if attrs.is_empty() {
182+
panic!("Attribute is empty. Expected: 'riscv_pac::pac_enum(unsafe <PacTraitToImplement>)'");
183+
}
184+
if attrs.len() > 2 {
185+
panic!(
186+
"Wrong attribute format. Expected: 'riscv_pac::pac_enum(unsafe <PacTraitToImplement>)'"
187+
);
188+
}
189+
if attrs[0] != "unsafe" {
190+
panic!("Attribute does not start with 'unsafe'. Expected: 'riscv_pac::pac_enum(unsafe <PacTraitToImplement>)'");
122191
}
123-
}
124-
125-
#[proc_macro_derive(ExceptionNumber)]
126-
pub fn exception_number_derive(input: TokenStream) -> TokenStream {
127-
let input = parse_macro_input!(input as DeriveInput);
128-
let pac_enum = PacNumberEnum::try_from(input).unwrap();
129-
pac_enum
130-
.quote("ExceptionNumber", "u16", "MAX_EXCEPTION_NUMBER")
131-
.into()
132-
}
133-
134-
#[proc_macro_derive(InterruptNumber)]
135-
pub fn interrupt_number_derive(input: TokenStream) -> TokenStream {
136-
let input = parse_macro_input!(input as DeriveInput);
137-
let pac_enum = PacNumberEnum::try_from(input).unwrap();
138-
pac_enum
139-
.quote("InterruptNumber", "u16", "MAX_INTERRUPT_NUMBER")
140-
.into()
141-
}
142-
143-
#[proc_macro_derive(PriorityNumber)]
144-
pub fn priority_number_derive(input: TokenStream) -> TokenStream {
145-
let input = parse_macro_input!(input as DeriveInput);
146-
let pac_enum = PacNumberEnum::try_from(input).unwrap();
147-
pac_enum
148-
.quote("PriorityNumber", "u8", "MAX_PRIORITY_NUMBER")
149-
.into()
150-
}
151192

152-
#[proc_macro_derive(HartIdNumber)]
153-
pub fn hart_id_number_derive(input: TokenStream) -> TokenStream {
154-
let input = parse_macro_input!(input as DeriveInput);
155-
let pac_enum = PacNumberEnum::try_from(input).unwrap();
156-
pac_enum
157-
.quote("HartIdNumber", "u16", "MAX_HART_ID_NUMBER")
158-
.into()
193+
let trait_impl = match attrs[1] {
194+
"ExceptionNumber" => pac_enum.quote("ExceptionNumber", "u16", "MAX_EXCEPTION_NUMBER"),
195+
"InterruptNumber" => pac_enum.quote("InterruptNumber", "u16", "MAX_INTERRUPT_NUMBER"),
196+
"PriorityNumber" => pac_enum.quote("PriorityNumber", "u8", "MAX_PRIORITY_NUMBER"),
197+
"HartIdNumber" => pac_enum.quote("HartIdNumber", "u16", "MAX_HART_ID_NUMBER"),
198+
_ => panic!("Unknown trait '{}'. Expected: 'ExceptionNumber', 'InterruptNumber', 'PriorityNumber', or 'HartIdNumber'", attrs[1]),
199+
};
200+
quote! {
201+
#input
202+
#trait_impl
203+
}
204+
.into()
159205
}

0 commit comments

Comments
 (0)