Rust · 3360 bytes Raw Blame History
1 use std::path::PathBuf;
2
3 use armfortas::driver::OptLevel;
4 use armfortas::testing::{capture_from_path, CaptureRequest, CapturedStage, Stage};
5
6 fn fixture(name: &str) -> PathBuf {
7 let path = PathBuf::from("tests/fixtures").join(name);
8 assert!(path.exists(), "missing test fixture {}", path.display());
9 path
10 }
11
12 fn capture_text(request: CaptureRequest, stage: Stage) -> String {
13 let result = capture_from_path(&request).expect("capture should succeed");
14 match result.get(stage) {
15 Some(CapturedStage::Text(text)) => text.clone(),
16 Some(CapturedStage::Run(_)) => panic!("expected text stage for {}", stage.as_str()),
17 None => panic!("missing requested stage {}", stage.as_str()),
18 }
19 }
20
21 fn suspicious_cross_class_moves(text: &str) -> Vec<&str> {
22 text.lines()
23 .filter(|line| {
24 let trimmed = line.trim();
25 trimmed.starts_with("movreg x") && trimmed.contains(", d")
26 || trimmed.starts_with("movreg d") && trimmed.contains(", x")
27 || trimmed.starts_with("movreg w") && trimmed.contains(", s")
28 || trimmed.starts_with("movreg s") && trimmed.contains(", w")
29 })
30 .collect()
31 }
32
33 fn move_contexts(text: &str, needles: &[&str]) -> Vec<String> {
34 let lines: Vec<_> = text.lines().collect();
35 let mut contexts = Vec::new();
36 for (idx, line) in lines.iter().enumerate() {
37 let trimmed = line.trim();
38 if needles.iter().any(|needle| trimmed.contains(needle)) {
39 let func_start = (0..=idx)
40 .rev()
41 .find(|cursor| {
42 let candidate = lines[*cursor].trim();
43 candidate.starts_with("function ") || candidate.ends_with(':')
44 })
45 .unwrap_or(idx);
46 let start = idx.saturating_sub(2);
47 let end = (idx + 3).min(lines.len());
48 contexts.push(format!(
49 "{}\n{}",
50 lines[func_start],
51 lines[start..end].join("\n")
52 ));
53 }
54 }
55 contexts
56 }
57
58 #[test]
59 fn fortbite_complex_scalar_division_keeps_pointer_arithmetic_out_of_fp_regs() {
60 let regalloc = capture_text(
61 CaptureRequest {
62 input: fixture("fortbite_complex_scalar_division.f90"),
63 requested: [Stage::Regalloc].into_iter().collect(),
64 opt_level: OptLevel::O0,
65 },
66 Stage::Regalloc,
67 );
68 let asm = capture_text(
69 CaptureRequest {
70 input: fixture("fortbite_complex_scalar_division.f90"),
71 requested: [Stage::Asm].into_iter().collect(),
72 opt_level: OptLevel::O0,
73 },
74 Stage::Asm,
75 );
76 let regalloc_bad = suspicious_cross_class_moves(&regalloc);
77 let asm_bad = suspicious_cross_class_moves(&asm);
78 let regalloc_contexts =
79 move_contexts(&regalloc, &["movreg x", "movreg d", "movreg w", "movreg s"]);
80 let asm_contexts = move_contexts(&asm, &["mov x", "mov d", "mov w", "mov s"]);
81
82 assert!(
83 regalloc_bad.is_empty() && asm_bad.is_empty(),
84 "suspicious cross-class moves remained in backend capture\nregalloc:\n{}\nregalloc-contexts:\n{}\nasm:\n{}\nasm-contexts:\n{}",
85 regalloc_bad.join("\n"),
86 regalloc_contexts.join("\n---\n"),
87 asm_bad.join("\n"),
88 asm_contexts.join("\n---\n"),
89 );
90 }
91