Skip to content

Commit 5f7099e

Browse files
committed
rust: implement a #[vtable] macro
Use a single `#[vtable]` macro to replace the current boilerplating of `ToUse`, `USE_NONE`, `declare_file_operations` required for declaring and implementing traits that maps to Linux's pure vtables and contains optional methods. Signed-off-by: Gary Guo <[email protected]>
1 parent 07278d5 commit 5f7099e

File tree

3 files changed

+143
-1
lines changed

3 files changed

+143
-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, string::String, vec::Vec};
1717

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

2020
pub use super::build_assert;
2121

rust/macros/lib.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
55
mod helpers;
66
mod module;
7+
mod vtable;
78

89
use proc_macro::TokenStream;
910

@@ -92,3 +93,54 @@ use proc_macro::TokenStream;
9293
pub fn module(ts: TokenStream) -> TokenStream {
9394
module::module(ts)
9495
}
96+
97+
/// Declares or implements a vtable trait.
98+
///
99+
/// Linux's use of pure vtables is very close to Rust traits, but they differ
100+
/// in how unimplemented functions are represented. In Rust, traits can provide
101+
/// default implementation for all non-required methods (and the default
102+
/// implementation could just return `Error::EINVAL`); Linux typically use C
103+
/// `NULL` pointers to represent these functions.
104+
///
105+
/// This attribute is intended to close the gap. Traits can be declared and
106+
/// implemented with the `#[vtable]` attribute, and a `HAS_*` associated constant
107+
/// will be generated for each method in the trait, indicating if the implementor
108+
/// has overriden a method.
109+
///
110+
/// This attribute is not needed if all methods are required.
111+
///
112+
/// # Examples
113+
///
114+
/// ```ignore
115+
/// use kernel::prelude::*;
116+
///
117+
/// // Declares a `#[vtable]` trait
118+
/// #[vtable]
119+
/// pub trait Operations: Send + Sync + Sized {
120+
/// fn foo(&self) -> Result<()> {
121+
/// Err(EINVAL)
122+
/// }
123+
///
124+
/// fn bar(&self) -> Result<()> {
125+
/// Err(EINVAL)
126+
/// }
127+
/// }
128+
///
129+
/// struct Foo;
130+
///
131+
/// // Implements the `#[vtable]` trait
132+
/// #[vtable]
133+
/// impl Operations for Foo {
134+
/// fn foo(&self) -> Result<()> {
135+
/// # Err(EINVAL)
136+
/// /* ... */
137+
/// }
138+
/// }
139+
///
140+
/// assert_eq!(<Foo as Operations>::HAS_FOO, true);
141+
/// assert_eq!(<Foo as Operations>::HAS_BAR, false);
142+
/// ```
143+
#[proc_macro_attribute]
144+
pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream {
145+
vtable::vtable(attr, ts)
146+
}

rust/macros/vtable.rs

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

0 commit comments

Comments
 (0)