extern crate proc_macro;
use proc_macro::{TokenStream, TokenTree};
use proc_macro2::Span;
use quote::quote;
use syn::{
parse, parse_macro_input, spanned::Spanned, AttrStyle, Attribute, Ident, ItemFn, ReturnType,
Type, Visibility,
};
#[proc_macro_attribute]
pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
let f = parse_macro_input!(input as ItemFn);
let valid_signature = f.sig.constness.is_none()
&& f.vis == Visibility::Inherited
&& f.sig.abi.is_none()
&& f.sig.inputs.is_empty()
&& f.sig.generics.params.is_empty()
&& f.sig.generics.where_clause.is_none()
&& f.sig.variadic.is_none()
&& match f.sig.output {
ReturnType::Default => false,
ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
};
if !valid_signature {
return parse::Error::new(
f.span(),
"`#[entry]` function must have signature `[unsafe] fn() -> !`",
)
.to_compile_error()
.into();
}
if !args.is_empty() {
return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
.to_compile_error()
.into();
}
let tramp_ident = Ident::new("__aarch32_rt_kmain", Span::call_site());
let block = f.block;
if let Err(error) = check_attr_whitelist(&f.attrs, VectorKind::Entry) {
return error;
}
let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
quote!(
#(#cfgs)*
#(#attrs)*
#[doc(hidden)]
#[export_name = "kmain"]
pub unsafe extern "C" fn #tramp_ident() -> ! {
#block
}
)
.into()
}
#[derive(Debug, PartialEq)]
enum Exception {
Undefined,
SupervisorCall,
PrefetchAbort,
DataAbort,
Irq,
}
impl std::fmt::Display for Exception {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Exception::Undefined => write!(f, "Undefined"),
Exception::SupervisorCall => write!(f, "SupervisorCall"),
Exception::PrefetchAbort => write!(f, "PrefetchAbort"),
Exception::DataAbort => write!(f, "DataAbort"),
Exception::Irq => write!(f, "Irq"),
}
}
}
#[proc_macro_attribute]
pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream {
handle_vector(args, input, VectorKind::Exception)
}
#[proc_macro_attribute]
pub fn irq(args: TokenStream, input: TokenStream) -> TokenStream {
handle_vector(args, input, VectorKind::Interrupt)
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
enum VectorKind {
Entry,
Exception,
Interrupt,
}
fn handle_vector(args: TokenStream, input: TokenStream, kind: VectorKind) -> TokenStream {
let f = parse_macro_input!(input as ItemFn);
if let Err(error) = check_attr_whitelist(&f.attrs, kind) {
return error;
}
let returns_never = match f.sig.output {
ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
_ => false,
};
let exception = match kind {
VectorKind::Entry => {
panic!("Don't handle #[entry] with `handle_vector`!");
}
VectorKind::Exception => {
let mut args_iter = args.into_iter();
let Some(TokenTree::Ident(exception_name)) = args_iter.next() else {
return parse::Error::new(
Span::call_site(),
"This attribute requires the name of the exception as the first argument",
)
.to_compile_error()
.into();
};
if args_iter.next().is_some() {
return parse::Error::new(
Span::call_site(),
"This attribute accepts only one argument",
)
.to_compile_error()
.into();
}
match exception_name.to_string().as_str() {
"Undefined" => {
if !returns_never && f.sig.unsafety.is_none() {
return parse::Error::new(
exception_name.span().into(),
"Undefined handlers that don't return ! must be unsafe",
)
.to_compile_error()
.into();
}
Exception::Undefined
}
"SupervisorCall" => Exception::SupervisorCall,
"PrefetchAbort" => {
if !returns_never && f.sig.unsafety.is_none() {
return parse::Error::new(
exception_name.span().into(),
"PrefetchAbort handlers that don't return ! must be unsafe",
)
.to_compile_error()
.into();
}
Exception::PrefetchAbort
}
"DataAbort" => {
if !returns_never && f.sig.unsafety.is_none() {
return parse::Error::new(
exception_name.span().into(),
"DataAbort handlers that don't return ! must be unsafe",
)
.to_compile_error()
.into();
}
Exception::DataAbort
}
"Irq" => Exception::Irq,
_ => {
return parse::Error::new(
exception_name.span().into(),
"This is not a valid exception name",
)
.to_compile_error()
.into();
}
}
}
VectorKind::Interrupt => Exception::Irq,
};
let block = f.block;
let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
let handler = match exception {
Exception::Undefined => {
let tramp_ident = Ident::new("__aarch32_rt_undefined_handler", Span::call_site());
if returns_never {
quote!(
#(#cfgs)*
#(#attrs)*
#[doc(hidden)]
#[export_name = "_undefined_handler"]
pub unsafe extern "C" fn #tramp_ident(addr: usize) -> ! {
#block
}
)
} else {
quote!(
#(#cfgs)*
#(#attrs)*
#[doc(hidden)]
#[export_name = "_undefined_handler"]
pub unsafe extern "C" fn #tramp_ident(addr: usize) -> usize {
#block
}
)
}
}
Exception::PrefetchAbort => {
let tramp_ident = Ident::new("__aarch32_rt_prefetch_abort_handler", Span::call_site());
if returns_never {
quote!(
#(#cfgs)*
#(#attrs)*
#[doc(hidden)]
#[export_name = "_prefetch_abort_handler"]
pub unsafe extern "C" fn #tramp_ident(addr: usize) -> ! {
#block
}
)
} else {
quote!(
#(#cfgs)*
#(#attrs)*
#[doc(hidden)]
#[export_name = "_prefetch_abort_handler"]
pub unsafe extern "C" fn #tramp_ident(addr: usize) -> usize {
#block
}
)
}
}
Exception::DataAbort => {
let tramp_ident = Ident::new("__aarch32_rt_data_abort_handler", Span::call_site());
if returns_never {
quote!(
#(#cfgs)*
#(#attrs)*
#[doc(hidden)]
#[export_name = "_data_abort_handler"]
pub unsafe extern "C" fn #tramp_ident(addr: usize) -> ! {
#block
}
)
} else {
quote!(
#(#cfgs)*
#(#attrs)*
#[doc(hidden)]
#[export_name = "_data_abort_handler"]
pub unsafe extern "C" fn #tramp_ident(addr: usize) -> usize {
#block
}
)
}
}
Exception::SupervisorCall => {
let tramp_ident = Ident::new("__aarch32_rt_svc_handler", Span::call_site());
quote!(
#(#cfgs)*
#(#attrs)*
#[doc(hidden)]
#[export_name = "_svc_handler"]
pub unsafe extern "C" fn #tramp_ident(arg: u32) {
#block
}
)
}
Exception::Irq => {
let tramp_ident = Ident::new("__aarch32_rt_irq_handler", Span::call_site());
quote!(
#(#cfgs)*
#(#attrs)*
#[doc(hidden)]
#[export_name = "_irq_handler"]
pub unsafe extern "C" fn #tramp_ident() {
#block
}
)
}
};
quote!(
#handler
)
.into()
}
fn extract_cfgs(attrs: Vec<Attribute>) -> (Vec<Attribute>, Vec<Attribute>) {
let mut cfgs = vec![];
let mut not_cfgs = vec![];
for attr in attrs {
if eq(&attr, "cfg") {
cfgs.push(attr);
} else {
not_cfgs.push(attr);
}
}
(cfgs, not_cfgs)
}
fn check_attr_whitelist(attrs: &[Attribute], caller: VectorKind) -> Result<(), TokenStream> {
let whitelist = &[
"doc",
"link_section",
"cfg",
"allow",
"warn",
"deny",
"forbid",
"cold",
"naked",
"expect",
];
'o: for attr in attrs {
for val in whitelist {
if eq(attr, val) {
continue 'o;
}
}
let err_str = match caller {
VectorKind::Entry => "this attribute is not allowed on an aarch32-rt entry point",
VectorKind::Exception => {
"this attribute is not allowed on an exception handler controlled by aarch32-rt"
}
VectorKind::Interrupt => {
"this attribute is not allowed on an interrupt handler controlled by aarch32-rt"
}
};
return Err(parse::Error::new(attr.span(), err_str)
.to_compile_error()
.into());
}
Ok(())
}
fn eq(attr: &Attribute, name: &str) -> bool {
attr.style == AttrStyle::Outer && attr.path().is_ident(name)
}