Rust · 10713 bytes Raw Blame History
1 use std::path::PathBuf;
2 use std::process::Command;
3 use std::sync::atomic::{AtomicUsize, Ordering};
4
5 static NEXT_TEMP_ID: AtomicUsize = AtomicUsize::new(0);
6
7 fn compiler(name: &str) -> PathBuf {
8 if let Some(path) = std::env::var_os(format!("CARGO_BIN_EXE_{}", name)) {
9 return PathBuf::from(path);
10 }
11 let candidate = PathBuf::from("target/debug").join(name);
12 if candidate.exists() {
13 return std::fs::canonicalize(candidate).expect("cannot canonicalize debug compiler path");
14 }
15 let candidate = PathBuf::from("target/release").join(name);
16 if candidate.exists() {
17 return std::fs::canonicalize(candidate)
18 .expect("cannot canonicalize release compiler path");
19 }
20 panic!(
21 "compiler binary '{}' not built — run `cargo build --bins` first",
22 name
23 );
24 }
25
26 fn unique_path(stem: &str, ext: &str) -> PathBuf {
27 let pid = std::process::id();
28 let id = NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed);
29 std::env::temp_dir().join(format!("afs_regalloc_{}_{}_{}.{}", stem, pid, id, ext))
30 }
31
32 fn unique_dir(stem: &str) -> PathBuf {
33 let dir = unique_path(stem, "dir");
34 std::fs::create_dir_all(&dir).expect("cannot create regalloc test directory");
35 dir
36 }
37
38 fn write_program_in(dir: &std::path::Path, name: &str, text: &str) -> PathBuf {
39 let path = dir.join(name);
40 std::fs::write(&path, text).expect("cannot write regalloc test source");
41 path
42 }
43
44 fn compile_c_object(source: &std::path::Path, output: &std::path::Path) {
45 let result = Command::new("clang")
46 .args([
47 "-arch",
48 "arm64",
49 "-c",
50 source.to_str().unwrap(),
51 "-o",
52 output.to_str().unwrap(),
53 ])
54 .output()
55 .expect("failed to spawn clang");
56 assert!(
57 result.status.success(),
58 "clang failed for {}: {}",
59 source.display(),
60 String::from_utf8_lossy(&result.stderr)
61 );
62 }
63
64 fn compile_fortran_object(source: &std::path::Path, output: &std::path::Path, opt: &str) {
65 let result = Command::new(compiler("armfortas"))
66 .args([
67 "-c",
68 source.to_str().unwrap(),
69 opt,
70 "-o",
71 output.to_str().unwrap(),
72 ])
73 .output()
74 .expect("failed to spawn armfortas object compile");
75 assert!(
76 result.status.success(),
77 "armfortas failed for {} at {}: {}",
78 source.display(),
79 opt,
80 String::from_utf8_lossy(&result.stderr)
81 );
82 }
83
84 fn link_program(objects: &[&std::path::Path], output: &std::path::Path) {
85 let mut cmd = Command::new(compiler("armfortas"));
86 for object in objects {
87 cmd.arg(object);
88 }
89 let result = cmd
90 .args(["-o", output.to_str().unwrap()])
91 .output()
92 .expect("failed to spawn armfortas link");
93 assert!(
94 result.status.success(),
95 "link failed: {}",
96 String::from_utf8_lossy(&result.stderr)
97 );
98 }
99
100 fn compile_and_run_with_c(
101 stem: &str,
102 c_name: &str,
103 c_text: &str,
104 source_text: &str,
105 opt: &str,
106 expected: &str,
107 ) {
108 let dir = unique_dir(stem);
109 let c_src = write_program_in(&dir, c_name, c_text);
110 let c_obj = dir.join(format!("{}.o", c_name.trim_end_matches(".c")));
111 compile_c_object(&c_src, &c_obj);
112
113 let src = write_program_in(&dir, "main.f90", source_text);
114 let f_obj = dir.join("main.o");
115 compile_fortran_object(&src, &f_obj, opt);
116
117 let exe = dir.join(format!("{}.bin", stem));
118 link_program(&[&f_obj, &c_obj], &exe);
119 let run = Command::new(&exe)
120 .output()
121 .expect("regalloc runtime failed to spawn");
122 assert!(
123 run.status.success(),
124 "regalloc runtime failed for {} at {}: status={:?}\nstdout:\n{}\nstderr:\n{}",
125 stem,
126 opt,
127 run.status,
128 String::from_utf8_lossy(&run.stdout),
129 String::from_utf8_lossy(&run.stderr)
130 );
131 let stdout = String::from_utf8_lossy(&run.stdout);
132 assert!(
133 stdout.contains(expected),
134 "unexpected regalloc runtime output for {} at {}: {}",
135 stem,
136 opt,
137 stdout
138 );
139
140 let _ = std::fs::remove_dir_all(&dir);
141 }
142
143 #[test]
144 fn gp_values_live_across_call_pressure_survive() {
145 let c_text = "#include <stdint.h>\n\nint check_gp_live(int32_t a1, int32_t a2, int32_t a3, int32_t a4, int32_t a5, int32_t a6, int32_t a7, int32_t a8, int32_t a9, int32_t a10, int32_t a11, int32_t a12, int32_t a13, int32_t a14, int32_t a15, int32_t a16) {\n return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 + a14 + a15 + a16;\n}\n";
146 let src = "program p\n use iso_c_binding, only: c_int\n implicit none\n interface\n function check_gp_live(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) result(v) bind(C, name='check_gp_live')\n import :: c_int\n integer(c_int), value :: a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16\n integer(c_int) :: v\n end function check_gp_live\n end interface\n integer(c_int) :: total\n integer(c_int) :: a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16\n a1 = 1\n a2 = 2\n a3 = 3\n a4 = 4\n a5 = 5\n a6 = 6\n a7 = 7\n a8 = 8\n a9 = 9\n a10 = 10\n a11 = 11\n a12 = 12\n a13 = 13\n a14 = 14\n a15 = 15\n a16 = 16\n total = check_gp_live(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16)\n total = total + a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 + a14 + a15 + a16\n if (total /= 272_c_int) error stop 1\n print *, total\nend program\n";
147
148 for opt in ["-O0", "-O2"] {
149 compile_and_run_with_c(
150 "gp_live_across_call",
151 "check_gp_live.c",
152 c_text,
153 src,
154 opt,
155 "272",
156 );
157 }
158 }
159
160 #[test]
161 fn fp_values_live_across_call_pressure_survive() {
162 let c_text = "#include <stdint.h>\n\nint check_fp_live(double d1, double d2, double d3, double d4, double d5, double d6, double d7, double d8, double d9, double d10, double d11, double d12) {\n return (int)d1 + (int)d2 + (int)d3 + (int)d4 + (int)d5 + (int)d6 + (int)d7 + (int)d8 + (int)d9 + (int)d10 + (int)d11 + (int)d12;\n}\n";
163 let src = "program p\n use iso_c_binding, only: c_int, c_double\n implicit none\n interface\n function check_fp_live(d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12) result(v) bind(C, name='check_fp_live')\n import :: c_int, c_double\n real(c_double), value :: d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12\n integer(c_int) :: v\n end function check_fp_live\n end interface\n integer(c_int) :: total\n real(c_double) :: d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12\n d1 = 1.0d0\n d2 = 2.0d0\n d3 = 3.0d0\n d4 = 4.0d0\n d5 = 5.0d0\n d6 = 6.0d0\n d7 = 7.0d0\n d8 = 8.0d0\n d9 = 9.0d0\n d10 = 10.0d0\n d11 = 11.0d0\n d12 = 12.0d0\n total = check_fp_live(d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12)\n total = total + int(d1) + int(d2) + int(d3) + int(d4) + int(d5) + int(d6) + int(d7) + int(d8) + int(d9) + int(d10) + int(d11) + int(d12)\n if (total /= 156_c_int) error stop 1\n print *, total\nend program\n";
164
165 for opt in ["-O0", "-O2"] {
166 compile_and_run_with_c(
167 "fp_live_across_call",
168 "check_fp_live.c",
169 c_text,
170 src,
171 opt,
172 "156",
173 );
174 }
175 }
176
177 #[test]
178 fn mixed_gp_fp_values_live_across_nested_call_pressure_survive() {
179 let c_text = "#include <stdint.h>\n\nint check_mixed_live(int32_t a1, int32_t a2, int32_t a3, int32_t a4, int32_t a5, int32_t a6, int32_t a7, int32_t a8, int32_t a9, int32_t a10, int32_t a11, int32_t a12, double d1, double d2, double d3, double d4, double d5, double d6, double d7, double d8, double d9, double d10) {\n return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + (int)d1 + (int)d2 + (int)d3 + (int)d4 + (int)d5 + (int)d6 + (int)d7 + (int)d8 + (int)d9 + (int)d10;\n}\n";
180 let src = "program p\n use iso_c_binding, only: c_int, c_double\n implicit none\n interface\n function check_mixed_live(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10) result(v) bind(C, name='check_mixed_live')\n import :: c_int, c_double\n integer(c_int), value :: a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12\n real(c_double), value :: d1, d2, d3, d4, d5, d6, d7, d8, d9, d10\n integer(c_int) :: v\n end function check_mixed_live\n end interface\n integer(c_int) :: total\n integer(c_int) :: a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12\n real(c_double) :: d1, d2, d3, d4, d5, d6, d7, d8, d9, d10\n a1 = 1\n a2 = 2\n a3 = 3\n a4 = 4\n a5 = 5\n a6 = 6\n a7 = 7\n a8 = 8\n a9 = 9\n a10 = 10\n a11 = 11\n a12 = 12\n d1 = 1.0d0\n d2 = 2.0d0\n d3 = 3.0d0\n d4 = 4.0d0\n d5 = 5.0d0\n d6 = 6.0d0\n d7 = 7.0d0\n d8 = 8.0d0\n d9 = 9.0d0\n d10 = 10.0d0\n total = check_mixed_live(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10)\n total = total + a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + int(d1) + int(d2) + int(d3) + int(d4) + int(d5) + int(d6) + int(d7) + int(d8) + int(d9) + int(d10)\n if (total /= 266_c_int) error stop 1\n print *, total\nend program\n";
181
182 for opt in ["-O0", "-O2"] {
183 compile_and_run_with_c(
184 "mixed_live_across_call",
185 "check_mixed_live.c",
186 c_text,
187 src,
188 opt,
189 "266",
190 );
191 }
192 }
193
194 #[test]
195 fn f32_stack_args_pack_without_8_byte_gaps() {
196 let c_text = "#include <stdint.h>\n\nint check_f32_stack(float a1, float a2, float a3, float a4, float a5, float a6, float a7, float a8, float a9, float a10) {\n return (int)a9 + (int)a10;\n}\n";
197 let src = "program p\n use iso_c_binding, only: c_int, c_float\n implicit none\n interface\n function check_f32_stack(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) result(v) bind(C, name='check_f32_stack')\n import :: c_int, c_float\n real(c_float), value :: a1, a2, a3, a4, a5, a6, a7, a8, a9, a10\n integer(c_int) :: v\n end function check_f32_stack\n end interface\n integer(c_int) :: total\n total = check_f32_stack(1.0_c_float, 2.0_c_float, 3.0_c_float, 4.0_c_float, 5.0_c_float, 6.0_c_float, 7.0_c_float, 8.0_c_float, 9.0_c_float, 10.0_c_float)\n if (total /= 19_c_int) error stop 1\n print *, total\nend program\n";
198
199 for opt in ["-O0", "-O2"] {
200 compile_and_run_with_c(
201 "f32_stack_dense",
202 "check_f32_stack.c",
203 c_text,
204 src,
205 opt,
206 "19",
207 );
208 }
209 }
210