| 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 |