1use std::fmt::Write as _;
29
30use rucc_base::Interner;
31
32use crate::pipeline::Report;
33use crate::stats::Kind;
34
35#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
40pub struct Wants {
41 optimized: bool,
43 missed: bool,
45 note: bool,
47}
48
49impl Wants {
50 #[must_use]
52 pub const fn none() -> Self {
53 Self { optimized: false, missed: false, note: false }
54 }
55
56 #[must_use]
58 pub const fn all() -> Self {
59 Self { optimized: true, missed: true, note: true }
60 }
61
62 pub fn add(&mut self, spec: &str) -> Result<(), String> {
77 if spec.is_empty() {
78 self.optimized = true;
79 return Ok(());
80 }
81 for word in spec.split('-') {
82 match word {
83 "all" => *self = Self::all(),
84 "optimized" => self.optimized = true,
85 "missed" => self.missed = true,
86 "note" => self.note = true,
87 _ => {
88 return Err(format!(
89 "`{word}` is not a kind of remark this compiler makes, which are \
90 `optimized`, `missed`, `note` and `all`"
91 ));
92 }
93 }
94 }
95 Ok(())
96 }
97
98 #[must_use]
100 pub const fn wants(self, kind: Kind) -> bool {
101 match kind {
102 Kind::Optimized => self.optimized,
103 Kind::Missed => self.missed,
104 Kind::Note => self.note,
105 }
106 }
107
108 #[must_use]
110 pub const fn is_empty(self) -> bool {
111 !self.optimized && !self.missed && !self.note
112 }
113}
114
115#[must_use]
125pub fn render(file: &str, report: &Report, names: &Interner, wants: Wants) -> String {
126 let mut out = String::new();
127 if wants.is_empty() {
128 return out;
129 }
130 for remark in &report.remarks {
131 let func = names.resolve(remark.func);
132 for event in remark.stats.events() {
133 if !wants.wants(event.kind) {
134 continue;
135 }
136 let _ = writeln!(
137 out,
138 "{file}: {func}: {}: {} ({}) [{}]",
139 event.kind, event.what, event.count, remark.pass
140 );
141 }
142 }
143 out
144}
145
146#[must_use]
152pub fn totals(report: &Report) -> String {
153 let mut out = String::new();
154 let mut seen: Vec<&'static str> = Vec::new();
155 for remark in &report.remarks {
156 if !seen.contains(&remark.pass) {
157 seen.push(remark.pass);
158 }
159 }
160 for pass in seen {
161 let stats = report.totals(pass);
162 if stats.is_empty() {
163 let _ = writeln!(out, "{pass}: nothing");
164 continue;
165 }
166 for event in stats.events() {
167 let _ = writeln!(out, "{pass}: {}: {} ({})", event.kind, event.what, event.count);
168 }
169 }
170 out
171}
172
173#[cfg(test)]
174mod tests {
175 use rucc_base::Interner;
176
177 use super::{Wants, render, totals};
178 use crate::Stats;
179 use crate::pipeline::{Remark, Report};
180 use crate::stats::Kind;
181
182 fn report(names: &mut Interner) -> Report {
184 let f = names.intern("f");
185 let g = names.intern("g");
186 let mut loud = Stats::new();
187 loud.record(Kind::Optimized, "a rewrite", 3);
188 loud.missed("a site it gave up on");
189 loud.note("something an analysis found");
190 Report {
191 remarks: vec![
192 Remark { pass: "fold", func: f, stats: loud },
193 Remark { pass: "fold", func: g, stats: Stats::new() },
194 Remark { pass: "dce", func: f, stats: Stats::new() },
195 Remark { pass: "dce", func: g, stats: Stats::new() },
196 ],
197 ..Report::default()
198 }
199 }
200
201 #[test]
202 fn nothing_is_printed_when_nothing_was_asked_for() {
203 let mut names = Interner::new();
204 let report = report(&mut names);
205 assert_eq!(render("a.c", &report, &names, Wants::none()), "");
206 }
207
208 #[test]
209 fn a_bare_flag_asks_for_the_rewrites_and_nothing_else() {
210 let mut wants = Wants::none();
211 wants.add("").expect("the bare flag is always allowed");
212 assert!(wants.wants(Kind::Optimized));
213 assert!(!wants.wants(Kind::Missed));
214 assert!(!wants.wants(Kind::Note));
215 }
216
217 #[test]
218 fn keywords_are_joined_by_hyphens_and_two_flags_add_up() {
219 let mut wants = Wants::none();
220 wants.add("missed-note").expect("both of those exist");
221 assert!(!wants.wants(Kind::Optimized));
222 assert!(wants.wants(Kind::Missed));
223 assert!(wants.wants(Kind::Note));
224 wants.add("optimized").expect("that exists too");
225 assert_eq!(wants, Wants::all(), "the second flag replaced the first");
226 }
227
228 #[test]
229 fn a_keyword_that_does_not_exist_is_refused_rather_than_ignored() {
230 let mut wants = Wants::none();
231 let why = wants.add("vectorized").expect_err("no such kind");
232 assert!(why.contains("`optimized`"), "{why}");
233 let why = wants.add("missed-vectorized").expect_err("one bad word spoils the argument");
234 assert!(why.contains("vectorized"), "{why}");
235 }
236
237 #[test]
238 fn every_kind_asked_for_is_printed_with_its_count_its_function_and_its_pass() {
239 let mut names = Interner::new();
240 let report = report(&mut names);
241 let text = render("a.c", &report, &names, Wants::all());
242 assert_eq!(
243 text,
244 "a.c: f: optimized: a rewrite (3) [fold]\n\
245 a.c: f: missed: a site it gave up on (1) [fold]\n\
246 a.c: f: note: something an analysis found (1) [fold]\n"
247 );
248 }
249
250 #[test]
251 fn asking_for_the_misses_leaves_out_the_rewrites() {
252 let mut names = Interner::new();
253 let report = report(&mut names);
254 let mut wants = Wants::none();
255 wants.add("missed").expect("that exists");
256 let text = render("a.c", &report, &names, wants);
257 assert_eq!(text, "a.c: f: missed: a site it gave up on (1) [fold]\n");
258 }
259
260 #[test]
261 fn the_totals_name_every_pass_that_ran_including_the_ones_that_said_nothing() {
262 let mut names = Interner::new();
263 let report = report(&mut names);
264 let text = totals(&report);
265 assert_eq!(
269 text,
270 "fold: optimized: a rewrite (3)\n\
271 fold: missed: a site it gave up on (1)\n\
272 fold: note: something an analysis found (1)\n\
273 dce: nothing\n"
274 );
275 }
276}