From d0fdc3961270617826e4794fca1d092853847707 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Sat, 8 Apr 2023 12:26:01 +0000 Subject: rust: init: add `PinnedDrop` trait and macros The `PinnedDrop` trait that facilitates destruction of pinned types. It has to be implemented via the `#[pinned_drop]` macro, since the `drop` function should not be called by normal code, only by other destructors. It also only works on structs that are annotated with `#[pin_data(PinnedDrop)]`. Co-developed-by: Gary Guo Signed-off-by: Gary Guo Signed-off-by: Benno Lossin Reviewed-by: Alice Ryhl Reviewed-by: Andreas Hindborg Link: https://lore.kernel.org/r/20230408122429.1103522-10-y86-dev@protonmail.com Signed-off-by: Miguel Ojeda --- rust/macros/lib.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++++ rust/macros/pinned_drop.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 rust/macros/pinned_drop.rs (limited to 'rust/macros') diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs index 6d84f2fd0d77..3fc74cb4ea19 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -8,6 +8,7 @@ mod concat_idents; mod helpers; mod module; mod pin_data; +mod pinned_drop; mod vtable; use proc_macro::TokenStream; @@ -180,6 +181,10 @@ pub fn concat_idents(ts: TokenStream) -> TokenStream { /// This macro enables the use of the [`pin_init!`] macro. When pin-initializing a `struct`, /// then `#[pin]` directs the type of initializer that is required. /// +/// If your `struct` implements `Drop`, then you need to add `PinnedDrop` as arguments to this +/// macro, and change your `Drop` implementation to `PinnedDrop` annotated with +/// `#[`[`macro@pinned_drop`]`]`, since dropping pinned values requires extra care. +/// /// # Examples /// /// ```rust,ignore @@ -191,9 +196,53 @@ pub fn concat_idents(ts: TokenStream) -> TokenStream { /// } /// ``` /// +/// ```rust,ignore +/// #[pin_data(PinnedDrop)] +/// struct DriverData { +/// #[pin] +/// queue: Mutex>, +/// buf: Box<[u8; 1024 * 1024]>, +/// raw_info: *mut Info, +/// } +/// +/// #[pinned_drop] +/// impl PinnedDrop for DriverData { +/// fn drop(self: Pin<&mut Self>) { +/// unsafe { bindings::destroy_info(self.raw_info) }; +/// } +/// } +/// ``` +/// /// [`pin_init!`]: ../kernel/macro.pin_init.html // ^ cannot use direct link, since `kernel` is not a dependency of `macros`. #[proc_macro_attribute] pub fn pin_data(inner: TokenStream, item: TokenStream) -> TokenStream { pin_data::pin_data(inner, item) } + +/// Used to implement `PinnedDrop` safely. +/// +/// Only works on structs that are annotated via `#[`[`macro@pin_data`]`]`. +/// +/// # Examples +/// +/// ```rust,ignore +/// #[pin_data(PinnedDrop)] +/// struct DriverData { +/// #[pin] +/// queue: Mutex>, +/// buf: Box<[u8; 1024 * 1024]>, +/// raw_info: *mut Info, +/// } +/// +/// #[pinned_drop] +/// impl PinnedDrop for DriverData { +/// fn drop(self: Pin<&mut Self>) { +/// unsafe { bindings::destroy_info(self.raw_info) }; +/// } +/// } +/// ``` +#[proc_macro_attribute] +pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream { + pinned_drop::pinned_drop(args, input) +} diff --git a/rust/macros/pinned_drop.rs b/rust/macros/pinned_drop.rs new file mode 100644 index 000000000000..88fb72b20660 --- /dev/null +++ b/rust/macros/pinned_drop.rs @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use proc_macro::{TokenStream, TokenTree}; + +pub(crate) fn pinned_drop(_args: TokenStream, input: TokenStream) -> TokenStream { + let mut toks = input.into_iter().collect::>(); + assert!(!toks.is_empty()); + // Ensure that we have an `impl` item. + assert!(matches!(&toks[0], TokenTree::Ident(i) if i.to_string() == "impl")); + // Ensure that we are implementing `PinnedDrop`. + let mut nesting: usize = 0; + let mut pinned_drop_idx = None; + for (i, tt) in toks.iter().enumerate() { + match tt { + TokenTree::Punct(p) if p.as_char() == '<' => { + nesting += 1; + } + TokenTree::Punct(p) if p.as_char() == '>' => { + nesting = nesting.checked_sub(1).unwrap(); + continue; + } + _ => {} + } + if i >= 1 && nesting == 0 { + // Found the end of the generics, this should be `PinnedDrop`. + assert!( + matches!(tt, TokenTree::Ident(i) if i.to_string() == "PinnedDrop"), + "expected 'PinnedDrop', found: '{:?}'", + tt + ); + pinned_drop_idx = Some(i); + break; + } + } + let idx = pinned_drop_idx + .unwrap_or_else(|| panic!("Expected an `impl` block implementing `PinnedDrop`.")); + // Fully qualify the `PinnedDrop`, as to avoid any tampering. + toks.splice(idx..idx, quote!(::kernel::init::)); + // Take the `{}` body and call the declarative macro. + if let Some(TokenTree::Group(last)) = toks.pop() { + let last = last.stream(); + quote!(::kernel::__pinned_drop! { + @impl_sig(#(#toks)*), + @impl_body(#last), + }) + } else { + TokenStream::from_iter(toks) + } +} -- cgit v1.2.3