Rust · 4091 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, RunCapture, Stage};
6
7 fn fixture(name: &str) -> PathBuf {
8 let path = PathBuf::from("tests/fixtures").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 capture_run(request: CaptureRequest) -> RunCapture {
23 let result = capture_from_path(&request).expect("capture should succeed");
24 match result.get(Stage::Run) {
25 Some(CapturedStage::Run(run)) => run.clone(),
26 Some(CapturedStage::Text(_)) => panic!("expected run stage"),
27 None => panic!("missing requested stage {}", Stage::Run.as_str()),
28 }
29 }
30
31 fn function_section<'a>(ir: &'a str, name: &str) -> &'a str {
32 let header = format!(" func @{}", name);
33 let start = ir
34 .find(&header)
35 .unwrap_or_else(|| panic!("missing function section for {}", name));
36 let rest = &ir[start..];
37 let end = rest
38 .find("\n }\n")
39 .unwrap_or_else(|| panic!("unterminated function section for {}", name));
40 &rest[..end + "\n }".len()]
41 }
42
43 fn parse_last_int(stdout: &str) -> i32 {
44 stdout
45 .split_whitespace()
46 .last()
47 .unwrap_or_else(|| panic!("missing integer in stdout:\n{}", stdout))
48 .parse()
49 .unwrap_or_else(|_| panic!("stdout did not end in an integer:\n{}", stdout))
50 }
51
52 #[test]
53 fn ofast_reassociates_float_constant_chain_but_o3_stays_strict() {
54 let source = fixture("ofast_fast_math_reassoc.f90");
55
56 let o3_ir = capture_text(
57 CaptureRequest {
58 input: source.clone(),
59 requested: BTreeSet::from([Stage::OptIr]),
60 opt_level: OptLevel::O3,
61 },
62 Stage::OptIr,
63 );
64 let ofast_ir = capture_text(
65 CaptureRequest {
66 input: source.clone(),
67 requested: BTreeSet::from([Stage::OptIr]),
68 opt_level: OptLevel::Ofast,
69 },
70 Stage::OptIr,
71 );
72
73 let o3_main = function_section(&o3_ir, "__prog_ofast_fast_math_reassoc");
74 let ofast_main = function_section(&ofast_ir, "__prog_ofast_fast_math_reassoc");
75
76 assert!(
77 o3_main.contains("fadd") && o3_main.contains("fsub"),
78 "O3 should keep the strict float chain intact:\n{}",
79 o3_main
80 );
81 assert!(
82 !ofast_main.contains("fsub"),
83 "Ofast should reassociate away the subtract in the float chain:\n{}",
84 ofast_main
85 );
86
87 let o3_run = capture_run(CaptureRequest {
88 input: source.clone(),
89 requested: BTreeSet::from([Stage::Run]),
90 opt_level: OptLevel::O3,
91 });
92 let ofast_run = capture_run(CaptureRequest {
93 input: source.clone(),
94 requested: BTreeSet::from([Stage::Run, Stage::Obj]),
95 opt_level: OptLevel::Ofast,
96 });
97 let ofast_obj_a = capture_text(
98 CaptureRequest {
99 input: source.clone(),
100 requested: BTreeSet::from([Stage::Obj]),
101 opt_level: OptLevel::Ofast,
102 },
103 Stage::Obj,
104 );
105 let ofast_obj_b = capture_text(
106 CaptureRequest {
107 input: source,
108 requested: BTreeSet::from([Stage::Obj]),
109 opt_level: OptLevel::Ofast,
110 },
111 Stage::Obj,
112 );
113
114 assert_eq!(
115 parse_last_int(&o3_run.stdout),
116 0,
117 "strict O3 run should keep IEEE-style rounding loss"
118 );
119 assert_eq!(
120 parse_last_int(&ofast_run.stdout),
121 1,
122 "Ofast run should expose fast-math reassociation"
123 );
124 assert_eq!(
125 ofast_obj_a, ofast_obj_b,
126 "Ofast object snapshot should stay deterministic"
127 );
128 }
129