From patchwork Thu Nov 10 16:41:18 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Miguel Ojeda X-Patchwork-Id: 18206 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:6687:0:0:0:0:0 with SMTP id l7csp251421wru; Thu, 10 Nov 2022 08:43:52 -0800 (PST) X-Google-Smtp-Source: AMsMyM4PWHox4H0J8Ex47wkHheiK26/DBHRgrEd3pzUhdG6LYcSfD0yXz2cVK6f9sdqIR4T1ekPW X-Received: by 2002:aa7:ca54:0:b0:461:ec03:3c04 with SMTP id j20-20020aa7ca54000000b00461ec033c04mr2650072edt.24.1668098632318; Thu, 10 Nov 2022 08:43:52 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1668098632; cv=none; d=google.com; s=arc-20160816; b=uWSPHN/f46jVlUvjdwhjGHriQW7bplSv5C0awDZmJPr1684eSz1QOTr+2EtaPlVTSz uF1Trq2CF43ESTt+rP3oq9JkWRx6rHhV0hobS4/nQZFDVkO91OF0QqxU2oNclg3BhaWi kjcxK7DN1/xTTHxby962EzhEzcQGH9fhpXc+AbosuuNYDBxDtI/39wcOLy/G8oX46aLS vVPN+m9IlMamMWf4a+euAWpaDAvMef6PX32/6xgItouVb9lRYAe0Eg4GNMqElO7ZTfja HTMfCeRFacKBe+on7PLLI1rm43p6Yg6QKX5aQ0uiKTeDNjTbcPO4FfJEcyhcMJHw6D9W lGJQ== 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=v7eV+S8ivYWXkYq9+haQ1gOFr3R0P0LwRqUoYegbXzs=; b=DZ2IZUFjSecizaXFnjOPc7jQxVzCYRFGhcLJd8szW4c0ZMgfpLICWNkNWjHwb3UXLF rm1w2QO9ME7DZNoLyckwPAgb0KS9QzLfmWShyRlsYXAr9AZ3VbJ/Dg3gQmE9JGr/aSFo 2wdIeZsn+CkyN0HPIi9c6tg+fyndjVXHUULsXm04ItnswNP5ejkSYbMSE1WsfM5Mliwh iLCz7ZDJBlteCmpObImvKDu0vb4+gvh4LejTu16aD5iO40vRcRS4FxR2IFitEKfUXsZO OGYbg6xBAKrfx6yFONt1E9WAzt5odPyXPNBZbaFWEDwNeKDgGovrW3aZZna02YB2jqqS gn6g== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@kernel.org header.s=k20201202 header.b=j0ZcbNCS; 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 dt3-20020a170907728300b0078db371355esi18422385ejc.987.2022.11.10.08.43.28; Thu, 10 Nov 2022 08:43:52 -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=j0ZcbNCS; 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 S232176AbiKJQnA (ORCPT + 99 others); Thu, 10 Nov 2022 11:43:00 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:40058 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232099AbiKJQmj (ORCPT ); Thu, 10 Nov 2022 11:42:39 -0500 Received: from dfw.source.kernel.org (dfw.source.kernel.org [139.178.84.217]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 526B543AF2; Thu, 10 Nov 2022 08:42:37 -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 E2A1761BCB; Thu, 10 Nov 2022 16:42:36 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 439A1C4347C; Thu, 10 Nov 2022 16:42:34 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1668098556; bh=j8Vv0JsY401mmuX1GnDq9A3c9gx2HGMWyl7yi4WgHN4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=j0ZcbNCStzx5mABTaSVrD/sXylGjxiWKUyEOG+4ul+Y0x7EzTVYg88N4cypw4uFVu JL8qfoUkpdr812dgFNyPWBSgst072b39TTPrbXhL4zbPjyWeJ2wVUQvWX81dyLlE2I w71o14k2k/Amh1+5mdp5WkJXSinvwhU45ZIvNBA3gopq5UrzRVmQrR8EADO/x/4Ysn rzEQoSqNGGGecSnrI7GKvoRzFRFeoC3Q3Js45UD5w4R1uedOZAERSW1FO1yOTJaytF RrV/Z6jD0vX6+9hUcw/gjm8vEd/GZrrrecTPwd9bioiowa9dlfRLiHtdrVY2XWiB4C 1zQjN1L8zlHXg== From: Miguel Ojeda 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 Subject: [PATCH v1 06/28] rust: macros: add `#[vtable]` proc macro Date: Thu, 10 Nov 2022 17:41:18 +0100 Message-Id: <20221110164152.26136-7-ojeda@kernel.org> In-Reply-To: <20221110164152.26136-1-ojeda@kernel.org> References: <20221110164152.26136-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?1749128191763978281?= X-GMAIL-MSGID: =?utf-8?q?1749128191763978281?= 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 [Reworded, adapted for upstream and applied latest changes] Signed-off-by: Miguel Ojeda Reviewed-by: Sergio González Collado --- 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() +}