From patchwork Fri Dec 2 16:14:37 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Miguel Ojeda X-Patchwork-Id: 28967 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:adf:f944:0:0:0:0:0 with SMTP id q4csp947743wrr; Fri, 2 Dec 2022 08:18:25 -0800 (PST) X-Google-Smtp-Source: AA0mqf4oMoDx74HwgALyMceSQ1RGRIYQ6ymDL8xKrvHzed6Kq65CZuqCPlULlLWmCkiqrqXRir7Z X-Received: by 2002:a17:903:2407:b0:17f:3da:f19d with SMTP id e7-20020a170903240700b0017f03daf19dmr55920288plo.147.1669997905177; Fri, 02 Dec 2022 08:18:25 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1669997905; cv=none; d=google.com; s=arc-20160816; b=QA/gfyK4m8HW/c2PHpZfVd+x0atJiLps70JCthKEeTaE1LZr5Rw6LawTqiuvU81YN2 rdWnprVdzSMJ/wPttT9Tvj2uEXPiFTSj96a+q+0MPT6nV5z7G5GiJZBTXgdCUGK70AaW /wD82Y2G4OR2mqDNCvYQQiBm8KQEAWOWZNN+42z4U5U9tkFPFp5gE5NGFx16m9zCVoYb NymetMMf3pFQ7fU5SxkK8CFeRCW8ssotUTu8U400iYMdGer0h1shUcz9j6Hb1117g85H vqz6zxxbkf3I3cOA+QJ3J+KkNPBJ0obopGJtE5PXrnELSGedx3Vbj7y9di+1Hp8/VLPa 42ww== 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 :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=RjLGWeRPJ8hgc0HRIV2im4kbJ60Iid6vvIeYEmsYPAU=; b=pGh5AcQaDd+2d6m5hCWqMwiLB6pV1n4OgvgtjzY9+5+MMkywZ3uGdzhY74ifrGl+gJ c7fkegdggGXRT6ZCPGySu0FibhPh0+I2fnT0PoAUsSdiMa86e12btPTAf0V47Wt6zuaI lXvpufiIikHd4XFo6A9YogvLJT7D165LZeq6spnE9nEp37ScgCOdHBQBegmede0U/Kkn q1vZr6Ao8sJVWsnG3MNkbD8YOs1papurr8sVN2ws1RQSniN2raFZnYlWLyXGNzxKdcW8 1so6Dj09uWP7TgTiffUH03Y0G4chvZesTkekR39nzkR49KX2ioFjLLHxxKn2L6BtVxko n8FA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@kernel.org header.s=k20201202 header.b=EZbLtNCw; 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=NONE sp=NONE dis=NONE) header.from=kernel.org Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id x18-20020a17090300d200b0018983e6c429si6670928plc.3.2022.12.02.08.18.09; Fri, 02 Dec 2022 08:18:25 -0800 (PST) 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=@kernel.org header.s=k20201202 header.b=EZbLtNCw; 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=NONE sp=NONE dis=NONE) header.from=kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233960AbiLBQQ3 (ORCPT + 99 others); Fri, 2 Dec 2022 11:16:29 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53130 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233840AbiLBQQB (ORCPT ); Fri, 2 Dec 2022 11:16:01 -0500 Received: from dfw.source.kernel.org (dfw.source.kernel.org [139.178.84.217]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 9B4CFD6796; Fri, 2 Dec 2022 08:15:47 -0800 (PST) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dfw.source.kernel.org (Postfix) with ESMTPS id 247A462322; Fri, 2 Dec 2022 16:15:47 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 2A9DFC433D7; Fri, 2 Dec 2022 16:15:43 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1669997746; bh=N9xIU8sHH23NdLn+DWGLMxxQ8DUCPXG+6JF3lVqrUEQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=EZbLtNCwmhU8xS2so2fhn2yZrtw7LGAKhWwFwbhyGNuK2aqoZ1goBxC0ZLststZ8g wVOtwdwpFLaoppmzJW+U0VpyNfVLK9K/YuY4qO9pREd6xP8p41yaCE3ntnQ6oVVIyr PK/VXlzRvAnyexkoD1Sf6GZprtRWZgwRs/GaiuNRxyyr8WoI2Wqq226GfazZirKivK t9I0aCysJhq862FL544IS4n4d1CBfhU8LoNzDgPnXObzZC41iIPvUyPuo4Yk15ZhAy YUmloqTzIeoOzFfXyUXScNRRMeSZIe6zKtLd3y+Nj8y4xFsjcblX+u6nrRUWYc5T72 Y2gMYyw/K1d3A== From: ojeda@kernel.org To: Miguel Ojeda , Wedson Almeida Filho , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, patches@lists.linux.dev, =?utf-8?q?Sergio_Gonz=C3=A1lez_Collado?= Subject: [PATCH v2 06/28] rust: macros: add `#[vtable]` proc macro Date: Fri, 2 Dec 2022 17:14:37 +0100 Message-Id: <20221202161502.385525-7-ojeda@kernel.org> In-Reply-To: <20221202161502.385525-1-ojeda@kernel.org> References: <20221202161502.385525-1-ojeda@kernel.org> MIME-Version: 1.0 X-Spam-Status: No, score=-7.1 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_HI, SPF_HELO_NONE,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: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1751119723444305414?= X-GMAIL-MSGID: =?utf-8?q?1751119723444305414?= From: Gary Guo This procedural macro attribute provides a simple way to declare a trait with a set of operations that later users can partially implement, providing compile-time `HAS_*` boolean associated constants that indicate whether a particular operation was overridden. This is useful as the Rust counterpart to structs like `file_operations` where some pointers may be `NULL`, indicating an operation is not provided. For instance: #[vtable] trait Operations { fn read(...) -> Result { Err(EINVAL) } fn write(...) -> Result { Err(EINVAL) } } #[vtable] impl Operations for S { fn read(...) -> Result { ... } } assert_eq!(::HAS_READ, true); assert_eq!(::HAS_WRITE, false); Signed-off-by: Gary Guo Reviewed-by: Sergio González Collado [Reworded, adapted for upstream and applied latest changes] Signed-off-by: Miguel Ojeda Reviewed-by: Finn Behrens --- rust/kernel/prelude.rs | 2 +- rust/macros/lib.rs | 52 +++++++++++++++++++++++ rust/macros/vtable.rs | 95 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 rust/macros/vtable.rs diff --git a/rust/kernel/prelude.rs b/rust/kernel/prelude.rs index 6a1c6b38327f..7c4c35bf3c66 100644 --- a/rust/kernel/prelude.rs +++ b/rust/kernel/prelude.rs @@ -15,7 +15,7 @@ pub use core::pin::Pin; pub use alloc::{boxed::Box, vec::Vec}; -pub use macros::module; +pub use macros::{module, vtable}; pub use super::{pr_alert, pr_crit, pr_debug, pr_emerg, pr_err, pr_info, pr_notice, pr_warn}; diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs index 15555e7ff487..e40caaf0a656 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -5,6 +5,7 @@ mod concat_idents; mod helpers; mod module; +mod vtable; use proc_macro::TokenStream; @@ -72,6 +73,57 @@ pub fn module(ts: TokenStream) -> TokenStream { module::module(ts) } +/// Declares or implements a vtable trait. +/// +/// Linux's use of pure vtables is very close to Rust traits, but they differ +/// in how unimplemented functions are represented. In Rust, traits can provide +/// default implementation for all non-required methods (and the default +/// implementation could just return `Error::EINVAL`); Linux typically use C +/// `NULL` pointers to represent these functions. +/// +/// This attribute is intended to close the gap. Traits can be declared and +/// implemented with the `#[vtable]` attribute, and a `HAS_*` associated constant +/// will be generated for each method in the trait, indicating if the implementor +/// has overridden a method. +/// +/// This attribute is not needed if all methods are required. +/// +/// # Examples +/// +/// ```ignore +/// use kernel::prelude::*; +/// +/// // Declares a `#[vtable]` trait +/// #[vtable] +/// pub trait Operations: Send + Sync + Sized { +/// fn foo(&self) -> Result<()> { +/// Err(EINVAL) +/// } +/// +/// fn bar(&self) -> Result<()> { +/// Err(EINVAL) +/// } +/// } +/// +/// struct Foo; +/// +/// // Implements the `#[vtable]` trait +/// #[vtable] +/// impl Operations for Foo { +/// fn foo(&self) -> Result<()> { +/// # Err(EINVAL) +/// // ... +/// } +/// } +/// +/// assert_eq!(::HAS_FOO, true); +/// assert_eq!(::HAS_BAR, false); +/// ``` +#[proc_macro_attribute] +pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream { + vtable::vtable(attr, ts) +} + /// Concatenate two identifiers. /// /// This is useful in macros that need to declare or reference items with names diff --git a/rust/macros/vtable.rs b/rust/macros/vtable.rs new file mode 100644 index 000000000000..34d5e7fb5768 --- /dev/null +++ b/rust/macros/vtable.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0 + +use proc_macro::{Delimiter, Group, TokenStream, TokenTree}; +use std::collections::HashSet; +use std::fmt::Write; + +pub(crate) fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream { + let mut tokens: Vec<_> = ts.into_iter().collect(); + + // Scan for the `trait` or `impl` keyword. + let is_trait = tokens + .iter() + .find_map(|token| match token { + TokenTree::Ident(ident) => match ident.to_string().as_str() { + "trait" => Some(true), + "impl" => Some(false), + _ => None, + }, + _ => None, + }) + .expect("#[vtable] attribute should only be applied to trait or impl block"); + + // Retrieve the main body. The main body should be the last token tree. + let body = match tokens.pop() { + Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => group, + _ => panic!("cannot locate main body of trait or impl block"), + }; + + let mut body_it = body.stream().into_iter(); + let mut functions = Vec::new(); + let mut consts = HashSet::new(); + while let Some(token) = body_it.next() { + match token { + TokenTree::Ident(ident) if ident.to_string() == "fn" => { + let fn_name = match body_it.next() { + Some(TokenTree::Ident(ident)) => ident.to_string(), + // Possibly we've encountered a fn pointer type instead. + _ => continue, + }; + functions.push(fn_name); + } + TokenTree::Ident(ident) if ident.to_string() == "const" => { + let const_name = match body_it.next() { + Some(TokenTree::Ident(ident)) => ident.to_string(), + // Possibly we've encountered an inline const block instead. + _ => continue, + }; + consts.insert(const_name); + } + _ => (), + } + } + + let mut const_items; + if is_trait { + const_items = " + /// A marker to prevent implementors from forgetting to use [`#[vtable]`](vtable) + /// attribute when implementing this trait. + const USE_VTABLE_ATTR: (); + " + .to_owned(); + + for f in functions { + let gen_const_name = format!("HAS_{}", f.to_uppercase()); + // Skip if it's declared already -- this allows user override. + if consts.contains(&gen_const_name) { + continue; + } + // We don't know on the implementation-site whether a method is required or provided + // so we have to generate a const for all methods. + write!( + const_items, + "/// Indicates if the `{f}` method is overridden by the implementor. + const {gen_const_name}: bool = false;", + ) + .unwrap(); + } + } else { + const_items = "const USE_VTABLE_ATTR: () = ();".to_owned(); + + for f in functions { + let gen_const_name = format!("HAS_{}", f.to_uppercase()); + if consts.contains(&gen_const_name) { + continue; + } + write!(const_items, "const {gen_const_name}: bool = true;").unwrap(); + } + } + + let new_body = vec![const_items.parse().unwrap(), body.stream()] + .into_iter() + .collect(); + tokens.push(TokenTree::Group(Group::new(Delimiter::Brace, new_body))); + tokens.into_iter().collect() +}