From c724f0f0c36bf744e7af97a1028890eb8f6690fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Fri, 22 Mar 2024 14:37:03 +0100 Subject: [PATCH] feat: add hermit-macro crate with system attribute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Kröning --- .github/workflows/ci.yml | 2 + Cargo.lock | 10 +++ Cargo.toml | 2 + hermit-macro/Cargo.toml | 12 ++++ hermit-macro/src/lib.rs | 22 ++++++ hermit-macro/src/system.rs | 135 +++++++++++++++++++++++++++++++++++++ 6 files changed, 183 insertions(+) create mode 100644 hermit-macro/Cargo.toml create mode 100644 hermit-macro/src/lib.rs create mode 100644 hermit-macro/src/system.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7424697c2..499942f921 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,6 +109,8 @@ jobs: run: cargo test --lib env: RUSTFLAGS: -Awarnings + - name: Macro unit tests + run: cargo test --package hermit-macro - name: Download loader run: gh release download --repo hermit-os/loader --pattern hermit-loader-x86_64 - run: rustup target add x86_64-unknown-none diff --git a/Cargo.lock b/Cargo.lock index 431d5361c7..619285f10a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -556,6 +556,7 @@ dependencies = [ "hashbrown", "hermit-dtb", "hermit-entry", + "hermit-macro", "hermit-sync", "llvm-tools", "lock_api", @@ -585,6 +586,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hermit-macro" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + [[package]] name = "hermit-sync" version = "0.1.5" diff --git a/Cargo.toml b/Cargo.toml index 20a3e6f194..5fe06746ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,6 +70,7 @@ semihosting = ["dep:semihosting"] shell = ["simple-shell"] [dependencies] +hermit-macro = { path = "hermit-macro" } ahash = { version = "0.8", default-features = false } align-address = "0.1" bit_field = "0.10" @@ -152,6 +153,7 @@ llvm-tools = "0.1" [workspace] members = [ + "hermit-macro", "xtask", ] exclude = [ diff --git a/hermit-macro/Cargo.toml b/hermit-macro/Cargo.toml new file mode 100644 index 0000000000..eb04386fb0 --- /dev/null +++ b/hermit-macro/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "hermit-macro" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1" +quote = "1" +syn = { version = "2", features = ["full"] } diff --git a/hermit-macro/src/lib.rs b/hermit-macro/src/lib.rs new file mode 100644 index 0000000000..b33e62136d --- /dev/null +++ b/hermit-macro/src/lib.rs @@ -0,0 +1,22 @@ +use proc_macro::TokenStream; +use quote::ToTokens; +use syn::parse::Nothing; +use syn::parse_macro_input; + +macro_rules! bail { + ($span:expr, $($tt:tt)*) => { + return Err(syn::Error::new_spanned($span, format!($($tt)*))) + }; +} + +mod system; + +// The structure of this implementation is inspired by Amanieu's excellent naked-function crate. +#[proc_macro_attribute] +pub fn system(attr: TokenStream, item: TokenStream) -> TokenStream { + parse_macro_input!(attr as Nothing); + match system::system_attribute(parse_macro_input!(item)) { + Ok(item) => item.into_token_stream().into(), + Err(e) => e.to_compile_error().into(), + } +} diff --git a/hermit-macro/src/system.rs b/hermit-macro/src/system.rs new file mode 100644 index 0000000000..bf78d61a54 --- /dev/null +++ b/hermit-macro/src/system.rs @@ -0,0 +1,135 @@ +use proc_macro2::{Ident, Span}; +use quote::quote; +use syn::{Abi, Attribute, Item, ItemFn, Pat, Result, Signature, Visibility}; + +fn validate_vis(vis: &Visibility) -> Result<()> { + if !matches!(vis, Visibility::Public(_)) { + bail!(vis, "#[system] functions must be public"); + } + + Ok(()) +} + +struct ParsedSig { + args: Vec, +} + +fn parse_sig(sig: &Signature) -> Result { + if let Some(constness) = sig.constness { + bail!(constness, "#[system] is not supported on const functions"); + } + if let Some(asyncness) = sig.asyncness { + bail!(asyncness, "#[system] is not supported on async functions"); + } + match &sig.abi { + Some(Abi { + extern_token: _, + name: Some(name), + }) if matches!(&*name.value(), "C" | "C-unwind") => {} + _ => bail!( + &sig.abi, + "#[system] functions must be `extern \"C\"` or `extern \"C-unwind\"`" + ), + } + if !sig.generics.params.is_empty() { + bail!( + &sig.generics, + "#[system] cannot be used with generic functions" + ); + } + if !sig.ident.to_string().starts_with("sys_") { + bail!(&sig.ident, "#[system] functions must start with `sys_`"); + } + + let mut args = vec![]; + + for arg in &sig.inputs { + let pat = match arg { + syn::FnArg::Receiver(_) => bail!(arg, "self is not allowed in #[system] functions"), + syn::FnArg::Typed(pat) => pat, + }; + if let Pat::Ident(pat) = &*pat.pat { + args.push(pat.ident.clone()); + } else { + bail!(pat, "unsupported pattern in #[system] function argument"); + } + } + + Ok(ParsedSig { args }) +} + +fn validate_attrs(attrs: &[Attribute]) -> Result<()> { + for attr in attrs { + if attr.path().is_ident("no_mangle") { + bail!(attr, "#[system] functions must not be `#[no_mangle]`"); + } + } + + Ok(()) +} + +fn emit_func(mut func: ItemFn, sig: &ParsedSig) -> Result { + let args = &sig.args; + let vis = func.vis.clone(); + let sig = func.sig.clone(); + + let ident = Ident::new(&format!("__{}", func.sig.ident), Span::call_site()); + func.sig.ident = ident.clone(); + func.vis = Visibility::Inherited; + + let func = syn::parse2(quote! { + #[no_mangle] + #vis #sig { + #func + + kernel_function!(#ident(#(#args),*)) + } + })?; + + Ok(func) +} + +pub fn system_attribute(func: ItemFn) -> Result { + validate_vis(&func.vis)?; + let sig = parse_sig(&func.sig)?; + validate_attrs(&func.attrs)?; + let func = emit_func(func, &sig)?; + // println!("{}", func.to_token_stream()); + // panic!(); + Ok(Item::Fn(func)) +} + +#[cfg(test)] +mod tests { + use quote::ToTokens; + + use super::*; + + #[test] + fn test_system_attribute() -> Result<()> { + let input = syn::parse2(quote! { + pub extern "C" fn sys_test(a: i8, b: i16) -> i32 { + let c = i16::from(a) + b; + i32::from(c) + } + })?; + + let expected = quote! { + #[no_mangle] + pub extern "C" fn sys_test(a: i8, b: i16) -> i32 { + extern "C" fn __sys_test(a: i8, b: i16) -> i32 { + let c = i16::from(a) + b; + i32::from(c) + } + + kernel_function!(__sys_test(a, b)) + } + }; + + let result = system_attribute(input)?.into_token_stream(); + + assert_eq!(expected.to_string(), result.to_string()); + + Ok(()) + } +}