Browse Source

macro: declare inner attribute `#[rustsbi(skip)]`

Signed-off-by: Zhouqi Jiang <luojia@hust.edu.cn>
Zhouqi Jiang 1 year ago
parent
commit
70e10d77c0
4 changed files with 195 additions and 12 deletions
  1. 28 9
      macros/src/lib.rs
  2. 40 1
      src/lib.rs
  3. 0 2
      tests/build-full.rs
  4. 127 0
      tests/build-skip.rs

+ 28 - 9
macros/src/lib.rs

@@ -1,5 +1,5 @@
 use proc_macro::TokenStream;
-use quote::quote;
+use quote::{quote, ToTokens};
 use syn::{parse_macro_input, punctuated::Punctuated, Data, DeriveInput, Fields, Generics, Ident};
 
 #[derive(Clone, Default)]
@@ -32,15 +32,35 @@ pub fn derive_rustsbi(input: TokenStream) -> TokenStream {
         Fields::Unnamed(f) => f.unnamed,
         Fields::Unit => Punctuated::new(),
     };
-
     let mut imp = RustSBIImp::default();
+
+    let mut ans = TokenStream::new();
+
     for field in &fields {
-        // for attr in field.attrs {
-        //     if let Meta::List(list) = attr.meta {
-        //         let vars =
-        //             Punctuated::<Ident, syn::Token![,]>::parse_terminated(&list.tokens).unwrap();
-        //     }
-        // }
+        let mut skipped = false;
+        for attr in &field.attrs {
+            if !attr.path().is_ident("rustsbi") {
+                continue
+            }
+            let parsed = attr.parse_nested_meta(|meta| {
+                if meta.path.is_ident("skip") {
+                    // skip this field in RustSBI
+                    skipped = true;
+                    Ok(())
+                } else {
+                    let path = meta.path.to_token_stream().to_string().replace(' ', "");
+                    Err(
+                        meta.error(format_args!("unknown rustsbi variant attribute `{}`", path))
+                    )
+                }
+            });
+            if let Err(err) = parsed {
+                ans.extend(TokenStream::from(err.to_compile_error()));
+            }
+        }
+        if skipped {
+            continue
+        }
         if let Some(name) = &field.ident {
             match name.to_string().as_str() {
                 "rfnc" | "fence" => imp.fence = Some(name),
@@ -60,7 +80,6 @@ pub fn derive_rustsbi(input: TokenStream) -> TokenStream {
         }
     }
 
-    let mut ans = TokenStream::new();
     ans.extend(impl_derive_rustsbi(&input.ident, imp, &input.generics));
     ans
 }

+ 40 - 1
src/lib.rs

@@ -806,9 +806,14 @@ pub extern crate sbi_spec as spec;
 ///
 /// ```rust
 /// #[derive(RustSBI)]
-/// struct MySBI<'a, T: rustsbi::Fence> {
+/// struct MySBI<'a, T: rustsbi::Fence, U, const N: usize>
+/// where
+///     U: rustsbi::Timer,
+/// {
 ///     fence: T,
+///     timer: U,
 ///     info: &'a MyEnvInfo,
+///     _dummy: [u8; N],
 /// }
 ///
 /// # use rustsbi::{RustSBI, HartMask};
@@ -827,6 +832,40 @@ pub extern crate sbi_spec as spec;
 /// # }
 /// ```
 ///
+/// Inner attribute `#[rustsbi(skip)]` informs the macro to skip a certain field when
+/// generating a RustSBI implementation.
+/// 
+/// ```rust
+/// #[derive(RustSBI)]
+/// struct MySBI {
+///     console: MyConsole,
+///     #[rustsbi(skip)]
+///     fence: MyFence,
+///     info: MyEnvInfo,
+/// }
+///
+/// // The derived `MySBI` implementation ignores the `fence: MyFence`. It can now
+/// // be used as a conventional struct field.
+/// // Notably, a `#[warn(unused)]` would be raised if `fence` is not furtherly used
+/// // by following code; `console` and `info` fields are not warned because they are
+/// // internally used by the trait implementation derived in the RustSBI macro.
+/// # use rustsbi::RustSBI;
+/// # use sbi_spec::binary::{SbiRet, Physical};
+/// # struct MyConsole;
+/// # impl rustsbi::Console for MyConsole {
+/// #     fn write(&self, _: Physical<&[u8]>) -> SbiRet { unimplemented!() }
+/// #     fn read(&self, _: Physical<&mut [u8]>) -> SbiRet { unimplemented!() }
+/// #     fn write_byte(&self, _: u8) -> SbiRet { unimplemented!() }
+/// # }
+/// # struct MyFence;
+/// # struct MyEnvInfo;
+/// # impl rustsbi::EnvInfo for MyEnvInfo {
+/// #     fn mvendorid(&self) -> usize { 1 }
+/// #     fn marchid(&self) -> usize { 2 }
+/// #     fn mimpid(&self) -> usize { 3 }
+/// # }
+/// ```
+///
 /// # Notes
 // note: following documents are inherted from `RustSBI` in the `rustsbi_macros` package.
 #[doc(inline)]

+ 0 - 2
tests/build-full.rs

@@ -4,8 +4,6 @@ use sbi_spec::{
     nacl::shmem_size::NATIVE,
 };
 
-// This struct should pass Rust build.
-
 #[derive(RustSBI)]
 struct FullyImplemented {
     console: DummyConsole,

+ 127 - 0
tests/build-skip.rs

@@ -0,0 +1,127 @@
+use sbi_spec::binary::{Physical, SbiRet};
+use rustsbi::RustSBI;
+
+#[derive(RustSBI)]
+struct SkipExtension {
+    console: DummyConsole,
+    #[rustsbi(skip)]
+    fence: DummyFence,
+    info: DummyEnvInfo,
+}
+
+#[derive(RustSBI)]
+struct SkipEnvInfo {
+    console: DummyConsole,
+    fence: DummyFence,
+    #[rustsbi(skip)]
+    info: DummyEnvInfo,
+    env_info: RealEnvInfo,
+}
+
+#[test]
+fn rustsbi_skip_extension() {
+    let sbi = SkipExtension {
+        console: DummyConsole,
+        fence: DummyFence,
+        info: DummyEnvInfo,
+    };
+    // 1. Skipped fields are neither used during RustSBI macro generation, ...
+    assert_eq!(sbi.handle_ecall(0x4442434E, 0x0, [0; 6]), SbiRet::success(1));
+    assert_eq!(sbi.handle_ecall(0x4442434E, 0x1, [0; 6]), SbiRet::success(2));
+    assert_eq!(sbi.handle_ecall(0x4442434E, 0x2, [0; 6]), SbiRet::success(3));
+    assert_eq!(sbi.handle_ecall(0x52464E43, 0x0, [0; 6]), SbiRet::not_supported());
+    assert_eq!(sbi.handle_ecall(0x52464E43, 0x1, [0; 6]), SbiRet::not_supported());
+    assert_eq!(sbi.handle_ecall(0x52464E43, 0x2, [0; 6]), SbiRet::not_supported());
+    // 2. ... nor do they appear in extension detection in Base extension.
+    // note that it's `assert_ne` - the handle_ecall should not return a success detection with 
+    // value 0 indicating this feature is not supported, ...
+    assert_ne!(sbi.handle_ecall(0x10, 0x3, [0x4442434E, 0, 0, 0, 0, 0]), SbiRet::success(0));
+    // ... and the `assert_eq` here means extension detection detected successfully that this
+    // extension is not supported in SBI implementation `SkipExtension`.
+    assert_eq!(sbi.handle_ecall(0x10, 0x3, [0x52464E43, 0, 0, 0, 0, 0]), SbiRet::success(0));
+    // Additionally, we illustrate here that the skipped fields may be used elsewhere.
+    let _ = sbi.fence;
+}
+
+#[test]
+fn rustsbi_skip_env_info() {
+    let sbi = SkipEnvInfo {
+        console: DummyConsole,
+        fence: DummyFence,
+        info: DummyEnvInfo,
+        env_info: RealEnvInfo,
+    };
+    // The `env_info` instead of `info` field would be used by RustSBI macro; struct
+    // `RealEnvInfo` would return 7, 8 and 9 for mvendorid, marchid and mimpid.
+    assert_eq!(sbi.handle_ecall(0x10, 0x4, [0x4442434E, 0, 0, 0, 0, 0]), SbiRet::success(7));
+    assert_eq!(sbi.handle_ecall(0x10, 0x5, [0x4442434E, 0, 0, 0, 0, 0]), SbiRet::success(8));
+    assert_eq!(sbi.handle_ecall(0x10, 0x6, [0x4442434E, 0, 0, 0, 0, 0]), SbiRet::success(9));
+    let _ = sbi.info;
+}
+
+// Return values of following trait impls are special values,
+// they are used by software to detect if one implementation is used by RustSBI.
+
+struct DummyConsole;
+
+impl rustsbi::Console for DummyConsole {
+    fn write(&self, _: Physical<&[u8]>) -> SbiRet {
+        SbiRet::success(1)
+    }
+
+    fn read(&self, _: Physical<&mut [u8]>) -> SbiRet {
+        SbiRet::success(2)
+    }
+
+    fn write_byte(&self, _: u8) -> SbiRet {
+        SbiRet::success(3)
+    }
+}
+
+struct DummyFence;
+
+impl rustsbi::Fence for DummyFence {
+    fn remote_fence_i(&self, _: rustsbi::HartMask) -> SbiRet {
+        SbiRet::success(4)
+    }
+
+    fn remote_sfence_vma(&self, _: rustsbi::HartMask, _: usize, _: usize) -> SbiRet {
+        SbiRet::success(5)
+    }
+
+    fn remote_sfence_vma_asid(&self, _: rustsbi::HartMask, _: usize, _: usize, _: usize) -> SbiRet {
+        SbiRet::success(6)
+    }
+}
+
+struct DummyEnvInfo;
+
+impl rustsbi::EnvInfo for DummyEnvInfo {
+    fn mvendorid(&self) -> usize {
+        unimplemented!()
+    }
+
+    fn marchid(&self) -> usize {
+        unimplemented!()
+    }
+
+    fn mimpid(&self) -> usize {
+        unimplemented!()
+    }
+}
+
+struct RealEnvInfo;
+
+impl rustsbi::EnvInfo for RealEnvInfo {
+    fn mvendorid(&self) -> usize {
+        7
+    }
+
+    fn marchid(&self) -> usize {
+        8
+    }
+
+    fn mimpid(&self) -> usize {
+        9
+    }
+}