use std::collections::HashMap;
use std::fmt::Write as _;
use rucc_base::{Interner, Symbol};
use rucc_ir::{FuncId, Module};
use rucc_session::OptLevel;
use crate::{Analyses, Fuel, Gates, Pass, Preserved, Stats, pass};
const O0: &[&str] = &["simplify-cfg"];
const O1: &[&str] = &["fold", "simplify", "narrow", "simplify-cfg", "dce"];
const O2: &[&str] = &["fold", "simplify", "narrow", "simplify-cfg", "dce"];
const O3: &[&str] = &["fold", "simplify", "narrow", "simplify-cfg", "dce"];
const OS: &[&str] = &["fold", "simplify", "narrow", "simplify-cfg", "dce"];
const OZ: &[&str] = &["fold", "simplify", "narrow", "simplify-cfg", "dce"];
#[must_use]
pub const fn for_level(level: OptLevel) -> &'static [&'static str] {
match level {
OptLevel::O0 => O0,
OptLevel::O1 => O1,
OptLevel::O2 => O2,
OptLevel::O3 => O3,
OptLevel::Os => OS,
OptLevel::Oz => OZ,
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Dumps {
all: bool,
before: Vec<String>,
after: Vec<String>,
}
impl Dumps {
pub fn add(&mut self, spec: &str) -> Result<(), String> {
if spec == "all" {
self.all = true;
return Ok(());
}
let (side, name) = match spec.split_once('-') {
Some(("before", name)) => (&mut self.before, name),
Some(("after", name)) => (&mut self.after, name),
_ => {
return Err(format!(
"`{spec}` is not a dump this compiler makes, which are `all`, \
`before-<pass>` and `after-<pass>`"
));
}
};
if pass::find(name).is_none() {
return Err(format!("`{name}` is not a pass this compiler has, see --print-pipeline"));
}
side.push(name.to_owned());
Ok(())
}
#[must_use]
pub fn is_empty(&self) -> bool {
!self.all && self.before.is_empty() && self.after.is_empty()
}
#[must_use]
pub fn wants_before(&self, name: &str) -> bool {
self.all || self.before.iter().any(|it| it == name)
}
#[must_use]
pub fn wants_after(&self, name: &str) -> bool {
self.all || self.after.iter().any(|it| it == name)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Options {
pub level: OptLevel,
pub toggles: Vec<(String, bool)>,
pub fuel: HashMap<String, u32>,
pub global_fuel: Option<u32>,
pub gates: Gates,
pub dumps: Dumps,
pub verify: bool,
}
impl Default for Options {
fn default() -> Self {
Self {
level: OptLevel::default(),
toggles: Vec::new(),
fuel: HashMap::new(),
global_fuel: None,
gates: Gates::default(),
dumps: Dumps::default(),
verify: cfg!(debug_assertions),
}
}
}
impl Options {
#[must_use]
pub fn for_level(level: OptLevel) -> Self {
Self { level, ..Self::default() }
}
#[must_use]
pub fn chosen(&self) -> Vec<&'static str> {
let mut names: Vec<&str> = for_level(self.level).to_vec();
for (name, on) in &self.toggles {
let name = name.as_str();
match *on {
true if !names.contains(&name) => names.push(name),
true => {}
false => names.retain(|it| *it != name),
}
}
names.into_iter().filter_map(pass::find).map(Pass::name).collect()
}
#[must_use]
pub fn passes(&self) -> Vec<&'static dyn Pass> {
let mut names = self.chosen();
for name in self.gates.enabled() {
let Some(found) = pass::find(name) else { continue };
if !names.contains(&found.name()) {
names.push(found.name());
}
}
names.into_iter().filter_map(pass::find).collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Dump {
pub name: String,
pub text: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Remark {
pub pass: &'static str,
pub func: Symbol,
pub stats: Stats,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Report {
pub dumps: Vec<Dump>,
pub broke: Vec<String>,
pub spent: Vec<(&'static str, u32)>,
pub remarks: Vec<Remark>,
}
impl Report {
#[must_use]
pub fn totals(&self, pass: &str) -> Stats {
let mut total = Stats::new();
for remark in self.remarks.iter().filter(|it| it.pass == pass) {
total.merge(&remark.stats);
}
total
}
}
pub fn run(module: &mut Module, names: &Interner, opts: &Options) -> Report {
let mut report = Report::default();
let chosen = opts.chosen();
let mut cached: HashMap<FuncId, Analyses> = HashMap::new();
let mut budget = opts.global_fuel;
for (index, pass) in opts.passes().into_iter().enumerate() {
let name = pass.name();
if opts.dumps.wants_before(name) {
report.dumps.push(dump(index, "before", name, module, names));
}
let mut fuel = match (opts.fuel.get(name).copied(), budget) {
(Some(count), Some(left)) => Fuel::of(count.min(left)),
(Some(count), None) => Fuel::of(count),
(None, Some(left)) => Fuel::of(left),
(None, None) => Fuel::unlimited(),
};
let default = chosen.contains(&name);
for id in module.funcs() {
if module[id].is_declaration() {
continue;
}
if !opts.gates.allows(name, default, id.raw(), names.resolve(module[id].name)) {
continue;
}
let an = cached.entry(id).or_default();
let stats = pass.run(&mut module[id], an, &mut fuel);
let keeps = if stats.changed() { pass.preserves() } else { Preserved::ALL };
for broken in an.settle(&module[id], keeps, opts.verify) {
let func = names.resolve(module[id].name);
report.broke.push(format!(
"the {name} pass said it preserved {} of {func} and did not",
broken.name()
));
}
if stats.changed() && opts.verify {
if let Err(errors) = rucc_ir::verify_func(module, &module[id], names) {
let func = names.resolve(module[id].name);
for error in errors {
report
.broke
.push(format!("the {name} pass left invalid IR in {func}, {error}"));
}
}
}
report.remarks.push(Remark { pass: name, func: module[id].name, stats });
}
report.spent.push((name, fuel.spent()));
if let Some(left) = &mut budget {
*left -= fuel.spent();
}
if opts.dumps.wants_after(name) {
report.dumps.push(dump(index, "after", name, module, names));
}
}
report
}
fn dump(index: usize, side: &str, name: &str, module: &Module, names: &Interner) -> Dump {
Dump { name: format!("{index:02}-{side}-{name}"), text: rucc_ir::print(module, names) }
}
#[must_use]
pub fn print(opts: &Options) -> String {
let mut out = String::new();
let _ = writeln!(out, "level: {}", opts.level);
if let Some(count) = opts.global_fuel {
let _ = writeln!(out, "global fuel: {count}");
}
let passes = opts.passes();
if passes.is_empty() {
let _ = writeln!(out, "no passes");
return out;
}
for (index, pass) in passes.iter().enumerate() {
let _ = write!(out, "{}: {}, {}", index + 1, pass.name(), pass.describe());
if let Some(note) = opts.gates.note(pass.name()) {
let _ = write!(out, " [{note}]");
}
out.push('\n');
}
out
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{Builder, Func, Module, Opcode, Signature, Type};
use rucc_session::OptLevel;
use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
use super::{Dumps, Options, for_level};
use crate::stats::Kind;
use crate::{Pass, pass};
fn module() -> (Interner, Module) {
let mut names = Interner::new();
let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
let mut module = Module::new(names.intern("test.c"), &target);
let func = foldable(&mut names, "f");
module.add_func(func);
(names, module)
}
fn two_functions() -> (Interner, Module) {
let mut names = Interner::new();
let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
let mut module = Module::new(names.intern("test.c"), &target);
for name in ["f", "g"] {
let func = foldable(&mut names, name);
module.add_func(func);
}
(names, module)
}
fn foldable(names: &mut Interner, name: &str) -> Func {
let mut func =
Func::new(names.intern(name), Signature::new().with_returns(&[Type::int(64)]));
let block = func.create_block();
let mut build = Builder::new(&mut func, block);
let narrow = build.iconst(Type::int(32), 7);
let wide = build.unary(Opcode::SExt, narrow, Type::int(64));
build.ret(&[wide]);
func
}
fn spoke_about(report: &super::Report, pass: &str, func: &str, names: &Interner) -> bool {
report.remarks.iter().any(|it| it.pass == pass && names.resolve(it.func) == func)
}
#[test]
fn every_pass_a_pipeline_names_is_a_pass_that_exists() {
for level in
[OptLevel::O0, OptLevel::O1, OptLevel::O2, OptLevel::O3, OptLevel::Os, OptLevel::Oz]
{
for name in for_level(level) {
assert!(
pass::find(name).is_some(),
"{level} names `{name}` and no pass answers to it"
);
}
}
}
#[test]
fn no_pipeline_names_a_pass_twice() {
for level in
[OptLevel::O0, OptLevel::O1, OptLevel::O2, OptLevel::O3, OptLevel::Os, OptLevel::Oz]
{
let names = for_level(level);
for (index, name) in names.iter().enumerate() {
assert!(!names[index + 1..].contains(name), "{level} runs `{name}` twice");
}
}
}
fn spent(report: &super::Report, pass: &str) -> Option<u32> {
report.spent.iter().find(|(name, _)| *name == pass).map(|&(_, count)| count)
}
fn names(opts: &Options) -> Vec<&'static str> {
opts.passes().into_iter().map(Pass::name).collect()
}
#[test]
fn the_level_that_optimizes_nothing_still_removes_what_nothing_reaches() {
assert_eq!(names(&Options::for_level(OptLevel::O0)), ["simplify-cfg"]);
assert!(names(&Options::for_level(OptLevel::O2)).len() > 1);
}
#[test]
fn a_pass_is_removed_by_no_and_added_by_the_bare_name_and_the_last_word_wins() {
let mut opts = Options::for_level(OptLevel::O2);
opts.toggles.push(("fold".to_owned(), false));
assert!(!names(&opts).contains(&"fold"), "{:?}", names(&opts));
opts.toggles.push(("fold".to_owned(), true));
assert!(names(&opts).contains(&"fold"), "{:?}", names(&opts));
let mut off = Options::for_level(OptLevel::O0);
off.toggles.push(("fold".to_owned(), true));
assert_eq!(
names(&off),
["simplify-cfg", "fold"],
"a pass the level did not choose is still reachable"
);
}
#[test]
fn asking_for_a_pass_twice_does_not_run_it_twice() {
let mut opts = Options::for_level(OptLevel::O2);
let before = names(&opts);
opts.toggles.push(("fold".to_owned(), true));
assert_eq!(names(&opts), before);
}
#[test]
fn the_pipeline_listing_names_the_level_and_every_pass_in_order() {
let text = super::print(&Options::for_level(OptLevel::O2));
assert!(text.starts_with("level: -O2\n"), "{text}");
assert!(text.contains("1: fold, "), "{text}");
let mut none = Options::for_level(OptLevel::O0);
none.toggles.push(("simplify-cfg".to_owned(), false));
let none = super::print(&none);
assert!(none.contains("no passes"), "{none}");
}
#[test]
fn running_the_pipeline_changes_the_module_and_reports_what_it_spent() {
let (names, mut module) = module();
let report = super::run(&mut module, &names, &Options::for_level(OptLevel::O2));
assert_eq!(spent(&report, "fold"), Some(1));
assert_eq!(spent(&report, "dce"), Some(1));
assert!(report.broke.is_empty(), "{:?}", report.broke);
assert!(report.dumps.is_empty(), "nothing asked for a dump");
assert!(rucc_ir::print(&module, &names).contains("iconst.i64 7"));
}
#[test]
fn the_analyses_survive_a_pass_that_keeps_them_and_not_one_that_does_not() {
let mut names = Interner::new();
let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
let mut module = Module::new(names.intern("test.c"), &target);
let mut func = Func::new(names.intern("f"), Signature::new());
let entry = func.create_block();
let dead = func.create_block();
let exit = func.create_block();
let mut build = Builder::new(&mut func, entry);
let never = build.iconst(Type::int(1), 0);
build.br_if(never, dead, &[], exit, &[]);
for block in [dead, exit] {
let mut build = Builder::new(&mut func, block);
build.ret(&[]);
}
module.add_func(func);
let report = super::run(&mut module, &names, &Options::for_level(OptLevel::O2));
assert_eq!(spent(&report, "simplify-cfg"), Some(1));
assert!(report.broke.is_empty(), "{:?}", report.broke);
let text = rucc_ir::print(&module, &names);
assert_eq!(
text.matches("\nblock").count(),
2,
"the block nothing reaches is still here:\n{text}"
);
}
#[test]
fn no_pass_that_optimizes_runs_at_no_optimization_however_much_there_is_to_do() {
let (names, mut module) = module();
let before = rucc_ir::print(&module, &names);
let report = super::run(&mut module, &names, &Options::for_level(OptLevel::O0));
assert_eq!(report.spent, vec![("simplify-cfg", 0)]);
assert_eq!(rucc_ir::print(&module, &names), before);
}
#[test]
fn a_gate_takes_a_pass_away_from_one_function_and_leaves_the_other_alone() {
let (names, mut module) = two_functions();
let mut opts = Options::for_level(OptLevel::O2);
opts.gates.add(false, "fold=g").expect("g is a function and fold is a pass");
let report = super::run(&mut module, &names, &opts);
assert!(spoke_about(&report, "fold", "f", &names));
assert!(!spoke_about(&report, "fold", "g", &names), "fold ran where it was gated off");
assert!(spoke_about(&report, "dce", "g", &names), "one pass gated off is not all of them");
let text = rucc_ir::print(&module, &names);
assert_eq!(text.matches("sext.i64").count(), 1, "{text}");
}
#[test]
fn a_function_can_be_gated_by_the_number_it_has_in_the_module() {
let (names, mut module) = two_functions();
let mut opts = Options::for_level(OptLevel::O2);
opts.gates.add(false, "fold=0").expect("0 is a function and fold is a pass");
let report = super::run(&mut module, &names, &opts);
assert!(!spoke_about(&report, "fold", "f", &names), "function 0 is the first one");
assert!(spoke_about(&report, "fold", "g", &names));
}
#[test]
fn enabling_a_pass_reaches_one_function_at_a_level_that_did_not_ask_for_it() {
let (names, mut module) = two_functions();
let mut opts = Options::for_level(OptLevel::O0);
opts.gates.add(true, "fold=1").expect("1 is a function and fold is a pass");
let running: Vec<&str> = opts.passes().into_iter().map(Pass::name).collect();
assert_eq!(
running,
["simplify-cfg", "fold"],
"the flag has to put the pass in the pipeline"
);
let report = super::run(&mut module, &names, &opts);
assert!(!spoke_about(&report, "fold", "f", &names), "nothing asked for f");
assert!(spoke_about(&report, "fold", "g", &names));
let text = rucc_ir::print(&module, &names);
assert_eq!(text.matches("sext.i64").count(), 1, "{text}");
}
#[test]
fn a_pass_gated_off_everywhere_runs_on_nothing_and_still_says_so() {
let (names, mut module) = two_functions();
let before = rucc_ir::print(&module, &names);
let mut opts = Options::for_level(OptLevel::O2);
for pass in pass::PASSES {
opts.gates.add(false, pass.name()).expect("a pass in the list is a pass that exists");
}
let report = super::run(&mut module, &names, &opts);
assert!(report.remarks.is_empty(), "a pass that did not run has nothing to report");
assert_eq!(spent(&report, "fold"), Some(0), "the pass is still in the pipeline");
assert_eq!(rucc_ir::print(&module, &names), before);
}
#[test]
fn the_pipeline_listing_says_which_passes_a_gate_touched() {
let mut opts = Options::for_level(OptLevel::O2);
opts.gates.add(false, "fold=2-4").expect("fold is a pass");
let text = super::print(&opts);
assert!(text.contains("1: fold, "), "{text}");
assert!(text.contains("[off for 2-4]"), "{text}");
assert_eq!(text.matches('[').count(), 1, "a pass no gate mentions says nothing extra");
}
#[test]
fn every_pass_at_no_fuel_leaves_the_module_exactly_as_it_found_it() {
for pass in pass::PASSES {
let (names, mut module) = module();
let before = rucc_ir::print(&module, &names);
let mut opts = Options::for_level(OptLevel::O0);
opts.toggles.push(("simplify-cfg".to_owned(), false));
opts.toggles.push((pass.name().to_owned(), true));
opts.fuel.insert(pass.name().to_owned(), 0);
let report = super::run(&mut module, &names, &opts);
assert_eq!(
report.spent,
vec![(pass.name(), 0)],
"{} spent fuel it had none of",
pass.name()
);
assert_eq!(
rucc_ir::print(&module, &names),
before,
"{} transformed the module at fuel zero",
pass.name()
);
}
}
#[test]
fn fuel_is_shared_across_the_functions_of_a_module() {
let mut names = Interner::new();
let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
let mut module = Module::new(names.intern("test.c"), &target);
for which in ["f", "g"] {
let mut func =
Func::new(names.intern(which), Signature::new().with_returns(&[Type::int(64)]));
let block = func.create_block();
let mut build = Builder::new(&mut func, block);
let narrow = build.iconst(Type::int(32), 7);
let wide = build.unary(Opcode::SExt, narrow, Type::int(64));
build.ret(&[wide]);
module.add_func(func);
}
let mut opts = Options::for_level(OptLevel::O2);
opts.fuel.insert("fold".to_owned(), 1);
let report = super::run(&mut module, &names, &opts);
assert_eq!(spent(&report, "fold"), Some(1));
assert_eq!(spent(&report, "dce"), Some(1));
let text = rucc_ir::print(&module, &names);
assert_eq!(text.matches("sext.i64").count(), 1, "{text}");
}
#[test]
fn global_fuel_is_spent_by_the_passes_in_order_and_the_rest_get_none() {
let (names, mut module) = module();
let mut opts = Options::for_level(OptLevel::O2);
opts.global_fuel = Some(1);
let report = super::run(&mut module, &names, &opts);
assert_eq!(spent(&report, "fold"), Some(1));
assert_eq!(spent(&report, "dce"), Some(0));
let text = rucc_ir::print(&module, &names);
assert!(text.contains("iconst.i64 7"), "{text}");
assert!(text.contains("iconst.i32 7"), "the orphaned constant is still there, {text}");
}
#[test]
fn a_budget_of_nothing_leaves_the_module_alone_and_still_runs_every_pass() {
let (names, mut module) = module();
let before = rucc_ir::print(&module, &names);
let mut opts = Options::for_level(OptLevel::O2);
opts.global_fuel = Some(0);
let report = super::run(&mut module, &names, &opts);
assert_eq!(rucc_ir::print(&module, &names), before);
assert!(report.spent.iter().all(|(_, spent)| *spent == 0), "{:?}", report.spent);
assert_eq!(report.spent.len(), opts.passes().len());
}
#[test]
fn the_tighter_of_the_two_limits_is_the_one_that_stops_the_pass() {
let (names, mut under) = module();
let mut opts = Options::for_level(OptLevel::O2);
opts.global_fuel = Some(0);
opts.fuel.insert("fold".to_owned(), 9);
assert_eq!(spent(&super::run(&mut under, &names, &opts), "fold"), Some(0));
let (names, mut over) = module();
let mut opts = Options::for_level(OptLevel::O2);
opts.global_fuel = Some(9);
opts.fuel.insert("fold".to_owned(), 0);
let report = super::run(&mut over, &names, &opts);
assert_eq!(spent(&report, "fold"), Some(0));
assert_eq!(spent(&report, "dce"), Some(0), "nothing was orphaned for it to remove");
}
#[test]
fn the_pipeline_listing_says_when_there_is_a_budget_and_says_nothing_when_there_is_not() {
let opts = Options::for_level(OptLevel::O2);
assert!(!super::print(&opts).contains("global fuel"));
let with = Options { global_fuel: Some(12), ..Options::for_level(OptLevel::O2) };
assert!(super::print(&with).contains("global fuel: 12"), "{}", super::print(&with));
}
#[test]
fn a_dump_is_taken_on_the_side_that_asked_for_it_and_not_the_other() {
let (names, mut module) = module();
let mut opts = Options::for_level(OptLevel::O2);
opts.dumps.add("after-fold").expect("a pass that exists");
let report = super::run(&mut module, &names, &opts);
assert_eq!(report.dumps.len(), 1);
assert_eq!(report.dumps[0].name, "00-after-fold");
assert!(report.dumps[0].text.contains("iconst.i64 7"));
}
#[test]
fn asking_for_all_dumps_gives_both_sides_of_every_pass() {
let (interner, mut module) = module();
let opts = {
let mut opts = Options::for_level(OptLevel::O2);
opts.dumps.add("all").expect("all is always a dump");
opts
};
let report = super::run(&mut module, &interner, &opts);
let taken: Vec<&str> = report.dumps.iter().map(|d| d.name.as_str()).collect();
let expected: Vec<String> = names(&opts)
.into_iter()
.enumerate()
.flat_map(|(at, name)| {
[format!("{at:02}-before-{name}"), format!("{at:02}-after-{name}")]
})
.collect();
assert_eq!(taken, expected);
assert!(report.dumps[0].text.contains("sext.i64"));
assert!(!report.dumps[1].text.contains("sext.i64"));
}
#[test]
fn every_pass_leaves_a_record_for_every_function_whether_or_not_it_had_anything_to_say() {
let (names, mut module) = module();
let opts = Options::for_level(OptLevel::O2);
let report = super::run(&mut module, &names, &opts);
let ran: Vec<&'static str> = opts.passes().into_iter().map(Pass::name).collect();
let seen: Vec<&'static str> = report.remarks.iter().map(|it| it.pass).collect();
assert_eq!(seen, ran);
assert!(report.remarks.iter().all(|it| names.resolve(it.func) == "f"));
assert!(
report.remarks.iter().any(|it| it.pass == "simplify" && it.stats.is_empty()),
"there is nothing in the fixture for the peephole to do"
);
}
#[test]
fn a_pass_spends_one_unit_of_fuel_for_each_rewrite_it_reports() {
let (names, mut module) = module();
let report = super::run(&mut module, &names, &Options::for_level(OptLevel::O2));
for (pass, spent) in &report.spent {
assert_eq!(
report.totals(pass).total(Kind::Optimized),
*spent,
"{pass} spent {spent} units of fuel and did not say on what"
);
}
assert!(report.spent.iter().any(|(_, spent)| *spent > 0), "nothing happened at all");
}
#[test]
fn what_the_passes_said_is_what_opt_info_prints() {
let (names, mut module) = module();
let report = super::run(&mut module, &names, &Options::for_level(OptLevel::O2));
let text = crate::optinfo::render("t.c", &report, &names, crate::Wants::all());
assert!(
text.contains("t.c: f: optimized: integer instruction folded to a constant (1) [fold]"),
"{text}"
);
assert!(
text.contains(
"t.c: f: optimized: instruction with no effects and no users removed (1) [dce]"
),
"{text}"
);
let mut misses = crate::Wants::none();
misses.add("missed").expect("that kind exists");
assert_eq!(crate::optinfo::render("t.c", &report, &names, misses), "");
}
#[test]
fn the_verifier_says_which_function_it_refused_and_leaves_the_others_out_of_it() {
let mut names = Interner::new();
let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
let mut module = Module::new(names.intern("test.c"), &target);
module.add_func(foldable(&mut names, "f"));
let mut g = foldable(&mut names, "g");
let stranded = g.create_block();
let mut build = Builder::new(&mut g, stranded);
let seven = build.iconst(Type::int(64), 7);
build.ret(&[seven]);
module.add_func(g);
let mut opts = Options::for_level(OptLevel::O0);
opts.toggles.push(("simplify-cfg".to_owned(), false));
opts.toggles.push(("fold".to_owned(), true));
opts.verify = true;
let report = super::run(&mut module, &names, &opts);
assert_eq!(report.broke.len(), 1, "{:?}", report.broke);
let complaint = &report.broke[0];
assert!(complaint.starts_with("the fold pass left invalid IR in g,"), "{complaint}");
assert!(complaint.contains("this block is not reachable"), "{complaint}");
}
#[test]
fn a_function_a_pass_did_not_change_is_not_verified_after_it() {
let mut names = Interner::new();
let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
let mut module = Module::new(names.intern("test.c"), &target);
let mut f = Func::new(names.intern("f"), Signature::new().with_returns(&[Type::int(64)]));
for _ in 0..2 {
let block = f.create_block();
let mut build = Builder::new(&mut f, block);
let seven = build.iconst(Type::int(64), 7);
build.ret(&[seven]);
}
module.add_func(f);
module.add_func(foldable(&mut names, "g"));
let mut opts = Options::for_level(OptLevel::O0);
opts.toggles.push(("simplify-cfg".to_owned(), false));
opts.toggles.push(("fold".to_owned(), true));
opts.verify = true;
let report = super::run(&mut module, &names, &opts);
assert!(report.broke.is_empty(), "{:?}", report.broke);
assert!(spoke_about(&report, "fold", "f", &names));
}
#[test]
fn a_dump_of_a_pass_that_does_not_exist_is_refused_rather_than_ignored() {
let mut dumps = Dumps::default();
assert!(dumps.add("after-no-such-pass").is_err());
assert!(dumps.add("sideways-fold").is_err());
assert!(dumps.add("fold").is_err());
assert!(dumps.is_empty());
}
}