Rust · 4863 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 function_sections(ir: &str) -> Vec<&str> {
35 ir.match_indices(" func @")
36 .map(|(idx, _)| {
37 let rest = &ir[idx..];
38 let end = rest
39 .find("\n }\n")
40 .unwrap_or_else(|| panic!("unterminated function section in:\n{}", rest));
41 &rest[..end + "\n }".len()]
42 })
43 .collect()
44 }
45
46 fn function_name<'a>(func_section: &'a str) -> &'a str {
47 let header = func_section.lines().next().expect("function header").trim();
48 let rest = header
49 .strip_prefix("func @")
50 .expect("function header prefix");
51 let end = rest
52 .find(|ch: char| ch == ' ' || ch == '(')
53 .unwrap_or(rest.len());
54 &rest[..end]
55 }
56
57 fn param_count(func_section: &str) -> usize {
58 let header = func_section.lines().next().expect("function header");
59 let inside = header
60 .split_once('(')
61 .and_then(|(_, tail)| tail.split_once(") ->"))
62 .map(|(params, _)| params.trim())
63 .expect("function header params");
64 if inside.is_empty() {
65 0
66 } else {
67 inside.split(", ").count()
68 }
69 }
70
71 fn call_arg_counts_for(func_section: &str, callee_name: &str) -> Vec<usize> {
72 func_section
73 .lines()
74 .filter_map(|line| {
75 let line = line.trim();
76 let call = line.find(&format!("call @{}", callee_name))?;
77 let inside = line[call..].split_once('(')?.1.split_once(')')?.0.trim();
78 Some(if inside.is_empty() {
79 0
80 } else {
81 inside.split(", ").count()
82 })
83 })
84 .collect()
85 }
86
87 #[test]
88 fn o2_elides_dead_dummy_arg_from_recursive_internal_helper() {
89 let source = fixture("ipo_dead_arg.f90");
90
91 let raw_ir = capture_text(
92 CaptureRequest {
93 input: source.clone(),
94 requested: BTreeSet::from([Stage::Ir]),
95 opt_level: OptLevel::O0,
96 },
97 Stage::Ir,
98 );
99 let opt_ir = capture_text(
100 CaptureRequest {
101 input: source,
102 requested: BTreeSet::from([Stage::OptIr]),
103 opt_level: OptLevel::O2,
104 },
105 Stage::OptIr,
106 );
107
108 let raw_sections = function_sections(&raw_ir);
109 assert_eq!(
110 raw_sections.len(),
111 2,
112 "raw IR should include the program body plus one contained helper:\n{}",
113 raw_ir
114 );
115 let raw_main = raw_sections[0];
116 let raw_helper = raw_sections[1];
117 let raw_helper_name = function_name(raw_helper);
118 let opt_main = function_section(&opt_ir, "__prog_ipo_dead_arg");
119 let opt_helper = function_section(&opt_ir, raw_helper_name);
120
121 assert_eq!(
122 param_count(raw_helper),
123 3,
124 "raw helper should keep all dummy args:\n{}",
125 raw_helper
126 );
127 assert_eq!(
128 param_count(opt_helper),
129 2,
130 "optimized helper should drop the dead dummy arg:\n{}",
131 opt_helper
132 );
133
134 let raw_call_counts = [
135 call_arg_counts_for(raw_main, raw_helper_name),
136 call_arg_counts_for(raw_helper, raw_helper_name),
137 ]
138 .concat();
139 // dead-arg-elim rewrites @helper in-place (no clone, no rename)
140 // so the optimized IR still calls @helper — with one fewer
141 // argument per site.
142 let opt_call_counts = [
143 call_arg_counts_for(opt_main, raw_helper_name),
144 call_arg_counts_for(opt_helper, raw_helper_name),
145 ]
146 .concat();
147
148 assert_eq!(
149 raw_call_counts,
150 vec![3, 3],
151 "raw IR should pass all three args at both call sites"
152 );
153 assert_eq!(
154 opt_call_counts,
155 vec![2, 2],
156 "optimized IR should trim the dead arg at both call sites"
157 );
158 }
159