Skip to content

Commit

Permalink
feat: add hermit-macro crate with system attribute
Browse files Browse the repository at this point in the history
Signed-off-by: Martin Kröning <[email protected]>
  • Loading branch information
mkroening committed Mar 22, 2024
1 parent 65a674a commit c724f0f
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -152,6 +153,7 @@ llvm-tools = "0.1"

[workspace]
members = [
"hermit-macro",
"xtask",
]
exclude = [
Expand Down
12 changes: 12 additions & 0 deletions hermit-macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"] }
22 changes: 22 additions & 0 deletions hermit-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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(),
}
}
135 changes: 135 additions & 0 deletions hermit-macro/src/system.rs
Original file line number Diff line number Diff line change
@@ -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<Ident>,
}

fn parse_sig(sig: &Signature) -> Result<ParsedSig> {
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<ItemFn> {
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<Item> {
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(())
}
}

0 comments on commit c724f0f

Please sign in to comment.