v / vlib / v2 / ssa / runtime_symbols_test.v
602 lines · 532 sloc · 15.33 KB · ddb021b9866c3b4523b746fa2f4c16a594f8bd89
Raw
1// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module ssa
5
6import os
7import v2.ast
8import v2.parser
9import v2.pref as vpref
10import v2.token
11import v2.transformer
12import v2.types
13
14fn build_ssa_for_runtime_symbol_test(code string) &Module {
15 return build_ssa_for_runtime_symbol_target_test(code, 'linux')
16}
17
18fn build_ssa_for_runtime_symbol_target_test(code string, target_os string) &Module {
19 tmp_file := os.join_path(os.vtmp_dir(), 'v2_ssa_runtime_symbols_${os.getpid()}.v')
20 os.write_file(tmp_file, code) or { panic('failed to write temp file') }
21 defer {
22 os.rm(tmp_file) or {}
23 }
24 mut prefs := &vpref.Preferences{
25 backend: .x64
26 no_parallel: true
27 }
28 prefs.target_os = target_os
29 mut file_set := token.FileSet.new()
30 mut par := parser.Parser.new(prefs)
31 files := par.parse_files([tmp_file], mut file_set)
32 mut env := types.Environment.new()
33 mut checker := types.Checker.new(prefs, file_set, env)
34 checker.check_files(files)
35 mut trans := transformer.Transformer.new_with_pref(env, prefs)
36 transformed := trans.transform_files(files)
37 mut ssa_mod := Module.new('runtime_symbols')
38 mut b := Builder.new_with_env(ssa_mod, env)
39 b.target_os = target_os
40 b.build_all(transformed)
41 return ssa_mod
42}
43
44fn build_ssa_for_runtime_symbol_target_flat_test(code string, target_os string) &Module {
45 tmp_file := os.join_path(os.vtmp_dir(), 'v2_ssa_runtime_symbols_flat_${os.getpid()}.v')
46 os.write_file(tmp_file, code) or { panic('failed to write temp file') }
47 defer {
48 os.rm(tmp_file) or {}
49 }
50 mut prefs := &vpref.Preferences{
51 backend: .x64
52 no_parallel: true
53 }
54 prefs.target_os = target_os
55 mut file_set := token.FileSet.new()
56 mut par := parser.Parser.new(prefs)
57 files := par.parse_files([tmp_file], mut file_set)
58 mut env := types.Environment.new()
59 mut checker := types.Checker.new(prefs, file_set, env)
60 checker.check_files(files)
61 mut trans := transformer.Transformer.new_with_pref(env, prefs)
62 flat := ast.flatten_files(files)
63 transformed_flat, _ := trans.transform_files_to_flat(&flat, files)
64 mut ssa_mod := Module.new('runtime_symbols_flat')
65 mut b := Builder.new_with_env(ssa_mod, env)
66 b.target_os = target_os
67 b.build_all_from_flat(&transformed_flat)
68 return ssa_mod
69}
70
71fn stdio_loaded_external_symbol_names(m &Module) []string {
72 mut names := []string{}
73 for val in m.values {
74 if val.kind != .instruction {
75 continue
76 }
77 instr := m.instrs[val.index]
78 if instr.op != .load || instr.operands.len != 1 {
79 continue
80 }
81 operand := instr.operands[0]
82 if operand <= 0 || operand >= m.values.len {
83 continue
84 }
85 global := m.values[operand]
86 if global.kind == .global
87 && global.name in ['stdout', 'stderr', 'stdin', '__stdoutp', '__stderrp', '__stdinp'] {
88 assert global.index >= 0 && global.index < m.globals.len
89 assert m.globals[global.index].linkage == .external
90 names << global.name
91 }
92 }
93 return names
94}
95
96fn called_function_names(m &Module) []string {
97 mut names := []string{}
98 for val in m.values {
99 if val.kind != .instruction {
100 continue
101 }
102 instr := m.instrs[val.index]
103 if instr.op != .call || instr.operands.len == 0 {
104 continue
105 }
106 callee := instr.operands[0]
107 if callee <= 0 || callee >= m.values.len {
108 continue
109 }
110 callee_val := m.values[callee]
111 if callee_val.kind == .func_ref {
112 names << callee_val.name
113 }
114 }
115 return names
116}
117
118fn global_value_names(m &Module) []string {
119 return m.values.filter(it.kind == .global).map(it.name)
120}
121
122fn store_destination_call_names(m &Module) []string {
123 mut names := []string{}
124 for val in m.values {
125 if val.kind != .instruction {
126 continue
127 }
128 instr := m.instrs[val.index]
129 if instr.op != .store || instr.operands.len < 2 {
130 continue
131 }
132 dest := instr.operands[1]
133 if dest <= 0 || dest >= m.values.len {
134 continue
135 }
136 dest_val := m.values[dest]
137 if dest_val.kind != .instruction {
138 continue
139 }
140 dest_instr := m.instrs[dest_val.index]
141 if dest_instr.op != .call || dest_instr.operands.len == 0 {
142 continue
143 }
144 callee := dest_instr.operands[0]
145 if callee <= 0 || callee >= m.values.len {
146 continue
147 }
148 callee_val := m.values[callee]
149 if callee_val.kind == .func_ref {
150 names << callee_val.name
151 }
152 }
153 return names
154}
155
156fn store_destination_global_names(m &Module) []string {
157 mut names := []string{}
158 for val in m.values {
159 if val.kind != .instruction {
160 continue
161 }
162 instr := m.instrs[val.index]
163 if instr.op != .store || instr.operands.len < 2 {
164 continue
165 }
166 dest := instr.operands[1]
167 if dest <= 0 || dest >= m.values.len {
168 continue
169 }
170 dest_val := m.values[dest]
171 if dest_val.kind == .global {
172 names << dest_val.name
173 }
174 }
175 return names
176}
177
178fn c_errno_write_test_code() string {
179 return '
180module main
181
182fn reset_errno() {
183 C.errno = 0
184}
185
186fn bump_errno() {
187 C.errno += 1
188}
189
190fn main() {
191 reset_errno()
192 bump_errno()
193}
194'
195}
196
197fn test_c_float_epsilon_macros_are_ssa_constants_not_external_globals() {
198 m := build_ssa_for_runtime_symbol_test('
199module main
200
201fn seek_set() int {
202 return C.SEEK_SET
203}
204
205fn flt_epsilon() f32 {
206 return C.FLT_EPSILON
207}
208
209fn dbl_epsilon() f64 {
210 return C.DBL_EPSILON
211}
212
213fn main() {
214 _ := seek_set()
215 _ := flt_epsilon()
216 _ := dbl_epsilon()
217}
218')
219 mut has_seek_set_const := false
220 mut has_flt_epsilon_const := false
221 mut has_dbl_epsilon_const := false
222 for val in m.values {
223 if val.kind == .global {
224 assert val.name != 'SEEK_SET'
225 assert val.name != 'FLT_EPSILON'
226 assert val.name != 'DBL_EPSILON'
227 }
228 if val.kind != .constant {
229 continue
230 }
231 if val.name == '1.19209290e-07' {
232 typ := m.type_store.types[val.typ]
233 assert typ.kind == .float_t
234 assert typ.width == 32
235 has_flt_epsilon_const = true
236 }
237 if val.name == '2.2204460492503131e-16' {
238 typ := m.type_store.types[val.typ]
239 assert typ.kind == .float_t
240 assert typ.width == 64
241 has_dbl_epsilon_const = true
242 }
243 if val.name == '0' {
244 typ := m.type_store.types[val.typ]
245 if typ.kind == .int_t && typ.width == 32 {
246 has_seek_set_const = true
247 }
248 }
249 }
250 assert has_seek_set_const
251 assert has_flt_epsilon_const
252 assert has_dbl_epsilon_const
253}
254
255fn windows_std_handle_macros_code() string {
256 return '
257module main
258
259fn input_handle() u32 {
260 return C.STD_INPUT_HANDLE
261}
262
263fn output_handle() u32 {
264 return C.STD_OUTPUT_HANDLE
265}
266
267fn error_handle() u32 {
268 return C.STD_ERROR_HANDLE
269}
270
271fn main() {
272 _ := input_handle()
273 _ := output_handle()
274 _ := error_handle()
275}
276'
277}
278
279fn windows_invalid_handle_value_macro_code() string {
280 return '
281module main
282
283fn invalid_handle() voidptr {
284 return C.INVALID_HANDLE_VALUE
285}
286
287fn main() {
288 _ := invalid_handle()
289}
290'
291}
292
293fn assert_windows_std_handle_macros_are_ssa_constants_not_external_globals(m &Module) {
294 global_names := global_value_names(m)
295 expected_consts := {
296 'STD_INPUT_HANDLE': '4294967286'
297 'STD_OUTPUT_HANDLE': '4294967285'
298 'STD_ERROR_HANDLE': '4294967284'
299 }
300 mut seen_consts := map[string]bool{}
301 for name, const_name in expected_consts {
302 assert name !in global_names
303 seen_consts[const_name] = false
304 }
305 for val in m.values {
306 if val.kind != .constant || val.name !in seen_consts {
307 continue
308 }
309 typ := m.type_store.types[val.typ]
310 assert typ.kind == .int_t
311 assert typ.width == 32
312 assert typ.is_unsigned
313 seen_consts[val.name] = true
314 }
315 for _, const_name in expected_consts {
316 assert seen_consts[const_name]
317 }
318}
319
320fn test_windows_std_handle_macros_are_ssa_constants_not_external_globals() {
321 m := build_ssa_for_runtime_symbol_target_test(windows_std_handle_macros_code(), 'windows')
322 assert_windows_std_handle_macros_are_ssa_constants_not_external_globals(m)
323}
324
325fn test_windows_std_handle_macros_are_flat_ssa_constants_not_external_globals() {
326 m := build_ssa_for_runtime_symbol_target_flat_test(windows_std_handle_macros_code(), 'windows')
327 assert_windows_std_handle_macros_are_ssa_constants_not_external_globals(m)
328}
329
330fn assert_windows_invalid_handle_value_macro_is_pointer_sized_constant_not_external_global(m &Module) {
331 global_names := global_value_names(m)
332 assert 'INVALID_HANDLE_VALUE' !in global_names
333
334 mut has_invalid_handle_const := false
335 for val in m.values {
336 if val.kind != .constant || val.name != '-1' {
337 continue
338 }
339 typ := m.type_store.types[val.typ]
340 if typ.kind == .ptr_t {
341 has_invalid_handle_const = true
342 }
343 }
344 assert has_invalid_handle_const
345}
346
347fn test_windows_invalid_handle_value_macro_is_pointer_sized_constant_not_external_global() {
348 m := build_ssa_for_runtime_symbol_target_test(windows_invalid_handle_value_macro_code(),
349 'windows')
350 assert_windows_invalid_handle_value_macro_is_pointer_sized_constant_not_external_global(m)
351}
352
353fn test_windows_invalid_handle_value_macro_is_flat_pointer_sized_constant_not_external_global() {
354 m := build_ssa_for_runtime_symbol_target_flat_test(windows_invalid_handle_value_macro_code(),
355 'windows')
356 assert_windows_invalid_handle_value_macro_is_pointer_sized_constant_not_external_global(m)
357}
358
359fn test_c_stdio_globals_are_loaded_on_non_macos_targets() {
360 m := build_ssa_for_runtime_symbol_target_test('
361module main
362
363fn main() {
364 _ := C.stdout
365 _ := C.stderr
366 _ := C.stdin
367}
368',
369 'linux')
370 load_names := stdio_loaded_external_symbol_names(m)
371 global_names := global_value_names(m)
372
373 assert 'stdout' in load_names
374 assert 'stderr' in load_names
375 assert 'stdin' in load_names
376 assert '__stdoutp' !in load_names
377 assert '__stderrp' !in load_names
378 assert '__stdinp' !in load_names
379 assert 'C.stdout' !in global_names
380 assert 'C.stderr' !in global_names
381 assert 'C.stdin' !in global_names
382}
383
384fn test_c_errno_uses_errno_location_on_linux_targets() {
385 m := build_ssa_for_runtime_symbol_target_test('
386module main
387
388fn read_errno() int {
389 return C.errno
390}
391
392fn main() {
393 _ := read_errno()
394}
395',
396 'linux')
397 call_names := called_function_names(m)
398 global_names := global_value_names(m)
399
400 assert '__errno_location' in call_names
401 assert '__error' !in call_names
402 assert 'errno' !in global_names
403}
404
405fn test_c_errno_flat_reads_use_errno_location_on_linux_targets() {
406 m := build_ssa_for_runtime_symbol_target_flat_test('
407module main
408
409fn read_errno() int {
410 return C.errno
411}
412
413fn main() {
414 _ := read_errno()
415}
416',
417 'linux')
418 call_names := called_function_names(m)
419 global_names := global_value_names(m)
420
421 assert '__errno_location' in call_names
422 assert '__error' !in call_names
423 assert 'errno' !in global_names
424}
425
426fn test_c_errno_writes_use_errno_location_on_linux_targets() {
427 m := build_ssa_for_runtime_symbol_target_test(c_errno_write_test_code(), 'linux')
428 call_names := called_function_names(m)
429 store_call_names := store_destination_call_names(m)
430 global_names := global_value_names(m)
431
432 assert call_names.filter(it == '__errno_location').len == 2
433 assert store_call_names.filter(it == '__errno_location').len == 2
434 assert '__error' !in call_names
435 assert 'errno' !in global_names
436 assert 'C.errno' !in global_names
437}
438
439fn test_c_errno_flat_writes_use_errno_location_on_linux_targets() {
440 m := build_ssa_for_runtime_symbol_target_flat_test(c_errno_write_test_code(), 'linux')
441 call_names := called_function_names(m)
442 store_call_names := store_destination_call_names(m)
443 global_names := global_value_names(m)
444
445 assert call_names.filter(it == '__errno_location').len == 2
446 assert store_call_names.filter(it == '__errno_location').len == 2
447 assert '__error' !in call_names
448 assert 'errno' !in global_names
449 assert 'C.errno' !in global_names
450}
451
452fn test_c_errno_uses_error_on_macos_targets() {
453 m := build_ssa_for_runtime_symbol_target_test('
454module main
455
456fn read_errno() int {
457 return C.errno
458}
459
460fn main() {
461 _ := read_errno()
462}
463',
464 'macos')
465 call_names := called_function_names(m)
466 global_names := global_value_names(m)
467
468 assert '__error' in call_names
469 assert '__errno_location' !in call_names
470 assert 'errno' !in global_names
471}
472
473fn test_c_errno_writes_use_error_on_macos_targets() {
474 m := build_ssa_for_runtime_symbol_target_test(c_errno_write_test_code(), 'macos')
475 call_names := called_function_names(m)
476 store_call_names := store_destination_call_names(m)
477 global_names := global_value_names(m)
478
479 assert call_names.filter(it == '__error').len == 2
480 assert store_call_names.filter(it == '__error').len == 2
481 assert '__errno_location' !in call_names
482 assert 'errno' !in global_names
483 assert 'C.errno' !in global_names
484}
485
486fn test_c_errno_flat_writes_use_error_on_macos_targets() {
487 m := build_ssa_for_runtime_symbol_target_flat_test(c_errno_write_test_code(), 'macos')
488 call_names := called_function_names(m)
489 store_call_names := store_destination_call_names(m)
490 global_names := global_value_names(m)
491
492 assert call_names.filter(it == '__error').len == 2
493 assert store_call_names.filter(it == '__error').len == 2
494 assert '__errno_location' !in call_names
495 assert 'errno' !in global_names
496 assert 'C.errno' !in global_names
497}
498
499fn test_c_errno_uses_errno_on_windows_targets() {
500 m := build_ssa_for_runtime_symbol_target_test('
501module main
502
503fn read_errno() int {
504 return C.errno
505}
506
507fn main() {
508 _ := read_errno()
509}
510',
511 'windows')
512 call_names := called_function_names(m)
513 global_names := global_value_names(m)
514
515 assert '_errno' in call_names
516 assert '__errno_location' !in call_names
517 assert '__error' !in call_names
518 assert 'errno' !in global_names
519 assert 'C.errno' !in global_names
520}
521
522fn test_c_errno_writes_use_errno_on_windows_targets() {
523 m := build_ssa_for_runtime_symbol_target_test(c_errno_write_test_code(), 'windows')
524 call_names := called_function_names(m)
525 store_call_names := store_destination_call_names(m)
526 store_global_names := store_destination_global_names(m)
527 global_names := global_value_names(m)
528
529 assert call_names.filter(it == '_errno').len == 2
530 assert store_call_names.filter(it == '_errno').len == 2
531 assert '__errno_location' !in call_names
532 assert '__error' !in call_names
533 assert store_global_names.filter(it == 'errno').len == 0
534 assert 'errno' !in global_names
535 assert 'C.errno' !in global_names
536}
537
538fn test_c_errno_flat_writes_use_errno_on_windows_targets() {
539 m := build_ssa_for_runtime_symbol_target_flat_test(c_errno_write_test_code(), 'windows')
540 call_names := called_function_names(m)
541 store_call_names := store_destination_call_names(m)
542 store_global_names := store_destination_global_names(m)
543 global_names := global_value_names(m)
544
545 assert call_names.filter(it == '_errno').len == 2
546 assert store_call_names.filter(it == '_errno').len == 2
547 assert '__errno_location' !in call_names
548 assert '__error' !in call_names
549 assert store_global_names.filter(it == 'errno').len == 0
550 assert 'errno' !in global_names
551 assert 'C.errno' !in global_names
552}
553
554fn test_c_stdio_globals_are_loaded_on_windows_targets() {
555 m := build_ssa_for_runtime_symbol_target_test('
556module main
557
558fn main() {
559 _ := C.stdout
560 _ := C.stderr
561 _ := C.stdin
562}
563',
564 'windows')
565 load_names := stdio_loaded_external_symbol_names(m)
566 global_names := global_value_names(m)
567
568 assert 'stdout' in load_names
569 assert 'stderr' in load_names
570 assert 'stdin' in load_names
571 assert '__stdoutp' !in load_names
572 assert '__stderrp' !in load_names
573 assert '__stdinp' !in load_names
574 assert 'C.stdout' !in global_names
575 assert 'C.stderr' !in global_names
576 assert 'C.stdin' !in global_names
577}
578
579fn test_c_stdio_globals_are_loaded_from_macos_symbols_on_macos_targets() {
580 m := build_ssa_for_runtime_symbol_target_test('
581module main
582
583fn main() {
584 _ := C.stdout
585 _ := C.stderr
586 _ := C.stdin
587}
588',
589 'macos')
590 load_names := stdio_loaded_external_symbol_names(m)
591 global_names := global_value_names(m)
592
593 assert '__stdoutp' in load_names
594 assert '__stderrp' in load_names
595 assert '__stdinp' in load_names
596 assert 'stdout' !in load_names
597 assert 'stderr' !in load_names
598 assert 'stdin' !in load_names
599 assert 'C.stdout' !in global_names
600 assert 'C.stderr' !in global_names
601 assert 'C.stdin' !in global_names
602}
603