| 1 | module x64 |
| 2 | |
| 3 | import os |
| 4 | import v2.mir |
| 5 | |
| 6 | struct X64BackendCompileFailure { |
| 7 | stdout string |
| 8 | stderr string |
| 9 | exit_code int |
| 10 | } |
| 11 | |
| 12 | fn run_x64_backend_compile_failure(name string, source string) X64BackendCompileFailure { |
| 13 | tmp_dir := os.join_path(os.vtmp_dir(), 'v_x64_backend_diagnostics_${name}_${os.getpid()}') |
| 14 | os.mkdir_all(tmp_dir) or { panic(err) } |
| 15 | defer { |
| 16 | os.rmdir_all(tmp_dir) or {} |
| 17 | } |
| 18 | source_path := os.join_path(tmp_dir, '${name}.v') |
| 19 | bin_path := os.join_path(tmp_dir, name) |
| 20 | os.write_file(source_path, source) or { panic(err) } |
| 21 | vexe := x64_backend_diagnostic_vexe_command_path() |
| 22 | mut build := os.new_process(vexe) |
| 23 | defer { |
| 24 | build.close() |
| 25 | } |
| 26 | build.set_args(['-v2', '-b', 'x64', source_path, '-o', bin_path]) |
| 27 | build.set_redirect_stdio() |
| 28 | build.run() |
| 29 | build.wait() |
| 30 | stdout := x64_backend_diagnostic_stdout(build.stdout_slurp()) |
| 31 | stderr := build.stderr_slurp() |
| 32 | return X64BackendCompileFailure{ |
| 33 | stdout: stdout |
| 34 | stderr: stderr |
| 35 | exit_code: build.code |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | fn x64_backend_diagnostic_vexe_command_path() string { |
| 40 | vexe := os.getenv_opt('VEXE') or { @VEXE } |
| 41 | if os.is_abs_path(vexe) || !os.exists(vexe) { |
| 42 | return vexe |
| 43 | } |
| 44 | return os.abs_path(vexe) |
| 45 | } |
| 46 | |
| 47 | fn x64_backend_diagnostic_stdout(stdout string) string { |
| 48 | mut lines := []string{} |
| 49 | for line in stdout.split_into_lines() { |
| 50 | if line.starts_with(' * ') { |
| 51 | continue |
| 52 | } |
| 53 | lines << line |
| 54 | } |
| 55 | return lines.join('\n') |
| 56 | } |
| 57 | |
| 58 | fn assert_x64_user_visible_compile_failure(failure X64BackendCompileFailure, expected_message string) { |
| 59 | assert failure.exit_code != 0, failure.stdout + failure.stderr |
| 60 | assert failure.stdout == '', failure.stdout |
| 61 | assert failure.stderr.contains(expected_message), failure.stderr |
| 62 | assert failure.stderr.contains(x64_backend_limitation_hint), failure.stderr |
| 63 | assert !failure.stderr.contains('Link failed:'), failure.stderr |
| 64 | assert !failure.stderr.contains('V panic:'), failure.stderr |
| 65 | assert !failure.stderr.contains('Backtrace'), failure.stderr |
| 66 | assert_x64_message_avoids_old_wording(failure.stderr) |
| 67 | } |
| 68 | |
| 69 | fn assert_x64_clean_user_diagnostic_message(msg string) { |
| 70 | assert msg.starts_with('x64: unsupported backend feature: '), msg |
| 71 | assert !msg.contains('Link failed:'), msg |
| 72 | assert !msg.contains('V panic:'), msg |
| 73 | assert !msg.contains('Backtrace'), msg |
| 74 | assert_x64_message_avoids_old_wording(msg) |
| 75 | } |
| 76 | |
| 77 | fn assert_x64_message_avoids_old_wording(msg string) { |
| 78 | assert !msg.contains('reachable helper'), msg |
| 79 | assert !msg.contains('stdout/stderr path'), msg |
| 80 | assert !msg.contains('must be compiled'), msg |
| 81 | assert !msg.contains('lowered before linking'), msg |
| 82 | assert !msg.contains('before external linking'), msg |
| 83 | assert !msg.contains('cannot resolve C stdio symbol'), msg |
| 84 | assert !msg.contains('C FILE streams'), msg |
| 85 | } |
| 86 | |
| 87 | fn assert_x64_message_mentions_only_target_linker(format ObjectFormat, msg string) { |
| 88 | match format { |
| 89 | .elf { |
| 90 | assert msg.contains('ELF linker'), msg |
| 91 | assert !msg.contains('Mach-O'), msg |
| 92 | assert !msg.contains('PE linker'), msg |
| 93 | assert !msg.contains('Windows'), msg |
| 94 | assert !msg.contains('Kernel32'), msg |
| 95 | assert !msg.contains('macOS'), msg |
| 96 | } |
| 97 | .macho { |
| 98 | assert msg.contains('Mach-O linker'), msg |
| 99 | assert !msg.contains('ELF linker'), msg |
| 100 | assert !msg.contains('PE linker'), msg |
| 101 | assert !msg.contains('Windows'), msg |
| 102 | assert !msg.contains('Kernel32'), msg |
| 103 | assert !msg.contains('Linux'), msg |
| 104 | } |
| 105 | .coff { |
| 106 | assert msg.contains('PE linker'), msg |
| 107 | assert !msg.contains('ELF linker'), msg |
| 108 | assert !msg.contains('Mach-O'), msg |
| 109 | assert !msg.contains('Linux'), msg |
| 110 | assert !msg.contains('macOS'), msg |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | fn test_x64_user_visible_stderr_reports_clean_codegen_abi_unsupported_without_link_noise() { |
| 116 | $if windows { |
| 117 | println('skipping ${@FN}: SysV codegen diagnostic is not available on Windows') |
| 118 | } $else $if x64 { |
| 119 | failure := run_x64_backend_compile_failure('clean_codegen_abi_unsupported', 'module main |
| 120 | |
| 121 | fn many(a0 f64, a1 f64, a2 f64, a3 f64, a4 f64, a5 f64, a6 f64, a7 f64, a8 f64) f64 { |
| 122 | return a8 |
| 123 | } |
| 124 | |
| 125 | fn main() { |
| 126 | _ = many(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0) |
| 127 | } |
| 128 | ') |
| 129 | assert_x64_user_visible_compile_failure(failure, |
| 130 | 'x64: unsupported backend feature: stack-passed float parameter') |
| 131 | assert !failure.stderr.contains('Windows'), failure.stderr |
| 132 | assert !failure.stderr.contains('Kernel32'), failure.stderr |
| 133 | assert !failure.stderr.contains('Mach-O'), failure.stderr |
| 134 | assert !failure.stderr.contains('PE linker'), failure.stderr |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | fn test_x64_user_visible_stderr_reports_captured_fn_literal_unsupported() { |
| 139 | $if x64 { |
| 140 | failure := run_x64_backend_compile_failure('captured_fn_literal_unsupported', 'module main |
| 141 | |
| 142 | fn make_delta(delta int) fn (int) int { |
| 143 | return fn [delta] (value int) int { |
| 144 | return value + delta |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | fn main() { |
| 149 | f := make_delta(10) |
| 150 | println(f(1)) |
| 151 | } |
| 152 | ') |
| 153 | assert_x64_user_visible_compile_failure(failure, 'x64: unsupported backend feature: ') |
| 154 | assert failure.stderr.contains('native x64 cannot lower captured function literal'), failure.stderr |
| 155 | |
| 156 | assert failure.stderr.contains('closure environments are not implemented yet'), failure.stderr |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | fn test_x64_unsupported_backend_feature_message_is_normalized() { |
| 161 | assert x64_unsupported_backend_feature_message('stack-passed float parameter') == 'x64: unsupported backend feature: stack-passed float parameter' |
| 162 | assert x64_unsupported_backend_feature_message('backend feature: Windows argument lowering') == 'x64: unsupported backend feature: Windows argument lowering' |
| 163 | assert x64_unsupported_backend_feature_message('backend feature: SysV direct aggregate call result with MEMORY eightbyte classes is not implemented yet') == 'x64: unsupported backend feature: SysV direct aggregate call result with MEMORY eightbyte classes is not implemented yet' |
| 164 | } |
| 165 | |
| 166 | fn test_x64_user_visible_linker_diagnostic_message_for_generic_external_by_format() { |
| 167 | for format in [ObjectFormat.elf, .macho, .coff] { |
| 168 | msg := x64_unresolved_external_symbol_message(format, 'v_missing_runtime_symbol', |
| 169 | 'referenced from test relocation') |
| 170 | |
| 171 | assert_x64_clean_user_diagnostic_message(msg) |
| 172 | assert_x64_message_mentions_only_target_linker(format, msg) |
| 173 | assert msg.contains('cannot resolve external symbol `v_missing_runtime_symbol` yet'), msg |
| 174 | assert msg.contains('referenced from test relocation'), msg |
| 175 | assert !msg.contains('C stdio/file-descriptor symbol'), msg |
| 176 | assert !msg.contains('Windows'), msg |
| 177 | assert !msg.contains('Kernel32'), msg |
| 178 | assert !msg.contains('C FILE/stdio'), msg |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | fn test_x64_user_visible_crt_stdio_and_fd_diagnostic_is_windows_only() { |
| 183 | for symbol_name in ['stderr', 'fread', '_get_osfhandle', '_open_osfhandle'] { |
| 184 | for format in [ObjectFormat.elf, .macho] { |
| 185 | msg := x64_unresolved_external_symbol_message(format, symbol_name, |
| 186 | 'referenced from test relocation') |
| 187 | |
| 188 | assert_x64_clean_user_diagnostic_message(msg) |
| 189 | assert_x64_message_mentions_only_target_linker(format, msg) |
| 190 | assert msg.contains('cannot resolve external symbol `${symbol_name}` yet'), msg |
| 191 | assert !msg.contains('C stdio/file-descriptor symbol'), msg |
| 192 | assert !msg.contains('Kernel32'), msg |
| 193 | assert !msg.contains('C FILE/stdio'), msg |
| 194 | } |
| 195 | |
| 196 | pe_msg := x64_unresolved_external_symbol_message(.coff, symbol_name, |
| 197 | 'referenced from test relocation') |
| 198 | assert_x64_clean_user_diagnostic_message(pe_msg) |
| 199 | assert_x64_message_mentions_only_target_linker(.coff, pe_msg) |
| 200 | assert pe_msg.contains('cannot resolve C stdio/file-descriptor symbol `${symbol_name}`'), pe_msg |
| 201 | assert pe_msg.contains('Windows x64 native backend uses Kernel32 handles'), pe_msg |
| 202 | assert pe_msg.contains('Kernel32 handles'), pe_msg |
| 203 | assert pe_msg.contains('C FILE/stdio calls'), pe_msg |
| 204 | assert !pe_msg.contains('Linux'), pe_msg |
| 205 | assert !pe_msg.contains('macOS'), pe_msg |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | fn test_x64_user_visible_linker_diagnostic_message_for_missing_runtime_helper_by_format() { |
| 210 | for format in [ObjectFormat.elf, .macho, .coff] { |
| 211 | msg := x64_unresolved_external_symbol_message(format, 'builtin__Map_string_int__keys', |
| 212 | 'referenced from test relocation') |
| 213 | |
| 214 | assert_x64_clean_user_diagnostic_message(msg) |
| 215 | assert_x64_message_mentions_only_target_linker(format, msg) |
| 216 | assert msg.contains('cannot resolve V runtime helper `builtin__Map_string_int__keys`'), msg |
| 217 | assert msg.contains('native x64 backend does not implement this feature for this target yet'), msg |
| 218 | assert msg.contains('referenced from test relocation'), msg |
| 219 | assert !msg.contains('C stdio/file-descriptor symbol'), msg |
| 220 | assert !msg.contains('Kernel32'), msg |
| 221 | assert !msg.contains('C FILE/stdio'), msg |
| 222 | assert !msg.contains('not imported'), msg |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | fn test_x64_gen_detects_missing_runtime_helper_while_preparing_output() { |
| 227 | for format in [ObjectFormat.elf, .macho, .coff] { |
| 228 | mut mod := mir.Module{} |
| 229 | mut gen := Gen.new_with_format(&mod, format) |
| 230 | match format { |
| 231 | .elf { |
| 232 | gen.elf.add_undefined('builtin__Map_string_int__keys') |
| 233 | } |
| 234 | .macho { |
| 235 | gen.macho.add_undefined('_builtin__Map_string_int__keys') |
| 236 | } |
| 237 | .coff { |
| 238 | gen.coff.add_undefined('builtin__Map_string_int__keys') |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | msg := gen.unsupported_external_symbol_message() or { |
| 243 | assert false |
| 244 | continue |
| 245 | } |
| 246 | assert_x64_clean_user_diagnostic_message(msg) |
| 247 | assert_x64_message_mentions_only_target_linker(format, msg) |
| 248 | assert msg.contains(x64_linker_name(format)), msg |
| 249 | assert msg.contains('builtin__Map_string_int__keys'), msg |
| 250 | assert msg.contains('cannot resolve V runtime helper `builtin__Map_string_int__keys`'), msg |
| 251 | assert msg.contains('native x64 backend does not implement this feature for this target yet'), msg |
| 252 | assert msg.contains('needed while preparing native x64 output'), msg |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | fn test_x64_gen_keeps_linker_resolved_external_symbols_for_external_linkers() { |
| 257 | for format in [ObjectFormat.elf, .macho, .coff] { |
| 258 | mut mod := mir.Module{} |
| 259 | mut gen := Gen.new_with_format(&mod, format) |
| 260 | match format { |
| 261 | .elf { |
| 262 | gen.elf.add_undefined('calloc') |
| 263 | } |
| 264 | .macho { |
| 265 | gen.macho.add_undefined('_calloc') |
| 266 | } |
| 267 | .coff { |
| 268 | gen.coff.add_undefined('calloc') |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | if msg := gen.unsupported_external_symbol_message() { |
| 273 | assert false, msg |
| 274 | } |
| 275 | } |
| 276 | } |
| 277 | |