Skip to content

Commit b44becc

Browse files
nbdd0121ojeda
authored andcommitted
rust: macros: add #[vtable] proc macro
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<usize> { Err(EINVAL) } fn write(...) -> Result<usize> { Err(EINVAL) } } #[vtable] impl Operations for S { fn read(...) -> Result<usize> { ... } } assert_eq!(<S as Operations>::HAS_READ, true); assert_eq!(<S as Operations>::HAS_WRITE, false); Signed-off-by: Gary Guo <[email protected]> Reviewed-by: Sergio González Collado <[email protected]> [Reworded, adapted for upstream and applied latest changes] Signed-off-by: Miguel Ojeda <[email protected]>
1 parent 60f18c2 commit b44becc

File tree

3 files changed

+148
-1
lines changed

3 files changed

+148
-1
lines changed

rust/kernel/prelude.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub use core::pin::Pin;
1515

1616
pub use alloc::{boxed::Box, vec::Vec};
1717

18-
pub use macros::module;
18+
pub use macros::{module, vtable};
1919

2020
pub use super::{pr_alert, pr_crit, pr_debug, pr_emerg, pr_err, pr_info, pr_notice, pr_warn};
2121

rust/macros/lib.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
mod concat_idents;
66
mod helpers;
77
mod module;
8+
mod vtable;
89

910
use proc_macro::TokenStream;
1011

@@ -72,6 +73,57 @@ pub fn module(ts: TokenStream) -> TokenStream {
7273
module::module(ts)
7374
}
7475

76+
/// Declares or implements a vtable trait.
77+
///
78+
/// Linux's use of pure vtables is very close to Rust traits, but they differ
79+
/// in how unimplemented functions are represented. In Rust, traits can provide
80+
/// default implementation for all non-required methods (and the default
81+
/// implementation could just return `Error::EINVAL`); Linux typically use C
82+
/// `NULL` pointers to represent these functions.
83+
///
84+
/// This attribute is intended to close the gap. Traits can be declared and
85+
/// implemented with the `#[vtable]` attribute, and a `HAS_*` associated constant
86+
/// will be generated for each method in the trait, indicating if the implementor
87+
/// has overridden a method.
88+
///
89+
/// This attribute is not needed if all methods are required.
90+
///
91+
/// # Examples
92+
///
93+
/// ```ignore
94+
/// use kernel::prelude::*;
95+
///
96+
/// // Declares a `#[vtable]` trait
97+
/// #[vtable]
98+
/// pub trait Operations: Send + Sync + Sized {
99+
/// fn foo(&self) -> Result<()> {
100+
/// Err(EINVAL)
101+
/// }
102+
///
103+
/// fn bar(&self) -> Result<()> {
104+
/// Err(EINVAL)
105+
/// }
106+
/// }
107+
///
108+
/// struct Foo;
109+
///
110+
/// // Implements the `#[vtable]` trait
111+
/// #[vtable]
112+
/// impl Operations for Foo {
113+
/// fn foo(&self) -> Result<()> {
114+
/// # Err(EINVAL)
115+
/// // ...
116+
/// }
117+
/// }
118+
///
119+
/// assert_eq!(<Foo as Operations>::HAS_FOO, true);
120+
/// assert_eq!(<Foo as Operations>::HAS_BAR, false);
121+
/// ```
122+
#[proc_macro_attribute]
123+
pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream {
124+
vtable::vtable(attr, ts)
125+
}
126+
75127
/// Concatenate two identifiers.
76128
///
77129
/// This is useful in macros that need to declare or reference items with names

rust/macros/vtable.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
4+
use std::collections::HashSet;
5+
use std::fmt::Write;
6+
7+
pub(crate) fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream {
8+
let mut tokens: Vec<_> = ts.into_iter().collect();
9+
10+
// Scan for the `trait` or `impl` keyword.
11+
let is_trait = tokens
12+
.iter()
13+
.find_map(|token| match token {
14+
TokenTree::Ident(ident) => match ident.to_string().as_str() {
15+
"trait" => Some(true),
16+
"impl" => Some(false),
17+
_ => None,
18+
},
19+
_ => None,
20+
})
21+
.expect("#[vtable] attribute should only be applied to trait or impl block");
22+
23+
// Retrieve the main body. The main body should be the last token tree.
24+
let body = match tokens.pop() {
25+
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => group,
26+
_ => panic!("cannot locate main body of trait or impl block"),
27+
};
28+
29+
let mut body_it = body.stream().into_iter();
30+
let mut functions = Vec::new();
31+
let mut consts = HashSet::new();
32+
while let Some(token) = body_it.next() {
33+
match token {
34+
TokenTree::Ident(ident) if ident.to_string() == "fn" => {
35+
let fn_name = match body_it.next() {
36+
Some(TokenTree::Ident(ident)) => ident.to_string(),
37+
// Possibly we've encountered a fn pointer type instead.
38+
_ => continue,
39+
};
40+
functions.push(fn_name);
41+
}
42+
TokenTree::Ident(ident) if ident.to_string() == "const" => {
43+
let const_name = match body_it.next() {
44+
Some(TokenTree::Ident(ident)) => ident.to_string(),
45+
// Possibly we've encountered an inline const block instead.
46+
_ => continue,
47+
};
48+
consts.insert(const_name);
49+
}
50+
_ => (),
51+
}
52+
}
53+
54+
let mut const_items;
55+
if is_trait {
56+
const_items = "
57+
/// A marker to prevent implementors from forgetting to use [`#[vtable]`](vtable)
58+
/// attribute when implementing this trait.
59+
const USE_VTABLE_ATTR: ();
60+
"
61+
.to_owned();
62+
63+
for f in functions {
64+
let gen_const_name = format!("HAS_{}", f.to_uppercase());
65+
// Skip if it's declared already -- this allows user override.
66+
if consts.contains(&gen_const_name) {
67+
continue;
68+
}
69+
// We don't know on the implementation-site whether a method is required or provided
70+
// so we have to generate a const for all methods.
71+
write!(
72+
const_items,
73+
"/// Indicates if the `{f}` method is overridden by the implementor.
74+
const {gen_const_name}: bool = false;",
75+
)
76+
.unwrap();
77+
}
78+
} else {
79+
const_items = "const USE_VTABLE_ATTR: () = ();".to_owned();
80+
81+
for f in functions {
82+
let gen_const_name = format!("HAS_{}", f.to_uppercase());
83+
if consts.contains(&gen_const_name) {
84+
continue;
85+
}
86+
write!(const_items, "const {gen_const_name}: bool = true;").unwrap();
87+
}
88+
}
89+
90+
let new_body = vec![const_items.parse().unwrap(), body.stream()]
91+
.into_iter()
92+
.collect();
93+
tokens.push(TokenTree::Group(Group::new(Delimiter::Brace, new_body)));
94+
tokens.into_iter().collect()
95+
}

0 commit comments

Comments
 (0)