Rust · 3908 bytes Raw Blame History
1 use std::collections::BTreeSet;
2 use std::path::PathBuf;
3
4 use armfortas::driver::OptLevel;
5 use armfortas::testing::{capture_from_path, CaptureRequest, CapturedStage, Stage};
6
7 fn fixture(name: &str) -> PathBuf {
8 let path = PathBuf::from("test_programs").join(name);
9 assert!(path.exists(), "missing test fixture {}", path.display());
10 path
11 }
12
13 fn capture_text(request: CaptureRequest, stage: Stage) -> String {
14 let result = capture_from_path(&request).expect("capture should succeed");
15 match result.get(stage) {
16 Some(CapturedStage::Text(text)) => text.clone(),
17 Some(CapturedStage::Run(_)) => panic!("expected text stage for {}", stage.as_str()),
18 None => panic!("missing requested stage {}", stage.as_str()),
19 }
20 }
21
22 fn function_section<'a>(ir: &'a str, name: &str) -> &'a str {
23 let header = format!(" func @{}", name);
24 let start = ir
25 .find(&header)
26 .unwrap_or_else(|| panic!("missing function section for {}", name));
27 let rest = &ir[start..];
28 let end = rest
29 .find("\n }\n")
30 .unwrap_or_else(|| panic!("unterminated function section for {}", name));
31 &rest[..end + "\n }".len()]
32 }
33
34 fn param_count(func_section: &str) -> usize {
35 let header = func_section.lines().next().expect("function header");
36 let inside = header
37 .split_once('(')
38 .and_then(|(_, tail)| tail.split_once(") ->"))
39 .map(|(params, _)| params.trim())
40 .expect("function header params");
41 if inside.is_empty() {
42 0
43 } else {
44 inside.split(", ").count()
45 }
46 }
47
48 fn call_arg_counts_for(func_section: &str, callee_marker: &str) -> Vec<usize> {
49 func_section
50 .lines()
51 .filter_map(|line| {
52 let line = line.trim();
53 let call = line.find(&format!("call {}", callee_marker))?;
54 let inside = line[call..].split_once('(')?.1.split_once(')')?.0.trim();
55 Some(if inside.is_empty() {
56 0
57 } else {
58 inside.split(", ").count()
59 })
60 })
61 .collect()
62 }
63
64 #[test]
65 fn o2_elides_dead_dummy_arg_from_recursive_internal_helper() {
66 let source = fixture("ipo_dead_arg.f90");
67
68 let raw_ir = capture_text(
69 CaptureRequest {
70 input: source.clone(),
71 requested: BTreeSet::from([Stage::Ir]),
72 opt_level: OptLevel::O0,
73 },
74 Stage::Ir,
75 );
76 let opt_ir = capture_text(
77 CaptureRequest {
78 input: source,
79 requested: BTreeSet::from([Stage::OptIr]),
80 opt_level: OptLevel::O2,
81 },
82 Stage::OptIr,
83 );
84
85 let raw_main = function_section(&raw_ir, "__prog_ipo_dead_arg");
86 let raw_helper = function_section(&raw_ir, "helper");
87 let opt_main = function_section(&opt_ir, "__prog_ipo_dead_arg");
88 let opt_helper = function_section(&opt_ir, "helper");
89
90 assert_eq!(
91 param_count(raw_helper),
92 3,
93 "raw helper should keep all dummy args:\n{}",
94 raw_helper
95 );
96 assert_eq!(
97 param_count(opt_helper),
98 2,
99 "optimized helper should drop the dead dummy arg:\n{}",
100 opt_helper
101 );
102
103 let raw_call_counts = [
104 call_arg_counts_for(raw_main, "@helper"),
105 call_arg_counts_for(raw_helper, "@helper"),
106 ]
107 .concat();
108 // dead-arg-elim rewrites @helper in-place (no clone, no rename)
109 // so the optimized IR still calls @helper — with one fewer
110 // argument per site.
111 let opt_call_counts = [
112 call_arg_counts_for(opt_main, "@helper"),
113 call_arg_counts_for(opt_helper, "@helper"),
114 ]
115 .concat();
116
117 assert_eq!(
118 raw_call_counts,
119 vec![3, 3],
120 "raw IR should pass all three args at both call sites"
121 );
122 assert_eq!(
123 opt_call_counts,
124 vec![2, 2],
125 "optimized IR should trim the dead arg at both call sites"
126 );
127 }
128