Browse Source

macros: assign extensions (one or multiple) to one field

Signed-off-by: Zhouqi Jiang <[email protected]>
Zhouqi Jiang 1 year ago
parent
commit
2e85d376d9
3 changed files with 290 additions and 46 deletions
  1. 72 46
      macros/src/lib.rs
  2. 64 0
      src/lib.rs
  3. 154 0
      tests/build-rename.rs

+ 72 - 46
macros/src/lib.rs

@@ -3,19 +3,19 @@ use quote::{quote, ToTokens};
 use syn::{parse_macro_input, punctuated::Punctuated, Data, DeriveInput, Fields, Generics, Ident};
 
 #[derive(Clone, Default)]
-struct RustSBIImp<'a> {
-    fence: Option<&'a Ident>,
-    hsm: Option<&'a Ident>,
-    ipi: Option<&'a Ident>,
-    reset: Option<&'a Ident>,
-    timer: Option<&'a Ident>,
-    pmu: Option<&'a Ident>,
-    console: Option<&'a Ident>,
-    susp: Option<&'a Ident>,
-    cppc: Option<&'a Ident>,
-    nacl: Option<&'a Ident>,
-    sta: Option<&'a Ident>,
-    env_info: Option<&'a Ident>,
+struct RustSBIImp {
+    fence: Option<Ident>,
+    hsm: Option<Ident>,
+    ipi: Option<Ident>,
+    reset: Option<Ident>,
+    timer: Option<Ident>,
+    pmu: Option<Ident>,
+    console: Option<Ident>,
+    susp: Option<Ident>,
+    cppc: Option<Ident>,
+    nacl: Option<Ident>,
+    sta: Option<Ident>,
+    env_info: Option<Ident>,
 }
 
 /// This macro should be used in `rustsbi` crate as `rustsbi::RustSBI`.
@@ -34,18 +34,67 @@ pub fn derive_rustsbi(input: TokenStream) -> TokenStream {
     };
     let mut imp = RustSBIImp::default();
 
+    let mut replace_sbi_extension_ident = |extension_name: &str, ident: Ident| match extension_name
+    {
+        "rfnc" | "fence" => (true, imp.fence.replace(ident)),
+        "hsm" => (true, imp.hsm.replace(ident)),
+        "spi" | "ipi" => (true, imp.ipi.replace(ident)),
+        "srst" | "reset" => (true, imp.reset.replace(ident)),
+        "time" | "timer" => (true, imp.timer.replace(ident)),
+        "pmu" => (true, imp.pmu.replace(ident)),
+        "dbcn" | "console" => (true, imp.console.replace(ident)),
+        "susp" => (true, imp.susp.replace(ident)),
+        "cppc" => (true, imp.cppc.replace(ident)),
+        "nacl" => (true, imp.nacl.replace(ident)),
+        "sta" => (true, imp.sta.replace(ident)),
+        "info" | "env_info" => (true, imp.env_info.replace(ident)),
+        _ => (false, None),
+    };
     let mut ans = TokenStream::new();
