Skip to content

Commit 3fe2024

Browse files
asahilinaherrnst
authored andcommitted
rust: macros: Add versions macro
Signed-off-by: Asahi Lina <[email protected]>
1 parent 8011f2a commit 3fe2024

File tree

2 files changed

+348
-0
lines changed

2 files changed

+348
-0
lines changed

rust/macros/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod helpers;
99
mod module;
1010
mod pin_data;
1111
mod pinned_drop;
12+
mod versions;
1213
mod vtable;
1314
mod zeroable;
1415

@@ -78,6 +79,12 @@ pub fn module(ts: TokenStream) -> TokenStream {
7879
module::module(ts)
7980
}
8081

82+
/// Declares multiple variants of a structure or impl code
83+
#[proc_macro_attribute]
84+
pub fn versions(attr: TokenStream, item: TokenStream) -> TokenStream {
85+
versions::versions(attr, item)
86+
}
87+
8188
/// Declares or implements a vtable trait.
8289
///
8390
/// Linux's use of pure vtables is very close to Rust traits, but they differ

rust/macros/versions.rs

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
2+
3+
//use crate::helpers::expect_punct;
4+
5+
fn expect_group(it: &mut impl Iterator<Item = TokenTree>) -> Group {
6+
if let Some(TokenTree::Group(group)) = it.next() {
7+
group
8+
} else {
9+
panic!("Expected Group")
10+
}
11+
}
12+
13+
fn expect_punct(it: &mut impl Iterator<Item = TokenTree>) -> String {
14+
if let Some(TokenTree::Punct(punct)) = it.next() {
15+
punct.to_string()
16+
} else {
17+
panic!("Expected Group")
18+
}
19+
}
20+
21+
fn drop_until_punct(it: &mut impl Iterator<Item = TokenTree>, delimiter: &str, is_struct: bool) {
22+
let mut depth: isize = 0;
23+
let mut colons: isize = 0;
24+
for token in it.by_ref() {
25+
if let TokenTree::Punct(punct) = token {
26+
match punct.as_char() {
27+
':' => {
28+
colons += 1;
29+
}
30+
'<' => {
31+
if depth > 0 || colons == 2 || is_struct {
32+
depth += 1;
33+
}
34+
colons = 0;
35+
}
36+
'>' => {
37+
if depth > 0 {
38+
depth -= 1;
39+
}
40+
colons = 0;
41+
}
42+
_ => {
43+
colons = 0;
44+
if depth == 0 && delimiter.contains(&punct.to_string()) {
45+
break;
46+
}
47+
}
48+
}
49+
}
50+
}
51+
}
52+
53+
fn drop_until_braces(it: &mut impl Iterator<Item = TokenTree>) {
54+
let mut depth: isize = 0;
55+
let mut colons: isize = 0;
56+
for token in it.by_ref() {
57+
match token {
58+
TokenTree::Punct(punct) => match punct.as_char() {
59+
':' => {
60+
colons += 1;
61+
}
62+
'<' => {
63+
if depth > 0 || colons == 2 {
64+
depth += 1;
65+
}
66+
colons = 0;
67+
}
68+
'>' => {
69+
if depth > 0 {
70+
depth -= 1;
71+
}
72+
colons = 0;
73+
}
74+
_ => colons = 0,
75+
},
76+
TokenTree::Group(group) if group.delimiter() == Delimiter::Brace => {
77+
if depth == 0 {
78+
break;
79+
}
80+
}
81+
_ => (),
82+
}
83+
}
84+
}
85+
86+
struct VersionConfig {
87+
fields: &'static [&'static str],
88+
enums: &'static [&'static [&'static str]],
89+
versions: &'static [&'static [&'static str]],
90+
}
91+
92+
static AGX_VERSIONS: VersionConfig = VersionConfig {
93+
fields: &["G", "V"],
94+
enums: &[
95+
&["G13", "G14", "G14X"],
96+
&["V12_3", "V12_4", "V13_0B4", "V13_2", "V13_3"],
97+
],
98+
versions: &[
99+
&["G13", "V12_3"],
100+
&["G14", "V12_4"],
101+
&["G13", "V13_3"],
102+
&["G14", "V13_3"],
103+
&["G14X", "V13_3"],
104+
],
105+
};
106+
107+
fn check_version(
108+
config: &VersionConfig,
109+
ver: &[usize],
110+
it: &mut impl Iterator<Item = TokenTree>,
111+
) -> bool {
112+
let first = it.next().unwrap();
113+
let val: bool = match &first {
114+
TokenTree::Group(group) => check_version(config, ver, &mut group.stream().into_iter()),
115+
TokenTree::Ident(ident) => {
116+
let key = config
117+
.fields
118+
.iter()
119+
.position(|&r| r == ident.to_string())
120+
.unwrap_or_else(|| panic!("Unknown field {}", ident));
121+
let mut operator = expect_punct(it);
122+
let mut rhs_token = it.next().unwrap();
123+
if let TokenTree::Punct(punct) = &rhs_token {
124+
operator.extend(std::iter::once(punct.as_char()));
125+
rhs_token = it.next().unwrap();
126+
}
127+
let rhs_name = if let TokenTree::Ident(ident) = &rhs_token {
128+
ident.to_string()
129+
} else {
130+
panic!("Unexpected token {}", ident)
131+
};
132+
133+
let rhs = config.enums[key]
134+
.iter()
135+
.position(|&r| r == rhs_name)
136+
.unwrap_or_else(|| panic!("Unknown value for {}:{}", ident, rhs_name));
137+
let lhs = ver[key];
138+
139+
match operator.as_str() {
140+
"==" => lhs == rhs,
141+
"!=" => lhs != rhs,
142+
">" => lhs > rhs,
143+
">=" => lhs >= rhs,
144+
"<" => lhs < rhs,
145+
"<=" => lhs <= rhs,
146+
_ => panic!("Unknown operator {}", operator),
147+
}
148+
}
149+
_ => {
150+
panic!("Unknown token {}", first)
151+
}
152+
};
153+
154+
let boolop = it.next();
155+
match boolop {
156+
Some(TokenTree::Punct(punct)) => {
157+
let right = expect_punct(it);
158+
if right != punct.to_string() {
159+
panic!("Unexpected op {}{}", punct, right);
160+
}
161+
match punct.as_char() {
162+
'&' => val && check_version(config, ver, it),
163+
'|' => val || check_version(config, ver, it),
164+
_ => panic!("Unexpected op {}{}", right, right),
165+
}
166+
}
167+
Some(a) => panic!("Unexpected op {}", a),
168+
None => val,
169+
}
170+
}
171+
172+
fn filter_versions(
173+
config: &VersionConfig,
174+
tag: &str,
175+
ver: &[usize],
176+
tree: impl IntoIterator<Item = TokenTree>,
177+
is_struct: bool,
178+
) -> Vec<TokenTree> {
179+
let mut out = Vec::<TokenTree>::new();
180+
let mut it = tree.into_iter();
181+
182+
while let Some(token) = it.next() {
183+
let mut tail: Option<TokenTree> = None;
184+
match &token {
185+
TokenTree::Punct(punct) if punct.to_string() == "#" => {
186+
let group = expect_group(&mut it);
187+
let mut grp_it = group.stream().into_iter();
188+
let attr = grp_it.next().unwrap();
189+
match attr {
190+
TokenTree::Ident(ident) if ident.to_string() == "ver" => {
191+
if check_version(config, ver, &mut grp_it) {
192+
} else if is_struct {
193+
drop_until_punct(&mut it, ",", true);
194+
} else {
195+
let first = it.next().unwrap();
196+
match &first {
197+
TokenTree::Ident(ident)
198+
if ["while", "for", "loop", "if", "match", "unsafe", "fn"]
199+
.contains(&ident.to_string().as_str()) =>
200+
{
201+
drop_until_braces(&mut it);
202+
}
203+
TokenTree::Group(_) => (),
204+
_ => {
205+
drop_until_punct(&mut it, ",;", false);
206+
}
207+
}
208+
}
209+
}
210+
_ => {
211+
out.push(token.clone());
212+
out.push(TokenTree::Group(group.clone()));
213+
}
214+
}
215+
continue;
216+
}
217+
TokenTree::Punct(punct) if punct.to_string() == ":" => {
218+
let next = it.next();
219+
match next {
220+
Some(TokenTree::Punct(punct)) if punct.to_string() == ":" => {
221+
let next = it.next();
222+
match next {
223+
Some(TokenTree::Ident(idtag)) if idtag.to_string() == "ver" => {
224+
let ident = match out.pop() {
225+
Some(TokenTree::Ident(ident)) => ident,
226+
a => panic!("$ver not following ident: {:?}", a),
227+
};
228+
let name = ident.to_string() + tag;
229+
let new_ident = Ident::new(name.as_str(), ident.span());
230+
out.push(TokenTree::Ident(new_ident));
231+
continue;
232+
}
233+
Some(a) => {
234+
out.push(token.clone());
235+
out.push(token.clone());
236+
tail = Some(a);
237+
}
238+
None => {
239+
out.push(token.clone());
240+
out.push(token.clone());
241+
}
242+
}
243+
}
244+
Some(a) => {
245+
out.push(token.clone());
246+
tail = Some(a);
247+
}
248+
None => {
249+
out.push(token.clone());
250+
continue;
251+
}
252+
}
253+
}
254+
_ => {
255+
tail = Some(token);
256+
}
257+
}
258+
match &tail {
259+
Some(TokenTree::Group(group)) => {
260+
let new_body =
261+
filter_versions(config, tag, ver, &mut group.stream().into_iter(), is_struct);
262+
let mut stream = TokenStream::new();
263+
stream.extend(new_body);
264+
let mut filtered_group = Group::new(group.delimiter(), stream);
265+
filtered_group.set_span(group.span());
266+
out.push(TokenTree::Group(filtered_group));
267+
}
268+
Some(token) => {
269+
out.push(token.clone());
270+
}
271+
None => {}
272+
}
273+
}
274+
275+
out
276+
}
277+
278+
pub(crate) fn versions(attr: TokenStream, item: TokenStream) -> TokenStream {
279+
let config = match attr.to_string().as_str() {
280+
"AGX" => &AGX_VERSIONS,
281+
_ => panic!("Unknown version group {}", attr),
282+
};
283+
284+
let mut it = item.into_iter();
285+
let mut out = TokenStream::new();
286+
let mut body: Vec<TokenTree> = Vec::new();
287+
let mut is_struct = false;
288+
289+
while let Some(token) = it.next() {
290+
match token {
291+
TokenTree::Punct(punct) if punct.to_string() == "#" => {
292+
body.push(TokenTree::Punct(punct));
293+
body.push(it.next().unwrap());
294+
}
295+
TokenTree::Ident(ident)
296+
if ["struct", "enum", "union", "const", "type"]
297+
.contains(&ident.to_string().as_str()) =>
298+
{
299+
is_struct = ident.to_string() != "const";
300+
body.push(TokenTree::Ident(ident));
301+
body.push(it.next().unwrap());
302+
// This isn't valid syntax in a struct definition, so add it for the user
303+
body.push(TokenTree::Punct(Punct::new(':', Spacing::Joint)));
304+
body.push(TokenTree::Punct(Punct::new(':', Spacing::Alone)));
305+
body.push(TokenTree::Ident(Ident::new("ver", Span::call_site())));
306+
break;
307+
}
308+
TokenTree::Ident(ident) if ident.to_string() == "impl" => {
309+
body.push(TokenTree::Ident(ident));
310+
break;
311+
}
312+
TokenTree::Ident(ident) if ident.to_string() == "fn" => {
313+
body.push(TokenTree::Ident(ident));
314+
break;
315+
}
316+
_ => {
317+
body.push(token);
318+
}
319+
}
320+
}
321+
322+
body.extend(it);
323+
324+
for ver in config.versions {
325+
let tag = ver.join("");
326+
let mut ver_num = Vec::<usize>::new();
327+
for (i, comp) in ver.iter().enumerate() {
328+
let idx = config.enums[i].iter().position(|&r| r == *comp).unwrap();
329+
ver_num.push(idx);
330+
}
331+
out.extend(filter_versions(
332+
config,
333+
&tag,
334+
&ver_num,
335+
body.clone(),
336+
is_struct,
337+
));
338+
}
339+
340+
out
341+
}

0 commit comments

Comments
 (0)