use std::fmt::Write as _;
use rucc_base::Interner;
use crate::pipeline::Report;
use crate::stats::Kind;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Wants {
optimized: bool,
missed: bool,
note: bool,
}
impl Wants {
#[must_use]
pub const fn none() -> Self {
Self { optimized: false, missed: false, note: false }
}
#[must_use]
pub const fn all() -> Self {
Self { optimized: true, missed: true, note: true }
}
pub fn add(&mut self, spec: &str) -> Result<(), String> {
if spec.is_empty() {
self.optimized = true;
return Ok(());
}
for word in spec.split('-') {
match word {
"all" => *self = Self::all(),
"optimized" => self.optimized = true,
"missed" => self.missed = true,
"note" => self.note = true,
_ => {
return Err(format!(
"`{word}` is not a kind of remark this compiler makes, which are \
`optimized`, `missed`, `note` and `all`"
));
}
}
}
Ok(())
}
#[must_use]
pub const fn wants(self, kind: Kind) -> bool {
match kind {
Kind::Optimized => self.optimized,
Kind::Missed => self.missed,
Kind::Note => self.note,
}
}
#[must_use]
pub const fn is_empty(self) -> bool {
!self.optimized && !self.missed && !self.note
}
}
#[must_use]
pub fn render(file: &str, report: &Report, names: &Interner, wants: Wants) -> String {
let mut out = String::new();
if wants.is_empty() {
return out;
}
for remark in &report.remarks {
let func = names.resolve(remark.func);
for event in remark.stats.events() {
if !wants.wants(event.kind) {
continue;
}
let _ = writeln!(
out,
"{file}: {func}: {}: {} ({}) [{}]",
event.kind, event.what, event.count, remark.pass
);
}
}
out
}
#[must_use]
pub fn totals(report: &Report) -> String {
let mut out = String::new();
let mut seen: Vec<&'static str> = Vec::new();
for remark in &report.remarks {
if !seen.contains(&remark.pass) {
seen.push(remark.pass);
}
}
for pass in seen {
let stats = report.totals(pass);
if stats.is_empty() {
let _ = writeln!(out, "{pass}: nothing");
continue;
}
for event in stats.events() {
let _ = writeln!(out, "{pass}: {}: {} ({})", event.kind, event.what, event.count);
}
}
out
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use super::{Wants, render, totals};
use crate::Stats;
use crate::pipeline::{Remark, Report};
use crate::stats::Kind;
fn report(names: &mut Interner) -> Report {
let f = names.intern("f");
let g = names.intern("g");
let mut loud = Stats::new();
loud.record(Kind::Optimized, "a rewrite", 3);
loud.missed("a site it gave up on");
loud.note("something an analysis found");
Report {
remarks: vec![
Remark { pass: "fold", func: f, stats: loud },
Remark { pass: "fold", func: g, stats: Stats::new() },
Remark { pass: "dce", func: f, stats: Stats::new() },
Remark { pass: "dce", func: g, stats: Stats::new() },
],
..Report::default()
}
}
#[test]
fn nothing_is_printed_when_nothing_was_asked_for() {
let mut names = Interner::new();
let report = report(&mut names);
assert_eq!(render("a.c", &report, &names, Wants::none()), "");
}
#[test]
fn a_bare_flag_asks_for_the_rewrites_and_nothing_else() {
let mut wants = Wants::none();
wants.add("").expect("the bare flag is always allowed");
assert!(wants.wants(Kind::Optimized));
assert!(!wants.wants(Kind::Missed));
assert!(!wants.wants(Kind::Note));
}
#[test]
fn keywords_are_joined_by_hyphens_and_two_flags_add_up() {
let mut wants = Wants::none();
wants.add("missed-note").expect("both of those exist");
assert!(!wants.wants(Kind::Optimized));
assert!(wants.wants(Kind::Missed));
assert!(wants.wants(Kind::Note));
wants.add("optimized").expect("that exists too");
assert_eq!(wants, Wants::all(), "the second flag replaced the first");
}
#[test]
fn a_keyword_that_does_not_exist_is_refused_rather_than_ignored() {
let mut wants = Wants::none();
let why = wants.add("vectorized").expect_err("no such kind");
assert!(why.contains("`optimized`"), "{why}");
let why = wants.add("missed-vectorized").expect_err("one bad word spoils the argument");
assert!(why.contains("vectorized"), "{why}");
}
#[test]
fn every_kind_asked_for_is_printed_with_its_count_its_function_and_its_pass() {
let mut names = Interner::new();
let report = report(&mut names);
let text = render("a.c", &report, &names, Wants::all());
assert_eq!(
text,
"a.c: f: optimized: a rewrite (3) [fold]\n\
a.c: f: missed: a site it gave up on (1) [fold]\n\
a.c: f: note: something an analysis found (1) [fold]\n"
);
}
#[test]
fn asking_for_the_misses_leaves_out_the_rewrites() {
let mut names = Interner::new();
let report = report(&mut names);
let mut wants = Wants::none();
wants.add("missed").expect("that exists");
let text = render("a.c", &report, &names, wants);
assert_eq!(text, "a.c: f: missed: a site it gave up on (1) [fold]\n");
}
#[test]
fn the_totals_name_every_pass_that_ran_including_the_ones_that_said_nothing() {
let mut names = Interner::new();
let report = report(&mut names);
let text = totals(&report);
assert_eq!(
text,
"fold: optimized: a rewrite (3)\n\
fold: missed: a site it gave up on (1)\n\
fold: note: something an analysis found (1)\n\
dce: nothing\n"
);
}
}