Skip to content

Commit

Permalink
Expand macro for interrupt
Browse files Browse the repository at this point in the history
  • Loading branch information
romancardenas committed Jun 26, 2024
1 parent 32bd8cd commit 9dc9b41
Show file tree
Hide file tree
Showing 8 changed files with 277 additions and 83 deletions.
285 changes: 226 additions & 59 deletions riscv-pac/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,72 +6,214 @@ extern crate syn;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use std::collections::HashMap;
use std::str::FromStr;
use syn::{parse_macro_input, Data, DeriveInput, Ident};
use syn::{
parse::{Parse, ParseStream},
parse_macro_input,
spanned::Spanned,
Data, DeriveInput, Ident, Token,
};

struct PacNumberEnum {
/// Traits that can be implemented using the `pac_enum` macro
enum PacTrait {
Exception,
Interrupt(InterruptType),
Priority,
HartId,
}

impl PacTrait {
/// Returns a token stream representing the trait name
fn trait_name(&self) -> TokenStream2 {
match self {
Self::Exception => quote!(ExceptionNumber),
Self::Interrupt(_) => quote!(InterruptNumber),
Self::Priority => quote!(PriorityNumber),
Self::HartId => quote!(HartIdNumber),
}
}

/// Returns a token stream representing the data type used to represent the number
fn num_type(&self) -> TokenStream2 {
match self {
Self::Exception => quote!(usize),
Self::Interrupt(_) => quote!(usize),
Self::Priority => quote!(u8),
Self::HartId => quote!(u16),
}
}

/// Returns a token stream representing the name of the constant that holds the maximum number
fn const_name(&self) -> TokenStream2 {
match self {
Self::Exception => quote!(MAX_EXCEPTION_NUMBER),
Self::Interrupt(_) => quote!(MAX_INTERRUPT_NUMBER),
Self::Priority => quote!(MAX_PRIORITY_NUMBER),
Self::HartId => quote!(MAX_HART_ID_NUMBER),
}
}
}

impl Parse for PacTrait {
fn parse(input: ParseStream) -> syn::Result<Self> {
input.parse::<Token![unsafe]>()?;
let trait_name: TokenStream2 = input.parse()?;
match trait_name.to_string().as_str() {
"ExceptionNumber" => Ok(Self::Exception),
"CoreInterruptNumber" => Ok(Self::Interrupt(InterruptType::Core)),
"ExternalInterruptNumber" => Ok(Self::Interrupt(InterruptType::External)),
"PriorityNumber" => Ok(Self::Priority),
"HartIdNumber" => Ok(Self::HartId),
_ => Err(syn::Error::new(
trait_name.span(),
"Unknown trait name. Expected: 'ExceptionNumber', 'CoreInterruptNumber', 'ExternalInterruptNumber', 'PriorityNumber', or 'HartIdNumber'",
)),
}
}
}

/// Marker traits for interrupts
enum InterruptType {
Core,
External,
}

impl InterruptType {
/// Returns a token stream representing the name of the marker trait
fn marker_trait_name(&self) -> TokenStream2 {
match self {
Self::Core => quote!(CoreInterruptNumber),
Self::External => quote!(ExternalInterruptNumber),
}
}

/// Returns a token stream representing the name of the array of interrupt service routines
fn isr_array_name(&self) -> TokenStream2 {
match self {
Self::Core => quote!(__CORE_INTERRUPTS),
Self::External => quote!(__EXTERNAL_INTERRUPTS),
}
}

/// Returns a token stream representing the name of the interrupt dispatch function
fn dispatch_fn_name(&self) -> TokenStream2 {
match self {
Self::Core => quote!(_dispatch_core_interrupt),
Self::External => quote!(_dispatch_external_interrupt),
}
}
}

/// Struct containing the information needed to implement the `riscv-pac` traits for an enum
struct PacEnumItem {
/// The name of the enum
name: Ident,
numbers: Vec<(Ident, usize)>,
/// The maximum discriminant value
max_number: usize,
/// A map from discriminant values to variant names
numbers: HashMap<usize, Ident>,
}

impl PacNumberEnum {
impl PacEnumItem {
fn new(input: &DeriveInput) -> Self {
let name = input.ident.clone();
let (mut numbers, mut max_number) = (HashMap::new(), 0);

let variants = match &input.data {
Data::Enum(data) => &data.variants,
_ => panic!("Input is not an enum"),
};
let numbers = variants
.iter()
.map(|variant| {
let ident = &variant.ident;
let value = match &variant.discriminant {
Some(d) => match &d.1 {
syn::Expr::Lit(expr_lit) => match &expr_lit.lit {
syn::Lit::Int(lit_int) => match lit_int.base10_parse::<usize>() {
Ok(num) => num,
Err(_) => {
panic!("All variant discriminants must be unsigned integers")
}
},
_ => panic!("All variant discriminants must be unsigned integers"),
for v in variants.iter() {
let ident = v.ident.clone();
let value = match &v.discriminant {
Some(d) => match &d.1 {
syn::Expr::Lit(expr_lit) => match &expr_lit.lit {
syn::Lit::Int(lit_int) => match lit_int.base10_parse::<usize>() {
Ok(num) => num,
Err(_) => {
panic!("All variant discriminants must be unsigned integers")
}
},
_ => panic!("All variant discriminants must be unsigned integers"),
},
_ => panic!("Variant must have a discriminant"),
};
(ident.clone(), value)
})
.collect();
_ => panic!("All variant discriminants must be unsigned integers"),
},
_ => panic!("Variant must have a discriminant"),
};

Self { name, numbers }
if numbers.insert(value, ident).is_some() {
panic!("Duplicate discriminant value");
}
if value > max_number {
max_number = value;
}
}

Self {
name,
max_number,
numbers,
}
}

/// Returns a token stream representing the maximum discriminant value of the enum
fn max_discriminant(&self) -> TokenStream2 {
let max_discriminant = self.numbers.iter().map(|(_, num)| num).max().unwrap();
TokenStream2::from_str(&format!("{max_discriminant}")).unwrap()
TokenStream2::from_str(&format!("{}", self.max_number)).unwrap()
}

/// Returns a vector of token streams representing the valid matches in the `pac::from_number` function
fn valid_matches(&self) -> Vec<TokenStream2> {
self.numbers
.iter()
.map(|(ident, num)| {
.map(|(num, ident)| {
TokenStream2::from_str(&format!("{num} => Ok(Self::{ident})")).unwrap()
})
.collect()
}

fn quote(&self, trait_name: &str, num_type: &str, const_name: &str) -> TokenStream2 {
/// Returns a vector of token streams representing the interrupt handler functions
fn interrupt_handlers(&self) -> Vec<TokenStream2> {
self.numbers
.values()
.map(|ident| {
quote! { fn #ident () }
})
.collect()
}

/// Returns a sorted vector of token streams representing all the elements of the interrupt array.
/// If an interrupt number is not present in the enum, the corresponding element is `None`.
/// Otherwise, it is `Some(<interrupt_handler>)`.
fn interrupt_array(&self) -> Vec<TokenStream2> {
let mut vectors = vec![];
for i in 0..=self.max_number {
if let Some(ident) = self.numbers.get(&i) {
vectors.push(quote! { Some(#ident) });
} else {
vectors.push(quote! { None });
}
}
vectors
}

/// Returns a vector of token streams representing the trait implementations for
/// the enum. If the trait is an interrupt trait, the implementation also includes
/// the interrupt handler functions and the interrupt array.
fn impl_trait(&self, attr: &PacTrait) -> Vec<TokenStream2> {
let mut res = vec![];

let name = &self.name;

let trait_name = attr.trait_name();
let num_type = attr.num_type();
let const_name = attr.const_name();

let max_discriminant = self.max_discriminant();
let valid_matches = self.valid_matches();

let trait_name = TokenStream2::from_str(trait_name).unwrap();
let num_type = TokenStream2::from_str(num_type).unwrap();
let const_name = TokenStream2::from_str(const_name).unwrap();

quote! {
// Push the trait implementation
res.push(quote! {
unsafe impl riscv_pac::#trait_name for #name {
const #const_name: #num_type = #max_discriminant;

Expand All @@ -88,7 +230,53 @@ impl PacNumberEnum {
}
}
}
});

// Interrupt traits require additional code
if let PacTrait::Interrupt(interrupt_type) = attr {
let marker_trait_name = interrupt_type.marker_trait_name();

let isr_array_name = interrupt_type.isr_array_name();
let dispatch_fn_name = interrupt_type.dispatch_fn_name();

// Push the marker trait implementation
res.push(quote! { unsafe impl riscv_pac::#marker_trait_name for #name {} });

let interrupt_handlers = self.interrupt_handlers();
let interrupt_array = self.interrupt_array();

// Push the interrupt handler functions and the interrupt array
res.push(quote! {
extern "C" {
#(#interrupt_handlers;)*
}

#[no_mangle]
pub static #isr_array_name: [Option<unsafe extern "C" fn()>; #max_discriminant + 1] = [
#(#interrupt_array),*
];

#[no_mangle]
unsafe extern "C" fn #dispatch_fn_name(code: usize) {
extern "C" {
fn DefaultHandler();
}

if code < #isr_array_name.len() {
let h = &#isr_array_name[code];
if let Some(handler) = h {
handler();
} else {
DefaultHandler();
}
} else {
DefaultHandler();
}
}
});
}

res
}
}

Expand Down Expand Up @@ -131,35 +319,14 @@ impl PacNumberEnum {
#[proc_macro_attribute]
pub fn pac_enum(attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let pac_enum = PacNumberEnum::new(&input);

// attr should be unsafe ExceptionNumber, unsafe InterruptNumber, unsafe PriorityNumber, or unsafe HartIdNumber
// assert that attribute starts with the unsafe token. If not, raise a panic error
let attr = attr.to_string();
// split string into words and check if the first word is "unsafe"
let attrs = attr.split_whitespace().collect::<Vec<&str>>();
if attrs.is_empty() {
panic!("Attribute is empty. Expected: 'riscv_pac::pac_enum(unsafe <PacTraitToImplement>)'");
}
if attrs.len() > 2 {
panic!(
"Wrong attribute format. Expected: 'riscv_pac::pac_enum(unsafe <PacTraitToImplement>)'"
);
}
if attrs[0] != "unsafe" {
panic!("Attribute does not start with 'unsafe'. Expected: 'riscv_pac::pac_enum(unsafe <PacTraitToImplement>)'");
}
let pac_enum = PacEnumItem::new(&input);

let attr = parse_macro_input!(attr as PacTrait);

let trait_impl = match attrs[1] {
"ExceptionNumber" => pac_enum.quote("ExceptionNumber", "usize", "MAX_EXCEPTION_NUMBER"),
"InterruptNumber" => pac_enum.quote("InterruptNumber", "usize", "MAX_INTERRUPT_NUMBER"),
"PriorityNumber" => pac_enum.quote("PriorityNumber", "u8", "MAX_PRIORITY_NUMBER"),
"HartIdNumber" => pac_enum.quote("HartIdNumber", "u16", "MAX_HART_ID_NUMBER"),
_ => panic!("Unknown trait '{}'. Expected: 'ExceptionNumber', 'InterruptNumber', 'PriorityNumber', or 'HartIdNumber'", attrs[1]),
};
let trait_impl = pac_enum.impl_trait(&attr);
quote! {
#input
#trait_impl
#(#trait_impl)*
}
.into()
}
4 changes: 2 additions & 2 deletions riscv-pac/tests/ui/fail_empty_macro.stderr
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
error: custom attribute panicked
error: unexpected end of input, expected `unsafe`
--> tests/ui/fail_empty_macro.rs:1:1
|
1 | #[riscv_pac::pac_enum]
| ^^^^^^^^^^^^^^^^^^^^^^
|
= help: message: Attribute is empty. Expected: 'riscv_pac::pac_enum(unsafe <PacTraitToImplement>)'
= note: this error originates in the attribute macro `riscv_pac::pac_enum` (in Nightly builds, run with -Z macro-backtrace for more info)
8 changes: 3 additions & 5 deletions riscv-pac/tests/ui/fail_more_than_one_trait.stderr
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
error: custom attribute panicked
--> tests/ui/fail_more_than_one_trait.rs:1:1
error: Unknown trait name. Expected: 'ExceptionNumber', 'CoreInterruptNumber', 'ExternalInterruptNumber', 'PriorityNumber', or 'HartIdNumber'
--> tests/ui/fail_more_than_one_trait.rs:1:30
|
1 | #[riscv_pac::pac_enum(unsafe InterruptNumber, unsafe PriorityNumber)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: message: Wrong attribute format. Expected: 'riscv_pac::pac_enum(unsafe <PacTraitToImplement>)'
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
8 changes: 3 additions & 5 deletions riscv-pac/tests/ui/fail_no_unsafe.stderr
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
error: custom attribute panicked
--> tests/ui/fail_no_unsafe.rs:1:1
error: expected `unsafe`
--> tests/ui/fail_no_unsafe.rs:1:23
|
1 | #[riscv_pac::pac_enum(InterruptNumber)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: message: Attribute does not start with 'unsafe'. Expected: 'riscv_pac::pac_enum(unsafe <PacTraitToImplement>)'
| ^^^^^^^^^^^^^^^
2 changes: 1 addition & 1 deletion riscv-pac/tests/ui/fail_unknown_trait.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#[riscv_pac::pac_enum(unsafe CoreInterruptNumber)]
#[riscv_pac::pac_enum(unsafe InterruptNumber)]
#[derive(Clone, Copy, Debug, PartialEq)]
enum Interrupt {
I1 = 1,
Expand Down
10 changes: 4 additions & 6 deletions riscv-pac/tests/ui/fail_unknown_trait.stderr
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
error: custom attribute panicked
--> tests/ui/fail_unknown_trait.rs:1:1
error: Unknown trait name. Expected: 'ExceptionNumber', 'CoreInterruptNumber', 'ExternalInterruptNumber', 'PriorityNumber', or 'HartIdNumber'
--> tests/ui/fail_unknown_trait.rs:1:30
|
1 | #[riscv_pac::pac_enum(unsafe CoreInterruptNumber)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: message: Unknown trait 'CoreInterruptNumber'. Expected: 'ExceptionNumber', 'InterruptNumber', 'PriorityNumber', or 'HartIdNumber'
1 | #[riscv_pac::pac_enum(unsafe InterruptNumber)]
| ^^^^^^^^^^^^^^^
Loading

0 comments on commit 9dc9b41

Please sign in to comment.