diff --git a/nds-proc-macros/Cargo.toml b/nds-proc-macros/Cargo.toml index f12f5fb..902e9ef 100644 --- a/nds-proc-macros/Cargo.toml +++ b/nds-proc-macros/Cargo.toml @@ -11,4 +11,4 @@ proc-macro = true [dependencies] syn = { version = "2.0", features = ["full"]} -quote = "1.0" \ No newline at end of file +quote = "1.0" diff --git a/nds-proc-macros/src/lib.rs b/nds-proc-macros/src/lib.rs index 436ee6f..c73050f 100644 --- a/nds-proc-macros/src/lib.rs +++ b/nds-proc-macros/src/lib.rs @@ -1,21 +1,109 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, ItemFn, Meta, MetaNameValue, ReturnType, Type}; +use syn::{ + parse_macro_input, spanned::Spanned, Attribute, FnArg, ItemFn, Meta, MetaNameValue, PatType, + ReturnType, Type, Visibility, +}; -/// Use it to mark the ROM entry point. +/// Allows only `fn(Hw) -> !` +fn check_sig(signature: &syn::Signature) -> Result<(), syn::Error> { + let span = signature.span(); + if let ReturnType::Type(_, ref t) = signature.output { + match **t { + Type::Never(_) => {} + Type::Infer(_) => { + return Err(syn::Error::new( + span, + "Add `-> !`. To avoid ambiguity, please specify the return type as `!`", + )); + } + _ => { + return Err(syn::Error::new( + span, + "Change to `-> !`. The entry point must be diverging. E.g. `fn() -> !`", + )); + } + } + } else { + return Err(syn::Error::new( + span, + "Add `-> !`. To avoid ambiguity, please specify the return type as `!`.", + )); + } + + if signature.inputs.len() != 1 { + return Err(syn::Error::new( + span, + "Incorrect arguments. The entry point must take exactly one argument of type `Hw`", + )); + } else { + let arg = signature.inputs.first().unwrap(); + if let FnArg::Typed(PatType { ty, .. }) = arg { + if let Type::Path(ref path) = **ty { + if path.path.segments.last().unwrap().ident != "Hw" { + return Err(syn::Error::new( + arg.span(), + "Change type. The entry point must take exactly one argument of type `Hw`", + )); + } + } else { + return Err(syn::Error::new( + arg.span(), + "Change type. The entry point must take exactly one argument of type `Hw`", + )); + } + } else { + return Err(syn::Error::new( + arg.span(), + "Change type. The entry point can not be a method.", + )); + } + } + + Ok(()) +} + +/// Allow only `pub` visibility +fn check_vis(vis: &Visibility) -> Result<(), syn::Error> { + if let Visibility::Public(..) = vis { + Ok(()) + } else { + Err(syn::Error::new( + vis.span(), + "Add `pub`. The entry point must be public and without restriction.", + )) + } +} + +/// Forbids `#[no_mangle]` and `#[link_name = ...]` +fn check_attr(attrs: &Vec) -> Result<(), syn::Error> { + for attr in attrs { + match &attr.meta { + Meta::NameValue(MetaNameValue { path, .. }) if path.is_ident("export_name") => { + return Err(syn::Error::new(path.span(), "Remove this `link_name`. The entry point requires a specific symbol name that it's set by the `entry` attribute.")) + } + Meta::Path(path) if path.is_ident("no_mangle") => { + return Err(syn::Error::new(path.span(), "Remove this `no_mangle`. The entry point requires a specific symbol name that it's set by the `entry` attribute.")) + } + _ => {} + } + } + + Ok(()) +} + +/// Use it to mark the entry point of your program. /// -/// Example: +/// # Example: /// ```rust,no_run /// #![no_main] /// /// #[macro_use] -/// extern crate nds: +/// extern crate nds_proc_macros: /// /// #[entry] -/// fn main(mut hw: nds::Hw) -> ! { -/// loop { -/// nds::interrupts::swi_wait_for_v_blank(); -/// } +/// pub fn main(_: nds::Hw) -> ! { +/// loop { } /// } /// ``` #[proc_macro_attribute] @@ -24,37 +112,24 @@ pub fn entry(_: TokenStream, input: TokenStream) -> TokenStream { // Check the signature is correct: // - Must be `fn() -> !` - // - Must be `pub` // - Must have 1 argument - // - Must not have `link_name` or `no_mangle` attributes + // - Must be `pub` + // - Must not have `export_name` or `no_mangle` attributes - if let ReturnType::Type(_, t) = &input.sig.output { - assert!( - matches!(**t, Type::Never(_)), - "The function must return `!`" - ); - } else { - panic!("The function must return `!`"); + if let Err(e) = check_sig(&input.sig) { + return e.to_compile_error().into(); } - assert!(matches!(input.vis, syn::Visibility::Public(_))); + if let Err(e) = check_vis(&input.vis) { + return e.to_compile_error().into(); + } - assert_eq!(input.sig.inputs.len(), 1); - - for attr in &input.attrs { - match &attr.meta { - Meta::NameValue(MetaNameValue { path, .. }) if path.is_ident("link_name") => { - panic!("The `link_name` attribute is not allowed here"); - } - Meta::Path(path) if path.is_ident("no_mangle") => { - panic!("The `no_mangle` attribute is not allowed here"); - } - _ => {} - } + if let Err(e) = check_attr(&input.attrs) { + return e.to_compile_error().into(); } quote! { - #[link_name = "rust_main"] + #[export_name = "__rust_user_main"] #input } .into() diff --git a/nds-rt/src/lib.rs b/nds-rt/src/lib.rs index e3c4f43..b3f19ea 100644 --- a/nds-rt/src/lib.rs +++ b/nds-rt/src/lib.rs @@ -2,14 +2,19 @@ use nds_rs::Hw; +/// Entry point called from the C runtime +/// +/// # Safety +/// +/// Must never be called by user code. #[no_mangle] -pub extern "C" fn main() -> ! { +pub unsafe extern "C" fn main() -> ! { extern "Rust" { - #[link_name = "rust_main"] + #[link_name = "__rust_user_main"] fn main(hw: Hw) -> !; } - let peripherals = Hw::take().unwrap(); + let peripherals = Hw::take().unwrap_unchecked(); unsafe { main(peripherals); }