+    let check_already_exists =
+        |field: &syn::Field, extension_name: &str, origin: Option<Ident>, ans: &mut TokenStream| {
+            if let Some(_origin) = origin {
+                // TODO: provide more detailed proc macro error hinting that previous
+                // definition of this extension resides in `origin` once RFC 1566
+                // (Procedural Macro Diagnostics) is stablized.
+                // Link: https://github.com/rust-lang/rust/issues/54140
+                let error = syn::Error::new_spanned(
+                    &field,
+                    format!(
+                        "more than one field defined SBI extension '{}'. \
+                At most one fields should define the same SBI extension; consider using \
+                #[rustsbi(skip)] to ignore fields that shouldn't be treated as an extension.",
+                        extension_name
+                    ),
+                );
+                ans.extend(TokenStream::from(error.to_compile_error()));
+            }
+        };
 
     for field in &fields {
-        let mut skipped = false;
+        let mut field_already_parsed = false;
         for attr in &field.attrs {
             if !attr.path().is_ident("rustsbi") {
                 continue;
             }
             let parsed = attr.parse_nested_meta(|meta| {
+                let mut current_meta_accepted = false;
                 if meta.path.is_ident("skip") {
-                    // skip this field in RustSBI
-                    skipped = true;
+                    // accept meta but do nothing, effectively skip this field in RustSBI
+                    current_meta_accepted = true;
+                } else if let (Some(meta_path_ident), Some(field_ident)) =
+                    (meta.path.get_ident(), &field.ident)
+                {
+                    let extension_name = &meta_path_ident.to_string();
+                    let (replaced, origin) =
+                        replace_sbi_extension_ident(extension_name, field_ident.clone());
+                    if replaced {
+                        check_already_exists(field, extension_name, origin, &mut ans);
+                        current_meta_accepted = true;
+                    }
+                }
+                if current_meta_accepted {
+                    field_already_parsed = true;
                     Ok(())
                 } else {
                     let path = meta.path.to_token_stream().to_string().replace(' ', "");
@@ -56,38 +105,15 @@ pub fn derive_rustsbi(input: TokenStream) -> TokenStream {
                 ans.extend(TokenStream::from(err.to_compile_error()));
             }
         }
-        if skipped {
+        // Already parsed by inner attribute.
+        // Could be either skipped using #[rustsbi(skip)], or renamed using #[rustsbi(some_extension)]
+        if field_already_parsed {
             continue;
         }
-        if let Some(name) = &field.ident {
-            let origin = match name.to_string().as_str() {
-                "rfnc" | "fence" => imp.fence.replace(name),
-                "hsm" => imp.hsm.replace(name),
-                "spi" | "ipi" => imp.ipi.replace(name),
-                "srst" | "reset" => imp.reset.replace(name),
-                "time" | "timer" => imp.timer.replace(name),
-                "pmu" => imp.pmu.replace(name),
-                "dbcn" | "console" => imp.console.replace(name),
-                "susp" => imp.susp.replace(name),
-                "cppc" => imp.cppc.replace(name),
-                "nacl" => imp.nacl.replace(name),
-                "sta" => imp.sta.replace(name),
-                "info" | "env_info" => imp.env_info.replace(name),
-                _ => continue,
-            };
-            if let Some(_origin) = origin {
-                // TODO: provide more detailed proc macro error hinting that previous
-                // definition of this extension resides in `origin` once RFC 1566
-                // (Procedural Macro Diagnostics) is stablized.
-                // Link: https://github.com/rust-lang/rust/issues/54140
-                let error = syn::Error::new_spanned(
-                    &field,
-                    format!("more than one field defined SBI extension '{}'. \
-                    At most one fields should define the same SBI extension; consider using \
-                    #[rustsbi(skip)] to ignore fields that shouldn't be treated as an extension.", name),
-                );
-                ans.extend(TokenStream::from(error.to_compile_error()));
-            }
+        if let Some(field_ident) = &field.ident {
+            let (_replaced, origin) =
+                replace_sbi_extension_ident(field_ident.to_string().as_str(), field_ident.clone());
+            check_already_exists(field, &field_ident.to_string(), origin, &mut ans);
         }
     }
 

+ 64 - 0
src/lib.rs

@@ -897,6 +897,70 @@ pub extern crate sbi_spec as spec;
 /// # }
 /// ```
 ///
+/// In some cases we may manually assign fields to certain SBI extension other than defaulting
+/// to special names defined above, and sometimes we need to provide multiple SBI extensions
+/// with one field only. By listing the extension names separated by comma in the helper attribute,
+/// we can assign one or multiple SBI extensions to a field to solve the issues above.
+///
+/// ```rust
+/// #[derive(RustSBI)]
+/// struct MySBI {
+///     console: MyConsole,
+///     #[rustsbi(fence)]
+///     some_other_name: MyFence,
+///     info: MyEnvInfo,
+/// }
+///
+/// // RustSBI will now use the `some_other_name` field implementing `rustsbi::Fence`
+/// // as the implementation of SBI Remote Fence extension.
+/// # use rustsbi::{HartMask, 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;
+/// # impl rustsbi::Fence for MyFence {
+/// #     fn remote_fence_i(&self, _: HartMask) -> SbiRet { unimplemented!() }
+/// #     fn remote_sfence_vma(&self, _: HartMask, _: usize, _: usize) -> SbiRet { unimplemented!() }
+/// #     fn remote_sfence_vma_asid(&self, _: HartMask, _: usize, _: usize, _: usize) -> SbiRet { unimplemented!() }
+/// # }
+/// # struct MyEnvInfo;
+/// # impl rustsbi::EnvInfo for MyEnvInfo {
+/// #     fn mvendorid(&self) -> usize { 1 }
+/// #     fn marchid(&self) -> usize { 2 }
+/// #     fn mimpid(&self) -> usize { 3 }
+/// # }
+/// ```
+/// ```rust
+/// #[derive(RustSBI)]
+/// struct MySBI {
+///     #[rustsbi(ipi, timer)]
+///     clint: Clint, // <-- RISC-V CLINT will provide both Ipi and Timer extensions.
+///     info: MyEnvInfo,
+/// }
+///
+/// // Both Ipi and Timer extension implementations are now provided by the
+/// // `clint` field implementing both `rustsbi::Ipi` and `rustsbi::Timer`.
+/// # use rustsbi::{HartMask, RustSBI};
+/// # use sbi_spec::binary::{SbiRet, Physical};
+/// # struct Clint;
+/// # impl rustsbi::Timer for Clint {
+/// #     fn set_timer(&self, stime_value: u64) { unimplemented!() }
+/// # }
+/// # impl rustsbi::Ipi for Clint {
+/// #     fn send_ipi(&self, _: HartMask) -> SbiRet { unimplemented!() }
+/// # }
+/// # 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)]

+ 154 - 0
tests/build-rename.rs

@@ -0,0 +1,154 @@
+use std::cell::RefCell;
+
+use rustsbi::{HartMask, RustSBI};
+use sbi_spec::{
+    binary::{Physical, SbiRet},
+    dbcn::EID_DBCN,
+    rfnc::EID_RFNC,
+    spi::EID_SPI,
+    time::EID_TIME,
+};
+
+#[derive(RustSBI)]
+struct AssignOne {
+    console: DummyConsole,
+    #[rustsbi(fence)]
+    some_other_name: DummyFence,
+    info: DummyEnvInfo,
+}
+
+#[derive(RustSBI)]
+struct AssignMultiple {
+    #[rustsbi(ipi, timer)]
+    clint: MockClint,
+    info: DummyEnvInfo,
+}
+
+#[test]
+fn rustsbi_assign_one() {
+    let sbi = AssignOne {
+        console: DummyConsole,
+        some_other_name: DummyFence,
+        info: DummyEnvInfo,
+    };
+    // Both extension return correct values.
+    assert_eq!(sbi.handle_ecall(EID_DBCN, 0x0, [0; 6]), SbiRet::success(1));
+    assert_eq!(sbi.handle_ecall(EID_DBCN, 0x1, [0; 6]), SbiRet::success(2));
+    assert_eq!(sbi.handle_ecall(EID_DBCN, 0x2, [0; 6]), SbiRet::success(3));
+    assert_eq!(sbi.handle_ecall(EID_RFNC, 0x0, [0; 6]), SbiRet::success(4));
+    assert_eq!(sbi.handle_ecall(EID_RFNC, 0x1, [0; 6]), SbiRet::success(5));
+    assert_eq!(sbi.handle_ecall(EID_RFNC, 0x2, [0; 6]), SbiRet::success(6));
+    // Both extension exists.
+    assert_ne!(
+        sbi.handle_ecall(0x10, 0x3, [EID_DBCN, 0, 0, 0, 0, 0]),
+        SbiRet::success(0)
+    );
+    assert_ne!(
+        sbi.handle_ecall(0x10, 0x3, [EID_RFNC, 0, 0, 0, 0, 0]),
+        SbiRet::success(0)
+    );
+}
+
+#[test]
+fn rustsbi_assign_multiple() {
+    let sbi = AssignMultiple {
+        clint: MockClint {
+            time: RefCell::new(0),
+        },
+        info: DummyEnvInfo,
+    };
+    assert_eq!(sbi.handle_ecall(EID_SPI, 0x0, [0; 6]), SbiRet::success(10));
+    #[cfg(target_pointer_width = "64")]
+    sbi.handle_ecall(EID_TIME, 0x0, [0x1122334455667788, 0, 0, 0, 0, 0]);
+    #[cfg(target_pointer_width = "32")]
+    sbi.handle_ecall(EID_TIME, 0x0, [0x11223344, 0x55667788, 0, 0, 0, 0]);
+    assert_eq!(sbi.clint.time.take(), 0x1122334455667788);
+    assert_ne!(
+        sbi.handle_ecall(0x10, 0x3, [EID_SPI, 0, 0, 0, 0, 0]),
+        SbiRet::success(0)
+    );
+    assert_ne!(
+        sbi.handle_ecall(0x10, 0x3, [EID_TIME, 0, 0, 0, 0, 0]),
+        SbiRet::success(0)
+    );
+}
+
+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
+    }
+}
+
+struct MockClint {
+    time: RefCell<u64>,
+}
+
+impl rustsbi::Timer for MockClint {
+    fn set_timer(&self, stime_value: u64) {
+        self.time.replace(stime_value);
+    }
+}
+
+impl rustsbi::Ipi for MockClint {
+    fn send_ipi(&self, _: HartMask) -> SbiRet {
+        SbiRet::success(10)
+    }
+}