v4 / vlib / v / gen / c / cmain.v
502 lines · 473 sloc · 16.88 KB · cd213a17c4963eb35f9c52706c0d8731a89eee0c
Raw
1module c
2
3import v.util
4import v.ast
5import strings
6
7pub const reset_dbg_line = '#line 999999999'
8
9pub fn (mut g Gen) gen_c_main() {
10 if !g.has_main {
11 return
12 }
13 if g.pref.is_liveshared {
14 return
15 }
16 if g.pref.is_o {
17 // no main in .o files
18 return
19 }
20 if 'no_main' in g.pref.compile_defines {
21 return
22 }
23 g.out.writeln('')
24 main_fn_start_pos := g.out.len
25
26 is_sokol := 'sokol' in g.table.imports
27 if (g.pref.os == .android && g.pref.is_apk) || (g.pref.os == .ios && is_sokol) {
28 g.gen_c_android_sokol_main()
29 } else {
30 g.gen_c_main_header()
31 g.writeln('\tmain__main();')
32 g.gen_c_main_footer()
33 if g.pref.printfn_list.len > 0 && 'main' in g.pref.printfn_list {
34 println(g.out.after(main_fn_start_pos))
35 }
36 }
37}
38
39fn (mut g Gen) gen_vlines_reset() {
40 if g.pref.is_vlines {
41 // At this point, the v files are transpiled.
42 // The rest is auto generated code, which will not have
43 // different .v source file/line numbers.
44 g.vlines_path = util.vlines_escape_path(g.pref.out_name_c, g.pref.ccompiler)
45 g.writeln2('', '// Reset the C file/line numbers')
46 g.writeln2('${reset_dbg_line} "${g.vlines_path}"', '')
47 }
48}
49
50fn (mut g Gen) gen_windows_stdio_setup(force_console bool) {
51 g.writeln('\tBOOL con_valid = FALSE;')
52 if force_console {
53 g.writeln('\tcon_valid = AllocConsole();')
54 } else {
55 g.writeln('\tcon_valid = AttachConsole(ATTACH_PARENT_PROCESS);')
56 }
57 g.writeln('\tFILE* res_fp = 0;')
58 g.writeln('\terrno_t err;')
59 g.writeln('\tif (con_valid) {')
60 g.writeln('\t\terr = freopen_s(&res_fp, "CON", "w", stdout);')
61 g.writeln('\t\terr = freopen_s(&res_fp, "CON", "w", stderr);')
62 g.writeln('\t} else {')
63 g.writeln('\t\terr = freopen_s(&res_fp, "NUL", "w", stdout);')
64 g.writeln('\t\terr = freopen_s(&res_fp, "NUL", "w", stderr);')
65 g.writeln('\t}')
66 g.writeln('\t(void)err;')
67}
68
69pub fn fix_reset_dbg_line(src strings.Builder, out_file string) strings.Builder {
70 util.timing_start(@FN)
71 defer {
72 util.timing_measure(@FN)
73 }
74 // Note: using src.index() + a line counting loop + src.replace() here is slower,
75 // since it has to iterate over pretty much the entire src string several times.
76 // The loop below, does it just once, combining counting the lines, and finding the reset line:
77 mut dbg_reset_line_idx := 0
78 mut lines := 2
79 for idx, ob in src {
80 if ob == `\n` {
81 lines++
82 if unsafe { vmemcmp(&u8(src.data) + idx + 1, reset_dbg_line.str, reset_dbg_line.len) } == 0 {
83 dbg_reset_line_idx = idx + 1
84 break
85 }
86 }
87 }
88 // find the position of the "..\..\..\src.tmp.c":
89 mut first_quote_idx := 0
90 for idx := dbg_reset_line_idx; idx < src.len; idx++ {
91 if unsafe { &u8(src.data)[idx] } == `"` {
92 first_quote_idx = idx
93 break
94 }
95 }
96 // replace the reset line with the fixed line counter, keeping everything
97 // before and after it unchanged:
98 mut sb := strings.new_builder(src.len)
99 unsafe {
100 sb.write_ptr(&u8(src.data), dbg_reset_line_idx)
101 sb.write_string('#line ')
102 sb.write_decimal(lines)
103 sb.write_ptr(&u8(src.data) + first_quote_idx - 1, src.len - first_quote_idx)
104 }
105 $if trace_reset_dbg_line ? {
106 eprintln('> reset_dbg_line: ${out_file}:${lines} | first_quote_idx: ${first_quote_idx} | src.len: ${src.len} | sb.len: ${sb.len} | sb.cap: ${sb.cap}')
107 }
108 return sb
109}
110
111fn (mut g Gen) gen_c_main_function_only_header() {
112 if g.pref.cmain != '' {
113 g.writeln('int ${g.pref.cmain}(int ___argc, char** ___argv){')
114 return
115 }
116 if g.pref.os == .windows {
117 if g.is_gui_app() {
118 $if msvc {
119 // This is kinda bad but I dont see a way that is better
120 g.writeln('#pragma comment(linker, "/SUBSYSTEM:WINDOWS")')
121 }
122 // GUI application
123 g.writeln('int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance, LPWSTR cmd_line, int show_cmd){')
124 g.writeln('\tLPWSTR full_cmd_line = GetCommandLineW(); // Note: do not use cmd_line')
125 g.writeln('\ttypedef LPWSTR*(WINAPI *cmd_line_to_argv)(LPCWSTR, int*);')
126 g.writeln('\tHMODULE shell32_module = LoadLibrary(L"shell32.dll");')
127 g.writeln('\tcmd_line_to_argv CommandLineToArgvW = (cmd_line_to_argv)GetProcAddress(shell32_module, "CommandLineToArgvW");')
128 g.writeln('\tint ___argc;')
129 g.writeln('\twchar_t** ___argv = CommandLineToArgvW(full_cmd_line, &___argc);')
130 g.gen_windows_stdio_setup(g.force_main_console)
131
132 return
133 }
134 // Console application
135 g.writeln('int wmain(int ___argc, wchar_t* ___argv[], wchar_t* ___envp[]){')
136 return
137 }
138 g.writeln('int main(int ___argc, char** ___argv){')
139}
140
141fn (mut g Gen) gen_c_main_function_header() {
142 g.gen_c_main_function_only_header()
143 g.gen_c_main_trace_calls_hook()
144 if !g.pref.no_builtin {
145 if _ := g.table.global_scope.find_global('g_main_argc') {
146 g.writeln('\tg_main_argc = ___argc;')
147 }
148 if _ := g.table.global_scope.find_global('g_main_argv') {
149 g.writeln('\tg_main_argv = ___argv;')
150 }
151 }
152}
153
154fn (mut g Gen) gen_boehm_gc_init() {
155 g.writeln('#if defined(_VGCBOEHM)')
156 if g.pref.gc_mode == .boehm_leak {
157 g.writeln('\tGC_set_find_leak(1);')
158 }
159 if g.pref.os == .linux && !g.pref.no_builtin {
160 g.writeln('\tbool __v_gc_debugger_workaround = builtin__gc_prepare_for_debugger_init();')
161 }
162 g.writeln('\tGC_set_pages_executable(0);')
163 if g.pref.gc_mode in [.boehm_full_opt, .boehm_incr_opt] {
164 // Let the heap grow to roughly the live set before collecting, instead of
165 // keeping it small. A smaller divisor means rarer stop-the-world pauses;
166 // with thread-local allocation those pauses (not the allocator lock) are
167 // what serializes worker threads on allocation-heavy multithreaded servers
168 // (issue #27555). Emitted before GC_INIT(), so a user `GC_FREE_SPACE_DIVISOR`
169 // env var still wins and memory-constrained programs can raise it again.
170 g.writeln('\tGC_set_free_space_divisor(1);')
171 }
172 if g.pref.use_coroutines {
173 g.writeln('\tGC_allow_register_threads();')
174 }
175 g.writeln('\tGC_INIT();')
176 // Register V's array data header displacement so Boehm always recognises
177 // the interior pointer kept in `array.data` (which is offset by
178 // `array_data_header_size()` bytes from the GC allocation start).
179 g.writeln('\tGC_register_displacement(sizeof(void*));')
180 if g.pref.os == .linux && !g.pref.no_builtin {
181 g.writeln('\tbuiltin__gc_restore_roots_after_debugger_init(__v_gc_debugger_workaround);')
182 }
183 if g.pref.gc_mode in [.boehm_incr, .boehm_incr_opt] {
184 g.writeln('\tGC_enable_incremental();')
185 }
186 g.writeln('#endif')
187}
188
189fn (mut g Gen) gen_shared_library_boehm_init() {
190 if g.pref.gc_mode !in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] {
191 return
192 }
193 g.writeln('#if defined(_VGCBOEHM)')
194 // macOS hardened runtime denies PROT_READ|PROT_WRITE|PROT_EXEC mappings from
195 // non-codesigned dylibs, so libgc must not request executable pages. The
196 // equivalent restriction applies to Windows DLLs.
197 g.writeln('\tGC_set_pages_executable(0);')
198 if g.pref.gc_mode in [.boehm_full_opt, .boehm_incr_opt] {
199 // See gen_boehm_gc_init: rarer stop-the-world pauses for the opt modes,
200 // overridable via the `GC_FREE_SPACE_DIVISOR` env var (issue #27555).
201 // Only tune when this library is the one bringing up the collector. If the
202 // host process already initialized Boehm, the local GC_INIT() below is a
203 // no-op that will not re-read the env var, so setting the divisor here would
204 // silently override the host's process-wide value (and its env override).
205 g.writeln('\tif (!GC_is_init_called()) {')
206 g.writeln('\t\tGC_set_free_space_divisor(1);')
207 g.writeln('\t}')
208 }
209 g.writeln('\tGC_INIT();')
210 g.writeln('\tGC_register_displacement(sizeof(void*));')
211 g.writeln('#endif')
212}
213
214fn (g &Gen) has_user_defined_windows_dll_main() bool {
215 return 'DllMain' in g.export_funcs
216}
217
218fn (mut g Gen) gen_c_main_header() {
219 g.gen_c_main_function_header()
220 if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] {
221 g.gen_boehm_gc_init()
222 }
223 if g.pref.gc_mode == .vgc {
224 g.writeln('\tbuiltin__vgc_init();')
225 }
226 if !g.pref.no_builtin {
227 g.writeln('\t_vinit(___argc, (voidptr)___argv);')
228 }
229 g.gen_c_main_profile_hook()
230 if g.pref.is_livemain {
231 g.generate_hotcode_reloading_main_caller()
232 }
233}
234
235pub fn (mut g Gen) gen_c_main_footer() {
236 if !g.pref.no_builtin {
237 g.writeln('\t_vcleanup();')
238 }
239 g.writeln2('\treturn 0;', '}')
240}
241
242pub fn (mut g Gen) gen_c_android_sokol_main() {
243 // Weave autofree into sokol lifecycle callback(s)
244 if g.is_autofree {
245 g.writeln('// Wrapping cleanup/free callbacks for sokol to include _vcleanup()
246void (*_vsokol_user_cleanup_ptr)(void);
247void (*_vsokol_user_cleanup_cb_ptr)(void *);
248
249void (_vsokol_cleanup_cb)(void) {
250 if (_vsokol_user_cleanup_ptr) {
251 _vsokol_user_cleanup_ptr();
252 }
253 _vcleanup();
254}
255
256void (_vsokol_cleanup_userdata_cb)(void* user_data) {
257 if (_vsokol_user_cleanup_cb_ptr) {
258 _vsokol_user_cleanup_cb_ptr(g_desc.user_data);
259 }
260 _vcleanup();
261}
262')
263 }
264 g.writeln('// The sokol_main entry point on Android
265sapp_desc sokol_main(int argc, char* argv[]) {
266 (void)argc; (void)argv;')
267 g.gen_c_main_trace_calls_hook()
268
269 if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] {
270 g.gen_boehm_gc_init()
271 }
272 if g.pref.gc_mode == .vgc {
273 g.writeln('\tbuiltin__vgc_init();')
274 }
275 if !g.pref.no_builtin {
276 g.writeln('\t_vinit(argc, (voidptr)argv);')
277 }
278 g.gen_c_main_profile_hook()
279 g.writeln('\tmain__main();')
280 if g.is_autofree {
281 g.writeln(' // Wrap user provided cleanup/free functions for sokol to be able to call _vcleanup()
282 if (g_desc.cleanup_cb) {
283 _vsokol_user_cleanup_ptr = g_desc.cleanup_cb;
284 g_desc.cleanup_cb = _vsokol_cleanup_cb;
285 }
286 else if (g_desc.cleanup_userdata_cb) {
287 _vsokol_user_cleanup_cb_ptr = g_desc.cleanup_userdata_cb;
288 g_desc.cleanup_userdata_cb = _vsokol_cleanup_userdata_cb;
289 }
290')
291 }
292 g.writeln2(' return g_desc;', '}')
293}
294
295pub fn (mut g Gen) write_tests_definitions() {
296 g.includes.writeln('#include <setjmp.h> // write_tests_main')
297 g.definitions.writeln('jmp_buf g_jump_buffer;')
298}
299
300pub fn (mut g Gen) gen_failing_error_propagation_for_test_fn(or_block ast.OrExpr, cvar_name string) {
301 // in test_() functions, an `opt()?` call is sugar for
302 // `or { cb_propagate_test_error(@LINE, @FILE, @MOD, @FN, err.msg() ) }`
303 // and the test is considered failed
304 g.write_defer_stmts_when_needed(or_block.scope, true, or_block.pos)
305 paline, pafile, pamod, pafn := g.panic_debug_info(or_block.pos)
306 dot_or_ptr := if cvar_name in g.tmp_var_ptr { '->' } else { '.' }
307 err_msg := 'IError_name_table[${cvar_name}${dot_or_ptr}err._typ]._method_msg(${cvar_name}${dot_or_ptr}err._object)'
308 g.writeln('\tmain__TestRunner_name_table[test_runner._typ]._method_fn_error(test_runner._object, ${paline}, builtin__tos3("${pafile}"), builtin__tos3("${pamod}"), builtin__tos3("${pafn}"), ${err_msg} );')
309 g.writeln('\tlongjmp(g_jump_buffer, 1);')
310}
311
312pub fn (mut g Gen) gen_failing_return_error_for_test_fn(return_stmt ast.Return, cvar_name string) {
313 // in test_() functions, a `return error('something')` is sugar for
314 // `or { err := error('something') cb_propagate_test_error(@LINE, @FILE, @MOD, @FN, err.msg() ) return err }`
315 // and the test is considered failed
316 g.write_defer_stmts_when_needed(return_stmt.scope, true, return_stmt.pos)
317 paline, pafile, pamod, pafn := g.panic_debug_info(return_stmt.pos)
318 dot_or_ptr := if cvar_name in g.tmp_var_ptr { '->' } else { '.' }
319 err_msg := 'IError_name_table[${cvar_name}${dot_or_ptr}err._typ]._method_msg(${cvar_name}${dot_or_ptr}err._object)'
320 g.writeln('\tmain__TestRunner_name_table[test_runner._typ]._method_fn_error(test_runner._object, ${paline}, builtin__tos3("${pafile}"), builtin__tos3("${pamod}"), builtin__tos3("${pafn}"), ${err_msg} );')
321 g.writeln('\tlongjmp(g_jump_buffer, 1);')
322}
323
324pub fn (mut g Gen) gen_c_main_profile_hook() {
325 if g.pref.is_prof {
326 g.writeln2('', '\tsignal(SIGINT, vprint_profile_stats_on_signal);')
327 g.writeln('\tsignal(SIGTERM, vprint_profile_stats_on_signal);')
328 g.writeln2('\tatexit(vprint_profile_stats);', '')
329 }
330 if g.pref.profile_file != '' {
331 if 'no_profile_startup' in g.pref.compile_defines {
332 g.writeln('vreset_profile_stats();')
333 }
334 if g.pref.profile_fns.len > 0 {
335 g.writeln('vreset_profile_stats();')
336 // v__profile_enabled will be set true *inside* the fns in g.pref.profile_fns:
337 g.writeln('v__profile_enabled = false;')
338 }
339 }
340}
341
342pub fn (mut g Gen) gen_c_main_for_tests() {
343 main_fn_start_pos := g.out.len
344 g.writeln('')
345 g.gen_c_main_function_header()
346 if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] {
347 g.gen_boehm_gc_init()
348 }
349 if g.pref.gc_mode == .vgc {
350 g.writeln('\tbuiltin__vgc_init();')
351 }
352 g.writeln('\tmain__vtest_init();')
353 if !g.pref.no_builtin {
354 g.writeln('\t_vinit(___argc, (voidptr)___argv);')
355 }
356 g.gen_c_main_profile_hook()
357
358 mut before_each_fn := ''
359 mut after_each_fn := ''
360 for tname in g.test_function_names {
361 short_tname := if tname.contains('.') { tname.all_after_last('.') } else { tname }
362 if short_tname == 'before_each' {
363 before_each_fn = util.no_dots(tname)
364 continue
365 }
366 if short_tname == 'after_each' {
367 after_each_fn = util.no_dots(tname)
368 }
369 }
370 mut all_tfuncs := []string{}
371 for tname in g.get_all_test_function_names() {
372 all_tfuncs << tname
373 }
374 all_tfuncs = g.filter_only_matching_fn_names(all_tfuncs)
375 g.writeln('\tstring v_test_file = ${ctoslit(g.pref.path)};')
376 if g.pref.show_asserts {
377 g.writeln('\tmain__BenchedTests bt = main__start_testing(${all_tfuncs.len}, v_test_file);')
378 }
379 g.writeln2('',
380 '\tstruct _main__TestRunner_interface_methods _vtrunner = main__TestRunner_name_table[test_runner._typ];')
381 g.writeln2('\tvoid * _vtobj = test_runner._object;', '')
382 g.writeln('\tmain__VTestFileMetaInfo_free(test_runner.file_test_info);')
383 g.writeln('\t*(test_runner.file_test_info) = main__vtest_new_filemetainfo(v_test_file, ${all_tfuncs.len});')
384 g.writeln2('\t_vtrunner._method_start(_vtobj, ${all_tfuncs.len});', '')
385 for tnumber, tname in all_tfuncs {
386 tcname := util.no_dots(tname)
387 testfn := unsafe { g.table.fns[tname] }
388 short_tname := if tname.contains('.') { tname.all_after_last('.') } else { tname }
389 is_test_fn := short_tname.starts_with('test_')
390 lnum := testfn.pos.line_nr + 1
391 g.writeln('\tmain__VTestFnMetaInfo_free(test_runner.fn_test_info);')
392 g.writeln('\tstring tcname_${tnumber} = _S("${tcname}");')
393 g.writeln('\tstring tcmod_${tnumber} = _S("${testfn.mod}");')
394 g.writeln('\tstring tcfile_${tnumber} = ${ctoslit(testfn.file)};')
395 g.writeln('\t*(test_runner.fn_test_info) = main__vtest_new_metainfo(tcname_${tnumber}, tcmod_${tnumber}, tcfile_${tnumber}, ${lnum});')
396 g.writeln('\t_vtrunner._method_fn_start(_vtobj);')
397 g.writeln('\tbool failed_${tnumber} = false;')
398 g.writeln('\tif (!setjmp(g_jump_buffer)) {')
399 //
400 if g.pref.show_asserts {
401 g.writeln('\t\tmain__BenchedTests_testing_step_start(&bt, tcname_${tnumber});')
402 }
403 if is_test_fn && before_each_fn != '' {
404 g.writeln('\t\t${before_each_fn}();')
405 }
406 g.writeln('\t\t${tcname}();')
407 //
408 g.writeln('\t}else{')
409 //
410 g.writeln('\t\tfailed_${tnumber} = true;')
411 //
412 g.writeln('\t}')
413 if is_test_fn && after_each_fn != '' {
414 g.writeln('\tif (!setjmp(g_jump_buffer)) {')
415 g.writeln('\t\t${after_each_fn}();')
416 g.writeln('\t}else{')
417 g.writeln('\t\tfailed_${tnumber} = true;')
418 g.writeln('\t}')
419 }
420 g.writeln('\tif (failed_${tnumber}) {')
421 g.writeln('\t\t_vtrunner._method_fn_fail(_vtobj);')
422 g.writeln('\t}else{')
423 g.writeln('\t\t_vtrunner._method_fn_pass(_vtobj);')
424 g.writeln('\t}')
425 if g.pref.show_asserts {
426 g.writeln('\tmain__BenchedTests_testing_step_end(&bt);')
427 }
428 g.writeln('')
429 }
430 if g.pref.show_asserts {
431 g.writeln('\tmain__BenchedTests_end_testing(&bt);')
432 }
433 g.writeln2('', '\t_vtrunner._method_finish(_vtobj);')
434 g.writeln('\tint test_exit_code = _vtrunner._method_exit_code(_vtobj);')
435
436 g.writeln2('\t_vtrunner._method__v_free(_vtobj);', '')
437 g.writeln2('\t_vcleanup();', '')
438 g.writeln2('\treturn test_exit_code;', '}')
439 if g.pref.printfn_list.len > 0 && 'main' in g.pref.printfn_list {
440 println(g.out.after(main_fn_start_pos))
441 }
442}
443
444pub fn (mut g Gen) filter_only_matching_fn_names(fnames []string) []string {
445 if g.pref.run_only.len == 0 {
446 return fnames
447 }
448 mut res := []string{}
449 for tname in fnames {
450 if tname.contains('testsuite_') {
451 res << tname
452 continue
453 }
454 short_tname := if tname.contains('.') { tname.all_after_last('.') } else { tname }
455 mut is_matching := false
456 for fn_glob_pattern in g.pref.run_only {
457 if tname.match_glob(fn_glob_pattern) || short_tname.match_glob(fn_glob_pattern) {
458 is_matching = true
459 break
460 }
461 }
462 if !is_matching {
463 continue
464 }
465 res << tname
466 }
467 return res
468}
469
470pub fn (mut g Gen) gen_c_main_trace_calls_hook() {
471 if !g.pref.trace_calls {
472 return
473 }
474 should_trace_c_main := g.pref.should_trace_fn_name('C.main')
475 g.writeln('\tu8 bottom_of_stack = 0; g_stack_base = &bottom_of_stack; v__trace_calls__on_c_main(${should_trace_c_main});')
476}
477
478// gen_dll_main create DllMain() for windows .dll.
479pub fn (mut g Gen) gen_dll_main() {
480 g.writeln('VV_EXP BOOL DllMain(HINSTANCE hinst,DWORD fdwReason,LPVOID lpvReserved) {
481 switch (fdwReason) {
482 case DLL_PROCESS_ATTACH : {
483 _vinit_caller();
484 break;
485 }
486 case DLL_THREAD_ATTACH : {
487 break;
488 }
489 case DLL_THREAD_DETACH : {
490 break;
491 }
492 case DLL_PROCESS_DETACH : {
493 _vcleanup_caller();
494 break;
495 }
496 default:
497 return false;
498 }
499 return true;
500}
501 ')
502}
503