1use std::collections::HashMap;
34use std::fmt::Write as _;
35
36use rucc_base::{Interner, Symbol};
37use rucc_ir::{FuncId, Module};
38use rucc_session::OptLevel;
39
40use crate::{Analyses, Fuel, Gates, Pass, Preserved, Stats, pass};
41
42const O0: &[&str] = &["simplify-cfg"];
53
54const O1: &[&str] = &["fold", "simplify", "narrow", "simplify-cfg", "dce"];
61
62const O2: &[&str] = &["fold", "simplify", "narrow", "simplify-cfg", "dce"];
66
67const O3: &[&str] = &["fold", "simplify", "narrow", "simplify-cfg", "dce"];
70
71const OS: &[&str] = &["fold", "simplify", "narrow", "simplify-cfg", "dce"];
74
75const OZ: &[&str] = &["fold", "simplify", "narrow", "simplify-cfg", "dce"];
78
79#[must_use]
81pub const fn for_level(level: OptLevel) -> &'static [&'static str] {
82 match level {
83 OptLevel::O0 => O0,
84 OptLevel::O1 => O1,
85 OptLevel::O2 => O2,
86 OptLevel::O3 => O3,
87 OptLevel::Os => OS,
88 OptLevel::Oz => OZ,
89 }
90}
91
92#[derive(Debug, Clone, Default, PartialEq, Eq)]
97pub struct Dumps {
98 all: bool,
100 before: Vec<String>,
102 after: Vec<String>,
104}
105
106impl Dumps {
107 pub fn add(&mut self, spec: &str) -> Result<(), String> {
115 if spec == "all" {
116 self.all = true;
117 return Ok(());
118 }
119 let (side, name) = match spec.split_once('-') {
120 Some(("before", name)) => (&mut self.before, name),
121 Some(("after", name)) => (&mut self.after, name),
122 _ => {
123 return Err(format!(
124 "`{spec}` is not a dump this compiler makes, which are `all`, \
125 `before-<pass>` and `after-<pass>`"
126 ));
127 }
128 };
129 if pass::find(name).is_none() {
130 return Err(format!("`{name}` is not a pass this compiler has, see --print-pipeline"));
131 }
132 side.push(name.to_owned());
133 Ok(())
134 }
135
136 #[must_use]
138 pub fn is_empty(&self) -> bool {
139 !self.all && self.before.is_empty() && self.after.is_empty()
140 }
141
142 #[must_use]
144 pub fn wants_before(&self, name: &str) -> bool {
145 self.all || self.before.iter().any(|it| it == name)
146 }
147
148 #[must_use]
150 pub fn wants_after(&self, name: &str) -> bool {
151 self.all || self.after.iter().any(|it| it == name)
152 }
153}
154
155#[derive(Debug, Clone, PartialEq, Eq)]
157pub struct Options {
158 pub level: OptLevel,
160 pub toggles: Vec<(String, bool)>,
163 pub fuel: HashMap<String, u32>,
165 pub global_fuel: Option<u32>,
172 pub gates: Gates,
174 pub dumps: Dumps,
176 pub verify: bool,
178}
179
180impl Default for Options {
181 fn default() -> Self {
184 Self {
185 level: OptLevel::default(),
186 toggles: Vec::new(),
187 fuel: HashMap::new(),
188 global_fuel: None,
189 gates: Gates::default(),
190 dumps: Dumps::default(),
191 verify: cfg!(debug_assertions),
192 }
193 }
194}
195
196impl Options {
197 #[must_use]
199 pub fn for_level(level: OptLevel) -> Self {
200 Self { level, ..Self::default() }
201 }
202
203 #[must_use]
208 pub fn chosen(&self) -> Vec<&'static str> {
209 let mut names: Vec<&str> = for_level(self.level).to_vec();
210 for (name, on) in &self.toggles {
211 let name = name.as_str();
212 match *on {
213 true if !names.contains(&name) => names.push(name),
214 true => {}
215 false => names.retain(|it| *it != name),
216 }
217 }
218 names.into_iter().filter_map(pass::find).map(Pass::name).collect()
219 }
220
221 #[must_use]
228 pub fn passes(&self) -> Vec<&'static dyn Pass> {
229 let mut names = self.chosen();
230 for name in self.gates.enabled() {
231 let Some(found) = pass::find(name) else { continue };
234 if !names.contains(&found.name()) {
235 names.push(found.name());
236 }
237 }
238 names.into_iter().filter_map(pass::find).collect()
239 }
240}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
244pub struct Dump {
245 pub name: String,
248 pub text: String,
250}
251
252#[derive(Debug, Clone, PartialEq, Eq)]
258pub struct Remark {
259 pub pass: &'static str,
261 pub func: Symbol,
263 pub stats: Stats,
265}
266
267#[derive(Debug, Clone, Default, PartialEq, Eq)]
269pub struct Report {
270 pub dumps: Vec<Dump>,
273 pub broke: Vec<String>,
275 pub spent: Vec<(&'static str, u32)>,
277 pub remarks: Vec<Remark>,
280}
281
282impl Report {
283 #[must_use]
289 pub fn totals(&self, pass: &str) -> Stats {
290 let mut total = Stats::new();
291 for remark in self.remarks.iter().filter(|it| it.pass == pass) {
292 total.merge(&remark.stats);
293 }
294 total
295 }
296}
297
298pub fn run(module: &mut Module, names: &Interner, opts: &Options) -> Report {
304 let mut report = Report::default();
305 let chosen = opts.chosen();
306 let mut cached: HashMap<FuncId, Analyses> = HashMap::new();
312 let mut budget = opts.global_fuel;
316 for (index, pass) in opts.passes().into_iter().enumerate() {
317 let name = pass.name();
318 if opts.dumps.wants_before(name) {
319 report.dumps.push(dump(index, "before", name, module, names));
320 }
321 let mut fuel = match (opts.fuel.get(name).copied(), budget) {
322 (Some(count), Some(left)) => Fuel::of(count.min(left)),
326 (Some(count), None) => Fuel::of(count),
327 (None, Some(left)) => Fuel::of(left),
328 (None, None) => Fuel::unlimited(),
329 };
330 let default = chosen.contains(&name);
333 for id in module.funcs() {
334 if module[id].is_declaration() {
335 continue;
336 }
337 if !opts.gates.allows(name, default, id.raw(), names.resolve(module[id].name)) {
338 continue;
342 }
343 let an = cached.entry(id).or_default();
344 let stats = pass.run(&mut module[id], an, &mut fuel);
345 let keeps = if stats.changed() { pass.preserves() } else { Preserved::ALL };
350 for broken in an.settle(&module[id], keeps, opts.verify) {
351 let func = names.resolve(module[id].name);
352 report.broke.push(format!(
353 "the {name} pass said it preserved {} of {func} and did not",
354 broken.name()
355 ));
356 }
357 if stats.changed() && opts.verify {
364 if let Err(errors) = rucc_ir::verify_func(module, &module[id], names) {
365 let func = names.resolve(module[id].name);
366 for error in errors {
367 report
368 .broke
369 .push(format!("the {name} pass left invalid IR in {func}, {error}"));
370 }
371 }
372 }
373 report.remarks.push(Remark { pass: name, func: module[id].name, stats });
376 }
377 report.spent.push((name, fuel.spent()));
378 if let Some(left) = &mut budget {
379 *left -= fuel.spent();
381 }
382 if opts.dumps.wants_after(name) {
383 report.dumps.push(dump(index, "after", name, module, names));
384 }
385 }
386 report
387}
388
389fn dump(index: usize, side: &str, name: &str, module: &Module, names: &Interner) -> Dump {
391 Dump { name: format!("{index:02}-{side}-{name}"), text: rucc_ir::print(module, names) }
392}
393
394#[must_use]
400pub fn print(opts: &Options) -> String {
401 let mut out = String::new();
402 let _ = writeln!(out, "level: {}", opts.level);
403 if let Some(count) = opts.global_fuel {
407 let _ = writeln!(out, "global fuel: {count}");
408 }
409 let passes = opts.passes();
410 if passes.is_empty() {
411 let _ = writeln!(out, "no passes");
412 return out;
413 }
414 for (index, pass) in passes.iter().enumerate() {
415 let _ = write!(out, "{}: {}, {}", index + 1, pass.name(), pass.describe());
416 if let Some(note) = opts.gates.note(pass.name()) {
419 let _ = write!(out, " [{note}]");
420 }
421 out.push('\n');
422 }
423 out
424}
425
426#[cfg(test)]
427mod tests {
428 use rucc_base::Interner;
429 use rucc_ir::{Builder, Func, Module, Opcode, Signature, Type};
430 use rucc_session::OptLevel;
431 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
432
433 use super::{Dumps, Options, for_level};
434 use crate::stats::Kind;
435 use crate::{Pass, pass};
436
437 fn module() -> (Interner, Module) {
439 let mut names = Interner::new();
440 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
441 let mut module = Module::new(names.intern("test.c"), &target);
442 let func = foldable(&mut names, "f");
443 module.add_func(func);
444 (names, module)
445 }
446
447 fn two_functions() -> (Interner, Module) {
449 let mut names = Interner::new();
450 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
451 let mut module = Module::new(names.intern("test.c"), &target);
452 for name in ["f", "g"] {
453 let func = foldable(&mut names, name);
454 module.add_func(func);
455 }
456 (names, module)
457 }
458
459 fn foldable(names: &mut Interner, name: &str) -> Func {
461 let mut func =
462 Func::new(names.intern(name), Signature::new().with_returns(&[Type::int(64)]));
463 let block = func.create_block();
464 let mut build = Builder::new(&mut func, block);
465 let narrow = build.iconst(Type::int(32), 7);
466 let wide = build.unary(Opcode::SExt, narrow, Type::int(64));
467 build.ret(&[wide]);
468 func
469 }
470
471 fn spoke_about(report: &super::Report, pass: &str, func: &str, names: &Interner) -> bool {
473 report.remarks.iter().any(|it| it.pass == pass && names.resolve(it.func) == func)
474 }
475
476 #[test]
477 fn every_pass_a_pipeline_names_is_a_pass_that_exists() {
478 for level in
479 [OptLevel::O0, OptLevel::O1, OptLevel::O2, OptLevel::O3, OptLevel::Os, OptLevel::Oz]
480 {
481 for name in for_level(level) {
482 assert!(
483 pass::find(name).is_some(),
484 "{level} names `{name}` and no pass answers to it"
485 );
486 }
487 }
488 }
489
490 #[test]
491 fn no_pipeline_names_a_pass_twice() {
492 for level in
493 [OptLevel::O0, OptLevel::O1, OptLevel::O2, OptLevel::O3, OptLevel::Os, OptLevel::Oz]
494 {
495 let names = for_level(level);
496 for (index, name) in names.iter().enumerate() {
497 assert!(!names[index + 1..].contains(name), "{level} runs `{name}` twice");
498 }
499 }
500 }
501
502 fn spent(report: &super::Report, pass: &str) -> Option<u32> {
504 report.spent.iter().find(|(name, _)| *name == pass).map(|&(_, count)| count)
505 }
506
507 fn names(opts: &Options) -> Vec<&'static str> {
509 opts.passes().into_iter().map(Pass::name).collect()
510 }
511
512 #[test]
513 fn the_level_that_optimizes_nothing_still_removes_what_nothing_reaches() {
514 assert_eq!(names(&Options::for_level(OptLevel::O0)), ["simplify-cfg"]);
517 assert!(names(&Options::for_level(OptLevel::O2)).len() > 1);
518 }
519
520 #[test]
521 fn a_pass_is_removed_by_no_and_added_by_the_bare_name_and_the_last_word_wins() {
522 let mut opts = Options::for_level(OptLevel::O2);
523 opts.toggles.push(("fold".to_owned(), false));
524 assert!(!names(&opts).contains(&"fold"), "{:?}", names(&opts));
525 opts.toggles.push(("fold".to_owned(), true));
526 assert!(names(&opts).contains(&"fold"), "{:?}", names(&opts));
527
528 let mut off = Options::for_level(OptLevel::O0);
529 off.toggles.push(("fold".to_owned(), true));
530 assert_eq!(
531 names(&off),
532 ["simplify-cfg", "fold"],
533 "a pass the level did not choose is still reachable"
534 );
535 }
536
537 #[test]
538 fn asking_for_a_pass_twice_does_not_run_it_twice() {
539 let mut opts = Options::for_level(OptLevel::O2);
540 let before = names(&opts);
541 opts.toggles.push(("fold".to_owned(), true));
542 assert_eq!(names(&opts), before);
543 }
544
545 #[test]
546 fn the_pipeline_listing_names_the_level_and_every_pass_in_order() {
547 let text = super::print(&Options::for_level(OptLevel::O2));
548 assert!(text.starts_with("level: -O2\n"), "{text}");
549 assert!(text.contains("1: fold, "), "{text}");
550 let mut none = Options::for_level(OptLevel::O0);
551 none.toggles.push(("simplify-cfg".to_owned(), false));
552 let none = super::print(&none);
553 assert!(none.contains("no passes"), "{none}");
554 }
555
556 #[test]
557 fn running_the_pipeline_changes_the_module_and_reports_what_it_spent() {
558 let (names, mut module) = module();
559 let report = super::run(&mut module, &names, &Options::for_level(OptLevel::O2));
560 assert_eq!(spent(&report, "fold"), Some(1));
565 assert_eq!(spent(&report, "dce"), Some(1));
566 assert!(report.broke.is_empty(), "{:?}", report.broke);
567 assert!(report.dumps.is_empty(), "nothing asked for a dump");
568 assert!(rucc_ir::print(&module, &names).contains("iconst.i64 7"));
569 }
570
571 #[test]
572 fn the_analyses_survive_a_pass_that_keeps_them_and_not_one_that_does_not() {
573 let mut names = Interner::new();
578 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
579 let mut module = Module::new(names.intern("test.c"), &target);
580 let mut func = Func::new(names.intern("f"), Signature::new());
581 let entry = func.create_block();
582 let dead = func.create_block();
583 let exit = func.create_block();
584 let mut build = Builder::new(&mut func, entry);
585 let never = build.iconst(Type::int(1), 0);
586 build.br_if(never, dead, &[], exit, &[]);
587 for block in [dead, exit] {
588 let mut build = Builder::new(&mut func, block);
589 build.ret(&[]);
590 }
591 module.add_func(func);
592 let report = super::run(&mut module, &names, &Options::for_level(OptLevel::O2));
593 assert_eq!(spent(&report, "simplify-cfg"), Some(1));
594 assert!(report.broke.is_empty(), "{:?}", report.broke);
595 let text = rucc_ir::print(&module, &names);
596 assert_eq!(
598 text.matches("\nblock").count(),
599 2,
600 "the block nothing reaches is still here:\n{text}"
601 );
602 }
603
604 #[test]
605 fn no_pass_that_optimizes_runs_at_no_optimization_however_much_there_is_to_do() {
606 let (names, mut module) = module();
607 let before = rucc_ir::print(&module, &names);
608 let report = super::run(&mut module, &names, &Options::for_level(OptLevel::O0));
609 assert_eq!(report.spent, vec![("simplify-cfg", 0)]);
613 assert_eq!(rucc_ir::print(&module, &names), before);
614 }
615
616 #[test]
617 fn a_gate_takes_a_pass_away_from_one_function_and_leaves_the_other_alone() {
618 let (names, mut module) = two_functions();
619 let mut opts = Options::for_level(OptLevel::O2);
620 opts.gates.add(false, "fold=g").expect("g is a function and fold is a pass");
621 let report = super::run(&mut module, &names, &opts);
622 assert!(spoke_about(&report, "fold", "f", &names));
623 assert!(!spoke_about(&report, "fold", "g", &names), "fold ran where it was gated off");
624 assert!(spoke_about(&report, "dce", "g", &names), "one pass gated off is not all of them");
625 let text = rucc_ir::print(&module, &names);
628 assert_eq!(text.matches("sext.i64").count(), 1, "{text}");
629 }
630
631 #[test]
632 fn a_function_can_be_gated_by_the_number_it_has_in_the_module() {
633 let (names, mut module) = two_functions();
634 let mut opts = Options::for_level(OptLevel::O2);
635 opts.gates.add(false, "fold=0").expect("0 is a function and fold is a pass");
636 let report = super::run(&mut module, &names, &opts);
637 assert!(!spoke_about(&report, "fold", "f", &names), "function 0 is the first one");
638 assert!(spoke_about(&report, "fold", "g", &names));
639 }
640
641 #[test]
642 fn enabling_a_pass_reaches_one_function_at_a_level_that_did_not_ask_for_it() {
643 let (names, mut module) = two_functions();
644 let mut opts = Options::for_level(OptLevel::O0);
645 opts.gates.add(true, "fold=1").expect("1 is a function and fold is a pass");
646 let running: Vec<&str> = opts.passes().into_iter().map(Pass::name).collect();
647 assert_eq!(
648 running,
649 ["simplify-cfg", "fold"],
650 "the flag has to put the pass in the pipeline"
651 );
652 let report = super::run(&mut module, &names, &opts);
653 assert!(!spoke_about(&report, "fold", "f", &names), "nothing asked for f");
654 assert!(spoke_about(&report, "fold", "g", &names));
655 let text = rucc_ir::print(&module, &names);
656 assert_eq!(text.matches("sext.i64").count(), 1, "{text}");
657 }
658
659 #[test]
660 fn a_pass_gated_off_everywhere_runs_on_nothing_and_still_says_so() {
661 let (names, mut module) = two_functions();
662 let before = rucc_ir::print(&module, &names);
663 let mut opts = Options::for_level(OptLevel::O2);
664 for pass in pass::PASSES {
665 opts.gates.add(false, pass.name()).expect("a pass in the list is a pass that exists");
666 }
667 let report = super::run(&mut module, &names, &opts);
668 assert!(report.remarks.is_empty(), "a pass that did not run has nothing to report");
669 assert_eq!(spent(&report, "fold"), Some(0), "the pass is still in the pipeline");
670 assert_eq!(rucc_ir::print(&module, &names), before);
671 }
672
673 #[test]
674 fn the_pipeline_listing_says_which_passes_a_gate_touched() {
675 let mut opts = Options::for_level(OptLevel::O2);
676 opts.gates.add(false, "fold=2-4").expect("fold is a pass");
677 let text = super::print(&opts);
678 assert!(text.contains("1: fold, "), "{text}");
679 assert!(text.contains("[off for 2-4]"), "{text}");
680 assert_eq!(text.matches('[').count(), 1, "a pass no gate mentions says nothing extra");
681 }
682
683 #[test]
684 fn every_pass_at_no_fuel_leaves_the_module_exactly_as_it_found_it() {
685 for pass in pass::PASSES {
688 let (names, mut module) = module();
689 let before = rucc_ir::print(&module, &names);
690 let mut opts = Options::for_level(OptLevel::O0);
691 opts.toggles.push(("simplify-cfg".to_owned(), false));
695 opts.toggles.push((pass.name().to_owned(), true));
696 opts.fuel.insert(pass.name().to_owned(), 0);
697 let report = super::run(&mut module, &names, &opts);
698 assert_eq!(
699 report.spent,
700 vec![(pass.name(), 0)],
701 "{} spent fuel it had none of",
702 pass.name()
703 );
704 assert_eq!(
705 rucc_ir::print(&module, &names),
706 before,
707 "{} transformed the module at fuel zero",
708 pass.name()
709 );
710 }
711 }
712
713 #[test]
714 fn fuel_is_shared_across_the_functions_of_a_module() {
715 let mut names = Interner::new();
716 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
717 let mut module = Module::new(names.intern("test.c"), &target);
718 for which in ["f", "g"] {
719 let mut func =
720 Func::new(names.intern(which), Signature::new().with_returns(&[Type::int(64)]));
721 let block = func.create_block();
722 let mut build = Builder::new(&mut func, block);
723 let narrow = build.iconst(Type::int(32), 7);
724 let wide = build.unary(Opcode::SExt, narrow, Type::int(64));
725 build.ret(&[wide]);
726 module.add_func(func);
727 }
728 let mut opts = Options::for_level(OptLevel::O2);
729 opts.fuel.insert("fold".to_owned(), 1);
730 let report = super::run(&mut module, &names, &opts);
731 assert_eq!(spent(&report, "fold"), Some(1));
734 assert_eq!(spent(&report, "dce"), Some(1));
735 let text = rucc_ir::print(&module, &names);
736 assert_eq!(text.matches("sext.i64").count(), 1, "{text}");
737 }
738
739 #[test]
740 fn global_fuel_is_spent_by_the_passes_in_order_and_the_rest_get_none() {
741 let (names, mut module) = module();
742 let mut opts = Options::for_level(OptLevel::O2);
743 opts.global_fuel = Some(1);
744 let report = super::run(&mut module, &names, &opts);
745 assert_eq!(spent(&report, "fold"), Some(1));
749 assert_eq!(spent(&report, "dce"), Some(0));
750 let text = rucc_ir::print(&module, &names);
751 assert!(text.contains("iconst.i64 7"), "{text}");
752 assert!(text.contains("iconst.i32 7"), "the orphaned constant is still there, {text}");
753 }
754
755 #[test]
756 fn a_budget_of_nothing_leaves_the_module_alone_and_still_runs_every_pass() {
757 let (names, mut module) = module();
758 let before = rucc_ir::print(&module, &names);
759 let mut opts = Options::for_level(OptLevel::O2);
760 opts.global_fuel = Some(0);
761 let report = super::run(&mut module, &names, &opts);
762 assert_eq!(rucc_ir::print(&module, &names), before);
763 assert!(report.spent.iter().all(|(_, spent)| *spent == 0), "{:?}", report.spent);
764 assert_eq!(report.spent.len(), opts.passes().len());
768 }
769
770 #[test]
771 fn the_tighter_of_the_two_limits_is_the_one_that_stops_the_pass() {
772 let (names, mut under) = module();
774 let mut opts = Options::for_level(OptLevel::O2);
775 opts.global_fuel = Some(0);
776 opts.fuel.insert("fold".to_owned(), 9);
777 assert_eq!(spent(&super::run(&mut under, &names, &opts), "fold"), Some(0));
778
779 let (names, mut over) = module();
782 let mut opts = Options::for_level(OptLevel::O2);
783 opts.global_fuel = Some(9);
784 opts.fuel.insert("fold".to_owned(), 0);
785 let report = super::run(&mut over, &names, &opts);
786 assert_eq!(spent(&report, "fold"), Some(0));
787 assert_eq!(spent(&report, "dce"), Some(0), "nothing was orphaned for it to remove");
788 }
789
790 #[test]
791 fn the_pipeline_listing_says_when_there_is_a_budget_and_says_nothing_when_there_is_not() {
792 let opts = Options::for_level(OptLevel::O2);
793 assert!(!super::print(&opts).contains("global fuel"));
794 let with = Options { global_fuel: Some(12), ..Options::for_level(OptLevel::O2) };
795 assert!(super::print(&with).contains("global fuel: 12"), "{}", super::print(&with));
796 }
797
798 #[test]
799 fn a_dump_is_taken_on_the_side_that_asked_for_it_and_not_the_other() {
800 let (names, mut module) = module();
801 let mut opts = Options::for_level(OptLevel::O2);
802 opts.dumps.add("after-fold").expect("a pass that exists");
803 let report = super::run(&mut module, &names, &opts);
804 assert_eq!(report.dumps.len(), 1);
805 assert_eq!(report.dumps[0].name, "00-after-fold");
806 assert!(report.dumps[0].text.contains("iconst.i64 7"));
807 }
808
809 #[test]
810 fn asking_for_all_dumps_gives_both_sides_of_every_pass() {
811 let (interner, mut module) = module();
812 let opts = {
813 let mut opts = Options::for_level(OptLevel::O2);
814 opts.dumps.add("all").expect("all is always a dump");
815 opts
816 };
817 let report = super::run(&mut module, &interner, &opts);
818 let taken: Vec<&str> = report.dumps.iter().map(|d| d.name.as_str()).collect();
822 let expected: Vec<String> = names(&opts)
823 .into_iter()
824 .enumerate()
825 .flat_map(|(at, name)| {
826 [format!("{at:02}-before-{name}"), format!("{at:02}-after-{name}")]
827 })
828 .collect();
829 assert_eq!(taken, expected);
830 assert!(report.dumps[0].text.contains("sext.i64"));
831 assert!(!report.dumps[1].text.contains("sext.i64"));
832 }
833
834 #[test]
835 fn every_pass_leaves_a_record_for_every_function_whether_or_not_it_had_anything_to_say() {
836 let (names, mut module) = module();
837 let opts = Options::for_level(OptLevel::O2);
838 let report = super::run(&mut module, &names, &opts);
839 let ran: Vec<&'static str> = opts.passes().into_iter().map(Pass::name).collect();
840 let seen: Vec<&'static str> = report.remarks.iter().map(|it| it.pass).collect();
845 assert_eq!(seen, ran);
846 assert!(report.remarks.iter().all(|it| names.resolve(it.func) == "f"));
847 assert!(
848 report.remarks.iter().any(|it| it.pass == "simplify" && it.stats.is_empty()),
849 "there is nothing in the fixture for the peephole to do"
850 );
851 }
852
853 #[test]
854 fn a_pass_spends_one_unit_of_fuel_for_each_rewrite_it_reports() {
855 let (names, mut module) = module();
862 let report = super::run(&mut module, &names, &Options::for_level(OptLevel::O2));
863 for (pass, spent) in &report.spent {
864 assert_eq!(
865 report.totals(pass).total(Kind::Optimized),
866 *spent,
867 "{pass} spent {spent} units of fuel and did not say on what"
868 );
869 }
870 assert!(report.spent.iter().any(|(_, spent)| *spent > 0), "nothing happened at all");
871 }
872
873 #[test]
874 fn what_the_passes_said_is_what_opt_info_prints() {
875 let (names, mut module) = module();
876 let report = super::run(&mut module, &names, &Options::for_level(OptLevel::O2));
877 let text = crate::optinfo::render("t.c", &report, &names, crate::Wants::all());
878 assert!(
879 text.contains("t.c: f: optimized: integer instruction folded to a constant (1) [fold]"),
880 "{text}"
881 );
882 assert!(
883 text.contains(
884 "t.c: f: optimized: instruction with no effects and no users removed (1) [dce]"
885 ),
886 "{text}"
887 );
888 let mut misses = crate::Wants::none();
891 misses.add("missed").expect("that kind exists");
892 assert_eq!(crate::optinfo::render("t.c", &report, &names, misses), "");
893 }
894
895 #[test]
896 fn the_verifier_says_which_function_it_refused_and_leaves_the_others_out_of_it() {
897 let mut names = Interner::new();
902 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
903 let mut module = Module::new(names.intern("test.c"), &target);
904 module.add_func(foldable(&mut names, "f"));
905 let mut g = foldable(&mut names, "g");
906 let stranded = g.create_block();
907 let mut build = Builder::new(&mut g, stranded);
908 let seven = build.iconst(Type::int(64), 7);
909 build.ret(&[seven]);
910 module.add_func(g);
911
912 let mut opts = Options::for_level(OptLevel::O0);
915 opts.toggles.push(("simplify-cfg".to_owned(), false));
916 opts.toggles.push(("fold".to_owned(), true));
917 opts.verify = true;
918 let report = super::run(&mut module, &names, &opts);
919
920 assert_eq!(report.broke.len(), 1, "{:?}", report.broke);
921 let complaint = &report.broke[0];
922 assert!(complaint.starts_with("the fold pass left invalid IR in g,"), "{complaint}");
923 assert!(complaint.contains("this block is not reachable"), "{complaint}");
924 }
925
926 #[test]
927 fn a_function_a_pass_did_not_change_is_not_verified_after_it() {
928 let mut names = Interner::new();
933 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
934 let mut module = Module::new(names.intern("test.c"), &target);
935 let mut f = Func::new(names.intern("f"), Signature::new().with_returns(&[Type::int(64)]));
936 for _ in 0..2 {
937 let block = f.create_block();
938 let mut build = Builder::new(&mut f, block);
939 let seven = build.iconst(Type::int(64), 7);
940 build.ret(&[seven]);
941 }
942 module.add_func(f);
943 module.add_func(foldable(&mut names, "g"));
944
945 let mut opts = Options::for_level(OptLevel::O0);
946 opts.toggles.push(("simplify-cfg".to_owned(), false));
947 opts.toggles.push(("fold".to_owned(), true));
948 opts.verify = true;
949 let report = super::run(&mut module, &names, &opts);
950
951 assert!(report.broke.is_empty(), "{:?}", report.broke);
952 assert!(spoke_about(&report, "fold", "f", &names));
955 }
956
957 #[test]
958 fn a_dump_of_a_pass_that_does_not_exist_is_refused_rather_than_ignored() {
959 let mut dumps = Dumps::default();
960 assert!(dumps.add("after-no-such-pass").is_err());
961 assert!(dumps.add("sideways-fold").is_err());
962 assert!(dumps.add("fold").is_err());
963 assert!(dumps.is_empty());
964 }
965}