aboutsummaryrefslogtreecommitdiff
path: root/crates/mbe/src/benchmark.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/mbe/src/benchmark.rs')
-rw-r--r--crates/mbe/src/benchmark.rs225
1 files changed, 225 insertions, 0 deletions
diff --git a/crates/mbe/src/benchmark.rs b/crates/mbe/src/benchmark.rs
new file mode 100644
index 000000000..503ad1355
--- /dev/null
+++ b/crates/mbe/src/benchmark.rs
@@ -0,0 +1,225 @@
1//! This module add real world mbe example for benchmark tests
2
3use rustc_hash::FxHashMap;
4use syntax::{
5 ast::{self, NameOwner},
6 AstNode, SmolStr,
7};
8use test_utils::{bench, bench_fixture, skip_slow_tests};
9
10use crate::{
11 ast_to_token_tree,
12 parser::{Op, RepeatKind, Separator},
13 MacroRules,
14};
15
16#[test]
17fn benchmark_parse_macro_rules() {
18 if skip_slow_tests() {
19 return;
20 }
21 let rules = macro_rules_fixtures_tt();
22 let hash: usize = {
23 let _pt = bench("mbe parse macro rules");
24 rules.values().map(|it| MacroRules::parse(it).unwrap().rules.len()).sum()
25 };
26 assert_eq!(hash, 1144);
27}
28
29#[test]
30fn benchmark_expand_macro_rules() {
31 if skip_slow_tests() {
32 return;
33 }
34 let rules = macro_rules_fixtures();
35 let invocations = invocation_fixtures(&rules);
36
37 let hash: usize = {
38 let _pt = bench("mbe expand macro rules");
39 invocations
40 .into_iter()
41 .map(|(id, tt)| {
42 let res = rules[&id].expand(&tt);
43 assert!(res.err.is_none());
44 res.value.token_trees.len()
45 })
46 .sum()
47 };
48 assert_eq!(hash, 69413);
49}
50
51fn macro_rules_fixtures() -> FxHashMap<String, MacroRules> {
52 macro_rules_fixtures_tt()
53 .into_iter()
54 .map(|(id, tt)| (id, MacroRules::parse(&tt).unwrap()))
55 .collect()
56}
57
58fn macro_rules_fixtures_tt() -> FxHashMap<String, tt::Subtree> {
59 let fixture = bench_fixture::numerous_macro_rules();
60 let source_file = ast::SourceFile::parse(&fixture).ok().unwrap();
61
62 source_file
63 .syntax()
64 .descendants()
65 .filter_map(ast::MacroRules::cast)
66 .map(|rule| {
67 let id = rule.name().unwrap().to_string();
68 let (def_tt, _) = ast_to_token_tree(&rule.token_tree().unwrap()).unwrap();
69 (id, def_tt)
70 })
71 .collect()
72}
73
74/// Generate random invocation fixtures from rules
75fn invocation_fixtures(rules: &FxHashMap<String, MacroRules>) -> Vec<(String, tt::Subtree)> {
76 let mut seed = 123456789;
77 let mut res = Vec::new();
78
79 for (name, it) in rules {
80 for rule in &it.rules {
81 // Generate twice
82 for _ in 0..2 {
83 // The input are generated by filling the `Op` randomly.
84 // However, there are some cases generated are ambiguous for expanding, for example:
85 // ```rust
86 // macro_rules! m {
87 // ($($t:ident),* as $ty:ident) => {}
88 // }
89 // m!(as u32); // error: local ambiguity: multiple parsing options: built-in NTs ident ('t') or 1 other option.
90 // ```
91 //
92 // So we just skip any error cases and try again
93 let mut try_cnt = 0;
94 loop {
95 let mut subtree = tt::Subtree::default();
96 for op in rule.lhs.iter() {
97 collect_from_op(op, &mut subtree, &mut seed);
98 }
99 if it.expand(&subtree).err.is_none() {
100 res.push((name.clone(), subtree));
101 break;
102 }
103 try_cnt += 1;
104 if try_cnt > 100 {
105 panic!("invocaton fixture {} cannot be generated.\n", name);
106 }
107 }
108 }
109 }
110 }
111 return res;
112
113 fn collect_from_op(op: &Op, parent: &mut tt::Subtree, seed: &mut usize) {
114 return match op {
115 Op::Var { kind, .. } => match kind.as_ref().map(|it| it.as_str()) {
116 Some("ident") => parent.token_trees.push(make_ident("foo")),
117 Some("ty") => parent.token_trees.push(make_ident("Foo")),
118 Some("tt") => parent.token_trees.push(make_ident("foo")),
119 Some("vis") => parent.token_trees.push(make_ident("pub")),
120 Some("pat") => parent.token_trees.push(make_ident("foo")),
121 Some("path") => parent.token_trees.push(make_ident("foo")),
122 Some("literal") => parent.token_trees.push(make_literal("1")),
123 Some("expr") => parent.token_trees.push(make_ident("foo").into()),
124 Some("lifetime") => {
125 parent.token_trees.push(make_punct('\''));
126 parent.token_trees.push(make_ident("a"));
127 }
128 Some("block") => {
129 parent.token_trees.push(make_subtree(tt::DelimiterKind::Brace, None))
130 }
131 Some("item") => {
132 parent.token_trees.push(make_ident("fn"));
133 parent.token_trees.push(make_ident("foo"));
134 parent.token_trees.push(make_subtree(tt::DelimiterKind::Parenthesis, None));
135 parent.token_trees.push(make_subtree(tt::DelimiterKind::Brace, None));
136 }
137 Some("meta") => {
138 parent.token_trees.push(make_ident("foo"));
139 parent.token_trees.push(make_subtree(tt::DelimiterKind::Parenthesis, None));
140 }
141
142 None => (),
143 Some(kind) => panic!("Unhandled kind {}", kind),
144 },
145 Op::Leaf(leaf) => parent.token_trees.push(leaf.clone().into()),
146 Op::Repeat { tokens, kind, separator } => {
147 let max = 10;
148 let cnt = match kind {
149 RepeatKind::ZeroOrMore => rand(seed) % max,
150 RepeatKind::OneOrMore => 1 + rand(seed) % max,
151 RepeatKind::ZeroOrOne => rand(seed) % 2,
152 };
153 for i in 0..cnt {
154 for it in tokens.iter() {
155 collect_from_op(it, parent, seed);
156 }
157 if i + 1 != cnt {
158 if let Some(sep) = separator {
159 match sep {
160 Separator::Literal(it) => parent
161 .token_trees
162 .push(tt::Leaf::Literal(it.clone().into()).into()),
163 Separator::Ident(it) => parent
164 .token_trees
165 .push(tt::Leaf::Ident(it.clone().into()).into()),
166 Separator::Puncts(puncts) => {
167 for it in puncts {
168 parent
169 .token_trees
170 .push(tt::Leaf::Punct(it.clone().into()).into())
171 }
172 }
173 };
174 }
175 }
176 }
177 }
178 Op::Subtree { tokens, delimiter } => {
179 let mut subtree =
180 tt::Subtree { delimiter: delimiter.clone(), token_trees: Vec::new() };
181 tokens.iter().for_each(|it| {
182 collect_from_op(it, &mut subtree, seed);
183 });
184 parent.token_trees.push(subtree.into());
185 }
186 };
187
188 // Simple linear congruential generator for determistic result
189 fn rand(seed: &mut usize) -> usize {
190 let a = 1664525;
191 let c = 1013904223;
192 *seed = usize::wrapping_add(usize::wrapping_mul(*seed, a), c);
193 return *seed;
194 }
195 fn make_ident(ident: &str) -> tt::TokenTree {
196 tt::Leaf::Ident(tt::Ident { id: tt::TokenId::unspecified(), text: SmolStr::new(ident) })
197 .into()
198 }
199 fn make_punct(char: char) -> tt::TokenTree {
200 tt::Leaf::Punct(tt::Punct {
201 id: tt::TokenId::unspecified(),
202 char,
203 spacing: tt::Spacing::Alone,
204 })
205 .into()
206 }
207 fn make_literal(lit: &str) -> tt::TokenTree {
208 tt::Leaf::Literal(tt::Literal {
209 id: tt::TokenId::unspecified(),
210 text: SmolStr::new(lit),
211 })
212 .into()
213 }
214 fn make_subtree(
215 kind: tt::DelimiterKind,
216 token_trees: Option<Vec<tt::TokenTree>>,
217 ) -> tt::TokenTree {
218 tt::Subtree {
219 delimiter: Some(tt::Delimiter { id: tt::TokenId::unspecified(), kind }),
220 token_trees: token_trees.unwrap_or_default(),
221 }
222 .into()
223 }
224 }
225}