From patchwork Mon Aug 14 08:46:41 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Benno Lossin X-Patchwork-Id: 135287 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:b824:0:b0:3f2:4152:657d with SMTP id z4csp2629311vqi; Mon, 14 Aug 2023 02:46:05 -0700 (PDT) X-Google-Smtp-Source: AGHT+IFjVBwCMFr3oh9uegyef+agtHnjXGP056l6sdTnZJr8H9o7QzcI3LyHPmetjI+sAQTlJONY X-Received: by 2002:aa7:c30f:0:b0:523:6c47:56f8 with SMTP id l15-20020aa7c30f000000b005236c4756f8mr7107294edq.18.1692006365258; Mon, 14 Aug 2023 02:46:05 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1692006365; cv=none; d=google.com; s=arc-20160816; b=K+uPe4xUl9wTGGzTBJgm3VeiYgnofZ/gjNacLouGQDVBej9ZWXrtsQqbKbAJlqRIRe 5PM9Ijczv9B5c9CDrZ1pc20B162DLIRhKOLS4fxkXHv1YByeUg6yFpWSRuUi7GjvU95n /KRSrxva0wZVUMokXHxT5BNdTuTWupfkX5lax0Td3BRjfXAKw0x5ZFrFAZ4gAmUNQ4L4 1FVRjSYBbgwQBO4lKabHoFZ3fM5GZYOOLdYZt92TPBMPy91ZmAp7bGlSdT175Umf9OkN BGch7Z8KTzC3sjaYEnTbGwLX5IFW7RJM9y8bP9naTP0gHTwxKqJ8OhexntiV/iO4shTB ft0Q== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :feedback-id:references:in-reply-to:message-id:subject:cc:from:to :dkim-signature:date; bh=bMYBy/yxUzYNLePNMMjA4ZSmmlU+epSTw1cc5ylrenA=; fh=9qAu1mhYFxYeaElldShNhIt7ezwLosaXPSwpT1P2JOg=; b=xJgnNT6XWD2KrKfKAjt9pjLdBNRq6/GvhcKTKB5SRXWitfeBjJpezuopd9vNNth6ba GkyC4ErBaA3yt5uggQTkH/wR7GqdFTVitnbl8D7382xydpaM1lNylUvK8rsGTwRovkXS HsoBZ7YQq5v/j0VNqvMCMgKyjIpOdFipq/cXgadmfQhAzUPMY47k8YPV3487fR/1Amtr rRu4moLFV9qYnJ4YIFsY+/NhfsUTnlnIr6Cyh3qy2g2C5jKfRyA4aRnx3RdXEDpKlSNC qj9rbkmLY78CZHZIy+LsPiJR62fNz9GlStIt4AHOcLY8Fm0kW6lNm/D5zXqz0aiuI/j4 y6LA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@proton.me header.s=rayheo6bwfb25jkb4mklfbwy2m.protonmail header.b=MzE73qw+; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=proton.me Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id v14-20020aa7d80e000000b005234f302c99si7364488edq.499.2023.08.14.02.45.42; Mon, 14 Aug 2023 02:46:05 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@proton.me header.s=rayheo6bwfb25jkb4mklfbwy2m.protonmail header.b=MzE73qw+; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=proton.me Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S234950AbjHNIrQ (ORCPT + 99 others); Mon, 14 Aug 2023 04:47:16 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:41186 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234997AbjHNIqs (ORCPT ); Mon, 14 Aug 2023 04:46:48 -0400 Received: from mail-4316.protonmail.ch (mail-4316.protonmail.ch [185.70.43.16]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 9E36510C1 for ; Mon, 14 Aug 2023 01:46:46 -0700 (PDT) Date: Mon, 14 Aug 2023 08:46:41 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=proton.me; s=rayheo6bwfb25jkb4mklfbwy2m.protonmail; t=1692002804; x=1692262004; bh=bMYBy/yxUzYNLePNMMjA4ZSmmlU+epSTw1cc5ylrenA=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=MzE73qw+hMEoBUfH8OMzukx5yOplVWu6U1j0jSdZwHWnYoYdMjonRcqbiPMbfyYGZ zDW0DsdwwGuzMoZNDdrhjAZrQmhGZdX3TuakGyGiPA941Fp3z5hxbwL8q750+NgZUb kOHVd+VGE9BYn7FRXYQN2GIhihuzHiNRX9vQoWp3uipYnecmX473MkCOcw/nvEKDoj +Y5jRB5IWEOYnBAEAR7jHIJyXRVrBFO4BDK23G2fz1GhG/WgvW0You56RSMffk4FRu +OD4OajI5bv+phpUTRIA2/rYNtTzgyb8PPrm7/M3kOkcuP/FnjlfwPiT5/2meTHQd7 ewTgdvID3CYeA== To: Miguel Ojeda , Wedson Almeida Filho , Alex Gaynor From: Benno Lossin Cc: Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Alice Ryhl , Andreas Hindborg , rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, patches@lists.linux.dev, Asahi Lina Subject: [PATCH v4 03/13] rust: add derive macro for `Zeroable` Message-ID: <20230814084602.25699-4-benno.lossin@proton.me> In-Reply-To: <20230814084602.25699-1-benno.lossin@proton.me> References: <20230814084602.25699-1-benno.lossin@proton.me> Feedback-ID: 71780778:user:proton MIME-Version: 1.0 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_MSPIKE_H5, RCVD_IN_MSPIKE_WL,SPF_HELO_PASS,SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: INBOX X-GMAIL-THRID: 1774197266563031740 X-GMAIL-MSGID: 1774197266563031740 Add a derive proc-macro for the `Zeroable` trait. The macro supports structs where every field implements the `Zeroable` trait. This way `unsafe` implementations can be avoided. The macro is split into two parts: - a proc-macro to parse generics into impl and ty generics, - a declarative macro that expands to the impl block. Suggested-by: Asahi Lina Signed-off-by: Benno Lossin --- v3 -> v4: - add support for `+` in `quote!`. v2 -> v3: - change derive behavior, instead of adding `Zeroable` bounds for every field, add them only for generic type parameters, - still check that every field implements `Zeroable`, - removed Reviewed-by's due to changes. v1 -> v2: - fix Zeroable path, - add Reviewed-by from Gary and Björn. rust/kernel/init/macros.rs | 35 ++++++++++++++++++ rust/kernel/prelude.rs | 2 +- rust/macros/lib.rs | 20 +++++++++++ rust/macros/quote.rs | 12 +++++++ rust/macros/zeroable.rs | 72 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 rust/macros/zeroable.rs diff --git a/rust/kernel/init/macros.rs b/rust/kernel/init/macros.rs index 9182fdf99e7e..78091756dec0 100644 --- a/rust/kernel/init/macros.rs +++ b/rust/kernel/init/macros.rs @@ -1215,3 +1215,38 @@ macro_rules! __init_internal { ); }; } + +#[doc(hidden)] +#[macro_export] +macro_rules! __derive_zeroable { + (parse_input: + @sig( + $(#[$($struct_attr:tt)*])* + $vis:vis struct $name:ident + $(where $($whr:tt)*)? + ), + @impl_generics($($impl_generics:tt)*), + @ty_generics($($ty_generics:tt)*), + @body({ + $( + $(#[$($field_attr:tt)*])* + $field:ident : $field_ty:ty + ),* $(,)? + }), + ) => { + // SAFETY: every field type implements `Zeroable` and padding bytes may be zero. + #[automatically_derived] + unsafe impl<$($impl_generics)*> $crate::init::Zeroable for $name<$($ty_generics)*> + where + $($($whr)*)? + {} + const _: () = { + fn assert_zeroable() {} + fn ensure_zeroable<$($impl_generics)*>() + where $($($whr)*)? + { + $(assert_zeroable::<$field_ty>();)* + } + }; + }; +} diff --git a/rust/kernel/prelude.rs b/rust/kernel/prelude.rs index c28587d68ebc..ae21600970b3 100644 --- a/rust/kernel/prelude.rs +++ b/rust/kernel/prelude.rs @@ -18,7 +18,7 @@ pub use alloc::{boxed::Box, vec::Vec}; #[doc(no_inline)] -pub use macros::{module, pin_data, pinned_drop, vtable}; +pub use macros::{module, pin_data, pinned_drop, vtable, Zeroable}; pub use super::build_assert; diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs index b4bc44c27bd4..fd7a815e68a8 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -11,6 +11,7 @@ mod pin_data; mod pinned_drop; mod vtable; +mod zeroable; use proc_macro::TokenStream; @@ -343,3 +344,22 @@ pub fn paste(input: TokenStream) -> TokenStream { paste::expand(&mut tokens); tokens.into_iter().collect() } + +/// Derives the [`Zeroable`] trait for the given struct. +/// +/// This can only be used for structs where every field implements the [`Zeroable`] trait. +/// +/// # Examples +/// +/// ```rust +/// #[derive(Zeroable)] +/// pub struct DriverData { +/// id: i64, +/// buf_ptr: *mut u8, +/// len: usize, +/// } +/// ``` +#[proc_macro_derive(Zeroable)] +pub fn derive_zeroable(input: TokenStream) -> TokenStream { + zeroable::derive(input) +} diff --git a/rust/macros/quote.rs b/rust/macros/quote.rs index dddbb4e6f4cb..33a199e4f176 100644 --- a/rust/macros/quote.rs +++ b/rust/macros/quote.rs @@ -124,6 +124,18 @@ macro_rules! quote_spanned { )); quote_spanned!(@proc $v $span $($tt)*); }; + (@proc $v:ident $span:ident ; $($tt:tt)*) => { + $v.push(::proc_macro::TokenTree::Punct( + ::proc_macro::Punct::new(';', ::proc_macro::Spacing::Alone) + )); + quote_spanned!(@proc $v $span $($tt)*); + }; + (@proc $v:ident $span:ident + $($tt:tt)*) => { + $v.push(::proc_macro::TokenTree::Punct( + ::proc_macro::Punct::new('+', ::proc_macro::Spacing::Alone) + )); + quote_spanned!(@proc $v $span $($tt)*); + }; (@proc $v:ident $span:ident $id:ident $($tt:tt)*) => { $v.push(::proc_macro::TokenTree::Ident(::proc_macro::Ident::new(stringify!($id), $span))); quote_spanned!(@proc $v $span $($tt)*); diff --git a/rust/macros/zeroable.rs b/rust/macros/zeroable.rs new file mode 100644 index 000000000000..0d605c46ab3b --- /dev/null +++ b/rust/macros/zeroable.rs @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0 + +use crate::helpers::{parse_generics, Generics}; +use proc_macro::{TokenStream, TokenTree}; + +pub(crate) fn derive(input: TokenStream) -> TokenStream { + let ( + Generics { + impl_generics, + ty_generics, + }, + mut rest, + ) = parse_generics(input); + // This should be the body of the struct `{...}`. + let last = rest.pop(); + // Now we insert `Zeroable` as a bound for every generic parameter in `impl_generics`. + let mut new_impl_generics = Vec::with_capacity(impl_generics.len()); + // Are we inside of a generic where we want to add `Zeroable`? + let mut in_generic = !impl_generics.is_empty(); + // Have we already inserted `Zeroable`? + let mut inserted = false; + // Level of `<>` nestings. + let mut nested = 0; + for tt in impl_generics { + match &tt { + // If we find a `,`, then we have finished a generic/constant/lifetime parameter. + TokenTree::Punct(p) if nested == 0 && p.as_char() == ',' => { + if in_generic && !inserted { + new_impl_generics.extend(quote! { : ::kernel::init::Zeroable }); + } + in_generic = true; + inserted = false; + new_impl_generics.push(tt); + } + // If we find `'`, then we are entering a lifetime. + TokenTree::Punct(p) if nested == 0 && p.as_char() == '\'' => { + in_generic = false; + new_impl_generics.push(tt); + } + TokenTree::Punct(p) if nested == 0 && p.as_char() == ':' => { + new_impl_generics.push(tt); + if in_generic { + new_impl_generics.extend(quote! { ::kernel::init::Zeroable + }); + inserted = true; + } + } + TokenTree::Punct(p) if p.as_char() == '<' => { + nested += 1; + new_impl_generics.push(tt); + } + TokenTree::Punct(p) if p.as_char() == '>' => { + assert!(nested > 0); + nested -= 1; + new_impl_generics.push(tt); + } + _ => new_impl_generics.push(tt), + } + } + assert_eq!(nested, 0); + if in_generic && !inserted { + new_impl_generics.extend(quote! { : ::kernel::init::Zeroable }); + } + quote! { + ::kernel::__derive_zeroable!( + parse_input: + @sig(#(#rest)*), + @impl_generics(#(#new_impl_generics)*), + @ty_generics(#(#ty_generics)*), + @body(#last), + ); + } +}