use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{
parse_macro_input, punctuated::Punctuated, spanned::Spanned, token::Comma, Error, FnArg, Ident,
ItemFn, Path, Result, ReturnType, Type, Visibility,
};
pub mod asm;
pub struct Trap {
path: syn::Path,
ty: TrapType,
}
pub enum TrapType {
Exception,
CoreInterrupt,
ExternalInterrupt,
}
impl TrapType {
fn impl_trait(&self) -> &str {
match self {
Self::Exception => "riscv_rt::ExceptionNumber",
Self::ExternalInterrupt => "riscv_rt::ExternalInterruptNumber",
Self::CoreInterrupt => "riscv_rt::CoreInterruptNumber",
}
}
}
pub enum Fn {
PostInit,
SetupInterrupts,
Entry,
Trap(Trap),
}
impl Fn {
pub fn post_init(args: TokenStream, input: TokenStream) -> TokenStream {
let errors = Self::PostInit.check_args_empty(args).err();
Self::PostInit.quote_fn(input, errors)
}
pub fn setup_interrupts(args: TokenStream, input: TokenStream) -> TokenStream {
let errors = Self::SetupInterrupts.check_args_empty(args).err();
Self::SetupInterrupts.quote_fn(input, errors)
}
pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
let errors = Self::Entry.check_args_empty(args).err();
Self::Entry.quote_fn(input, errors)
}
pub fn trap(args: TokenStream, input: TokenStream, ty: TrapType) -> TokenStream {
let (path, errors) = match syn::parse::<Path>(args) {
Ok(path) => (path, None),
Err(e) => {
let path = syn::parse_str("invalid").unwrap();
let impl_trait = ty.impl_trait();
let err = Error::new(
e.span(),
format!("attribute expects a path to a variant of an enum that implements the `{impl_trait}` trait"),
);
(path, Some(err))
}
};
let trap = Trap { path, ty };
Self::Trap(trap).quote_fn(input, errors)
}
fn quote_fn(&self, item: TokenStream, mut errors: Option<Error>) -> TokenStream {
let mut func = parse_macro_input!(item as ItemFn);
self.check_fn(&func, &mut errors);
let extras = self.add_extras(&mut func, &mut errors);
let export_name = self.export_name(&func);
let link_section = self.link_section(&func);
let tokens = match errors {
Some(err) => err.to_compile_error(),
None => quote! {
#export_name
#link_section
#func
#extras
},
};
tokens.into()
}
fn check_fn(&self, f: &ItemFn, errors: &mut Option<Error>) {
if f.vis != Visibility::Inherited {
combine_err(errors, Error::new(f.vis.span(), "function must be private"));
}
let sig = &f.sig;
if let Some(constness) = sig.constness {
let span = constness.span();
combine_err(errors, Error::new(span, "function must not be const"));
}
if let Some(asyncness) = sig.asyncness {
let span = asyncness.span();
combine_err(errors, Error::new(span, "function must not be async"));
}
if let Some(abi) = &sig.abi {
combine_err(errors, Error::new(abi.span(), "ABI must not be specified"));
}
if !sig.generics.params.is_empty() {
let span = sig.generics.params.span();
combine_err(errors, Error::new(span, "generics are not allowed"));
}
self.check_inputs(&sig.inputs, errors);
if let Some(variadic) = &sig.variadic {
combine_err(
errors,
Error::new(variadic.span(), "variadic arguments are not allowed"),
);
}
self.check_output(&sig.output, errors);
if let Some(where_clause) = &sig.generics.where_clause {
let span = where_clause.span();
combine_err(errors, Error::new(span, "where clause is not allowed"));
}
}
fn check_inputs(&self, inputs: &Punctuated<FnArg, Comma>, errors: &mut Option<Error>) {
match self {
Self::PostInit | Self::SetupInterrupts => {
#[cfg(not(feature = "rvrt-u-boot"))]
self.check_fn_args(inputs, &["usize"], errors);
#[cfg(feature = "rvrt-u-boot")]
self.check_fn_args(inputs, &[], errors);
}
#[cfg(not(feature = "rvrt-u-boot"))]
Self::Entry => self.check_fn_args(inputs, &["usize", "usize", "usize"], errors),
#[cfg(feature = "rvrt-u-boot")]
Self::Entry => self.check_fn_args(inputs, &["c_int", "*const *const c_char"], errors),
Self::Trap(Trap { ty, .. }) => match ty {
TrapType::Exception => {
self.check_fn_args(inputs, &["&riscv_rt::TrapFrame"], errors)
}
TrapType::CoreInterrupt | TrapType::ExternalInterrupt => {
self.check_fn_args(inputs, &[], errors)
}
},
}
}
fn check_output(&self, output: &ReturnType, errors: &mut Option<Error>) {
match self {
Self::PostInit | Self::SetupInterrupts => check_output_empty(output, errors),
Self::Entry => check_output_never(output, errors),
Self::Trap(_) => check_output_empty_or_never(output, errors),
}
}
fn add_extras(&self, func: &mut ItemFn, errors: &mut Option<Error>) -> Option<TokenStream2> {
func.sig.ident = Ident::new(
&format!("__riscv_rt_{}", func.sig.ident),
func.sig.ident.span(),
);
match self {
Self::PostInit | Self::SetupInterrupts | Self::Entry => None,
Self::Trap(Trap { path, ty }) => {
let mut extras = vec![];
func.sig.abi = Some(syn::parse(quote! { extern "C" }.into()).unwrap());
let impl_trait = format!("::{}", ty.impl_trait());
let impl_trait: Path = syn::parse_str(&impl_trait).unwrap();
extras.push(quote! {
const _: fn() = || {
fn assert_impl<T: #impl_trait>(_arg: T) {}
assert_impl(#path);
};
});
if cfg!(feature = "rt-v-trap") && matches!(ty, TrapType::CoreInterrupt) {
let interr_ident = &path.segments.last().unwrap().ident;
match asm::RiscvArch::try_from_env() {
Some(arch) => extras.push(arch.start_interrupt_trap(interr_ident)),
None => combine_err(errors, Error::new(
path.span(),
"RISCV_RT_BASE_ISA must be defined for core interrupt handlers when `v-trap` feature is enabled",
)),
}
}
Some(quote! { #(#extras)* })
}
}
}
fn export_name(&self, _f: &ItemFn) -> Option<TokenStream2> {
let export_name = match self {
Self::PostInit => Some("__post_init".to_string()),
Self::SetupInterrupts => Some("_setup_interrupts".to_string()),
Self::Entry => Some("main".to_string()),
Self::Trap(Trap { path, .. }) => Some(path.segments.last().unwrap().ident.to_string()),
};
export_name.map(|name| match self {
Self::PostInit | Self::SetupInterrupts | Self::Trap(_) => quote! {
#[export_name = #name]
},
Self::Entry => quote! {
#[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), export_name = #name)]
},
})
}
fn link_section(&self, _f: &ItemFn) -> Option<TokenStream2> {
let section_name: Option<String> = match self {
Self::PostInit | Self::SetupInterrupts | Self::Entry | Self::Trap(_) => None,
};
section_name.map(|section| quote! {
#[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), link_section = #section)]
})
}
fn check_args_empty(&self, args: TokenStream) -> Result<()> {
if args.is_empty() {
Ok(())
} else {
let args: TokenStream2 = args.into();
Err(Error::new(args.span(), "macro arguments are not allowed"))
}
}
fn check_fn_args(
&self,
inputs: &Punctuated<FnArg, Comma>,
expected_types: &[&str],
errors: &mut Option<Error>,
) {
let mut expected_iter = expected_types.iter();
for arg in inputs.iter() {
match expected_iter.next() {
Some(expected) => {
if let Err(e) = check_arg_type(arg, expected) {
combine_err(errors, e);
}
}
None => {
combine_err(errors, Error::new(arg.span(), "too many input arguments"));
}
}
}
}
}
fn combine_err(acc: &mut Option<Error>, err: Error) {
match acc {
Some(e) => e.combine(err),
None => *acc = Some(err),
}
}
fn check_arg_type(arg: &FnArg, expected: &str) -> Result<()> {
match arg {
FnArg::Typed(argument) => {
if !is_correct_type(&argument.ty, expected) {
Err(Error::new(
argument.ty.span(),
format!("argument type must be `{expected}`"),
))
} else {
Ok(())
}
}
FnArg::Receiver(_) => Err(Error::new(arg.span(), "invalid argument")),
}
}
fn is_correct_type(ty: &Type, expected: &str) -> bool {
let mut correct = syn::parse_str(expected).unwrap();
correct = strip_type_path(&correct).unwrap();
if let Some(ty) = strip_type_path(ty) {
ty == correct
} else {
false
}
}
fn strip_type_path(ty: &Type) -> Option<Type> {
match ty {
Type::Ptr(ty) => {
let mut ty = ty.clone();
*ty.elem = strip_type_path(&ty.elem)?;
Some(Type::Ptr(ty))
}
Type::Reference(ty) => {
let mut ty = ty.clone();
ty.mutability = None;
ty.elem = Box::new(strip_type_path(&ty.elem)?);
Some(Type::Reference(ty))
}
Type::Path(ty) => {
let mut ty = ty.clone();
let last_segment = ty.path.segments.last().unwrap().clone();
ty.path.segments = Punctuated::new();
ty.path.segments.push_value(last_segment);
Some(Type::Path(ty))
}
_ => None,
}
}
fn check_output_empty(output: &ReturnType, errors: &mut Option<Error>) {
let is_valid = matches!(output, ReturnType::Default)
|| matches!(output, ReturnType::Type(_, ty) if matches!(**ty, Type::Tuple(ref tuple) if tuple.elems.is_empty()));
if !is_valid {
combine_err(errors, Error::new(output.span(), "return type must be ()"));
}
}
fn check_output_never(output: &ReturnType, errors: &mut Option<Error>) {
if !matches!(output, ReturnType::Type(_, ty) if matches!(**ty, Type::Never(_))) {
combine_err(errors, Error::new(output.span(), "return type must be !"));
}
}
fn check_output_empty_or_never(output: &ReturnType, errors: &mut Option<Error>) {
let is_valid = matches!(output, ReturnType::Default)
|| matches!(output, ReturnType::Type(_, ty) if matches!(**ty, Type::Tuple(ref tuple) if tuple.elems.is_empty()))
|| matches!(output, ReturnType::Type(_, ty) if matches!(**ty, Type::Never(_)));
if !is_valid {
combine_err(
errors,
Error::new(output.span(), "return type must be () or !"),
);
}
}