v2 / vlib / v / gen / c / cmain.v
485 lines · 456 sloc · 15.72 KB · cbaff6acd1f67affddc5451e3813fd7399bd8b4b
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 g.writeln('\tGC_set_free_space_divisor(2);')
165 }
166 if g.pref.use_coroutines {
167 g.writeln('\tGC_allow_register_threads();')
168 }
169 g.writeln('\tGC_INIT();')
170 // Register V's array data header displacement so Boehm always recognises
171 // the interior pointer kept in `array.data` (which is offset by
172 // `array_data_header_size()` bytes from the GC allocation start).
173 g.writeln('\tGC_register_displacement(sizeof(void*));')
174 if g.pref.os == .linux && !g.pref.no_builtin {
175 g.writeln('\tbuiltin__gc_restore_roots_after_debugger_init(__v_gc_debugger_workaround);')
176 }
177 if g.pref.gc_mode in [.boehm_incr, .boehm_incr_opt] {
178 g.writeln('\tGC_enable_incremental();')
179 }
180 g.writeln('#endif')
181}
182
183fn (mut g Gen) gen_windows_shared_library_boehm_init() {
184 if g.pref.gc_mode !in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] {
185 return
186 }
187 g.writeln('#if defined(_VGCBOEHM)')
188 g.writeln('\tGC_set_pages_executable(0);')
189 if g.pref.gc_mode in [.boehm_full_opt, .boehm_incr_opt] {
190 g.writeln('\tGC_set_free_space_divisor(2);')
191 }
192 g.writeln('\tGC_INIT();')
193 g.writeln('\tGC_register_displacement(sizeof(void*));')
194 g.writeln('#endif')
195}
196
197fn (g &Gen) has_user_defined_windows_dll_main() bool {
198 return 'DllMain' in g.export_funcs
199}
200
201fn (mut g Gen) gen_c_main_header() {
202 g.gen_c_main_function_header()
203 if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] {
204 g.gen_boehm_gc_init()
205 }
206 if g.pref.gc_mode == .vgc {
207 g.writeln('\tbuiltin__vgc_init();')
208 }
209 if !g.pref.no_builtin {
210 g.writeln('\t_vinit(___argc, (voidptr)___argv);')
211 }
212 g.gen_c_main_profile_hook()
213 if g.pref.is_livemain {
214 g.generate_hotcode_reloading_main_caller()
215 }
216}
217
218pub fn (mut g Gen) gen_c_main_footer() {
219 if !g.pref.no_builtin {
220 g.writeln('\t_vcleanup();')
221 }
222 g.writeln2('\treturn 0;', '}')
223}
224
225pub fn (mut g Gen) gen_c_android_sokol_main() {
226 // Weave autofree into sokol lifecycle callback(s)
227 if g.is_autofree {
228 g.writeln('// Wrapping cleanup/free callbacks for sokol to include _vcleanup()
229void (*_vsokol_user_cleanup_ptr)(void);
230void (*_vsokol_user_cleanup_cb_ptr)(void *);
231
232void (_vsokol_cleanup_cb)(void) {
233 if (_vsokol_user_cleanup_ptr) {
234 _vsokol_user_cleanup_ptr();
235 }
236 _vcleanup();
237}
238
239void (_vsokol_cleanup_userdata_cb)(void* user_data) {
240 if (_vsokol_user_cleanup_cb_ptr) {
241 _vsokol_user_cleanup_cb_ptr(g_desc.user_data);
242 }
243 _vcleanup();
244}
245')
246 }
247 g.writeln('// The sokol_main entry point on Android
248sapp_desc sokol_main(int argc, char* argv[]) {
249 (void)argc; (void)argv;')
250 g.gen_c_main_trace_calls_hook()
251
252 if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] {
253 g.gen_boehm_gc_init()
254 }
255 if g.pref.gc_mode == .vgc {
256 g.writeln('\tbuiltin__vgc_init();')
257 }
258 if !g.pref.no_builtin {
259 g.writeln('\t_vinit(argc, (voidptr)argv);')
260 }
261 g.gen_c_main_profile_hook()
262 g.writeln('\tmain__main();')
263 if g.is_autofree {
264 g.writeln(' // Wrap user provided cleanup/free functions for sokol to be able to call _vcleanup()
265 if (g_desc.cleanup_cb) {
266 _vsokol_user_cleanup_ptr = g_desc.cleanup_cb;
267 g_desc.cleanup_cb = _vsokol_cleanup_cb;
268 }
269 else if (g_desc.cleanup_userdata_cb) {
270 _vsokol_user_cleanup_cb_ptr = g_desc.cleanup_userdata_cb;
271 g_desc.cleanup_userdata_cb = _vsokol_cleanup_userdata_cb;
272 }
273')
274 }
275 g.writeln2(' return g_desc;', '}')
276}
277
278pub fn (mut g Gen) write_tests_definitions() {
279 g.includes.writeln('#include <setjmp.h> // write_tests_main')
280 g.definitions.writeln('jmp_buf g_jump_buffer;')
281}
282
283pub fn (mut g Gen) gen_failing_error_propagation_for_test_fn(or_block ast.OrExpr, cvar_name string) {
284 // in test_() functions, an `opt()?` call is sugar for
285 // `or { cb_propagate_test_error(@LINE, @FILE, @MOD, @FN, err.msg() ) }`
286 // and the test is considered failed
287 g.write_defer_stmts_when_needed(or_block.scope, true, or_block.pos)
288 paline, pafile, pamod, pafn := g.panic_debug_info(or_block.pos)
289 dot_or_ptr := if cvar_name in g.tmp_var_ptr { '->' } else { '.' }
290 err_msg := 'IError_name_table[${cvar_name}${dot_or_ptr}err._typ]._method_msg(${cvar_name}${dot_or_ptr}err._object)'
291 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} );')
292 g.writeln('\tlongjmp(g_jump_buffer, 1);')
293}
294
295pub fn (mut g Gen) gen_failing_return_error_for_test_fn(return_stmt ast.Return, cvar_name string) {
296 // in test_() functions, a `return error('something')` is sugar for
297 // `or { err := error('something') cb_propagate_test_error(@LINE, @FILE, @MOD, @FN, err.msg() ) return err }`
298 // and the test is considered failed
299 g.write_defer_stmts_when_needed(return_stmt.scope, true, return_stmt.pos)
300 paline, pafile, pamod, pafn := g.panic_debug_info(return_stmt.pos)
301 dot_or_ptr := if cvar_name in g.tmp_var_ptr { '->' } else { '.' }
302 err_msg := 'IError_name_table[${cvar_name}${dot_or_ptr}err._typ]._method_msg(${cvar_name}${dot_or_ptr}err._object)'
303 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} );')
304 g.writeln('\tlongjmp(g_jump_buffer, 1);')
305}
306
307pub fn (mut g Gen) gen_c_main_profile_hook() {
308 if g.pref.is_prof {
309 g.writeln2('', '\tsignal(SIGINT, vprint_profile_stats_on_signal);')
310 g.writeln('\tsignal(SIGTERM, vprint_profile_stats_on_signal);')
311 g.writeln2('\tatexit(vprint_profile_stats);', '')
312 }
313 if g.pref.profile_file != '' {
314 if 'no_profile_startup' in g.pref.compile_defines {
315 g.writeln('vreset_profile_stats();')
316 }
317 if g.pref.profile_fns.len > 0 {
318 g.writeln('vreset_profile_stats();')
319 // v__profile_enabled will be set true *inside* the fns in g.pref.profile_fns:
320 g.writeln('v__profile_enabled = false;')
321 }
322 }
323}
324
325pub fn (mut g Gen) gen_c_main_for_tests() {
326 main_fn_start_pos := g.out.len
327 g.writeln('')
328 g.gen_c_main_function_header()
329 if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] {
330 g.gen_boehm_gc_init()
331 }
332 if g.pref.gc_mode == .vgc {
333 g.writeln('\tbuiltin__vgc_init();')
334 }
335 g.writeln('\tmain__vtest_init();')
336 if !g.pref.no_builtin {
337 g.writeln('\t_vinit(___argc, (voidptr)___argv);')
338 }
339 g.gen_c_main_profile_hook()
340
341 mut before_each_fn := ''
342 mut after_each_fn := ''
343 for tname in g.test_function_names {
344 short_tname := if tname.contains('.') { tname.all_after_last('.') } else { tname }
345 if short_tname == 'before_each' {
346 before_each_fn = util.no_dots(tname)
347 continue
348 }
349 if short_tname == 'after_each' {
350 after_each_fn = util.no_dots(tname)
351 }
352 }
353 mut all_tfuncs := []string{}
354 for tname in g.get_all_test_function_names() {
355 all_tfuncs << tname
356 }
357 all_tfuncs = g.filter_only_matching_fn_names(all_tfuncs)
358 g.writeln('\tstring v_test_file = ${ctoslit(g.pref.path)};')
359 if g.pref.show_asserts {
360 g.writeln('\tmain__BenchedTests bt = main__start_testing(${all_tfuncs.len}, v_test_file);')
361 }
362 g.writeln2('',
363 '\tstruct _main__TestRunner_interface_methods _vtrunner = main__TestRunner_name_table[test_runner._typ];')
364 g.writeln2('\tvoid * _vtobj = test_runner._object;', '')
365 g.writeln('\tmain__VTestFileMetaInfo_free(test_runner.file_test_info);')
366 g.writeln('\t*(test_runner.file_test_info) = main__vtest_new_filemetainfo(v_test_file, ${all_tfuncs.len});')
367 g.writeln2('\t_vtrunner._method_start(_vtobj, ${all_tfuncs.len});', '')
368 for tnumber, tname in all_tfuncs {
369 tcname := util.no_dots(tname)
370 testfn := unsafe { g.table.fns[tname] }
371 short_tname := if tname.contains('.') { tname.all_after_last('.') } else { tname }
372 is_test_fn := short_tname.starts_with('test_')
373 lnum := testfn.pos.line_nr + 1
374 g.writeln('\tmain__VTestFnMetaInfo_free(test_runner.fn_test_info);')
375 g.writeln('\tstring tcname_${tnumber} = _S("${tcname}");')
376 g.writeln('\tstring tcmod_${tnumber} = _S("${testfn.mod}");')
377 g.writeln('\tstring tcfile_${tnumber} = ${ctoslit(testfn.file)};')
378 g.writeln('\t*(test_runner.fn_test_info) = main__vtest_new_metainfo(tcname_${tnumber}, tcmod_${tnumber}, tcfile_${tnumber}, ${lnum});')
379 g.writeln('\t_vtrunner._method_fn_start(_vtobj);')
380 g.writeln('\tbool failed_${tnumber} = false;')
381 g.writeln('\tif (!setjmp(g_jump_buffer)) {')
382 //
383 if g.pref.show_asserts {
384 g.writeln('\t\tmain__BenchedTests_testing_step_start(&bt, tcname_${tnumber});')
385 }
386 if is_test_fn && before_each_fn != '' {
387 g.writeln('\t\t${before_each_fn}();')
388 }
389 g.writeln('\t\t${tcname}();')
390 //
391 g.writeln('\t}else{')
392 //
393 g.writeln('\t\tfailed_${tnumber} = true;')
394 //
395 g.writeln('\t}')
396 if is_test_fn && after_each_fn != '' {
397 g.writeln('\tif (!setjmp(g_jump_buffer)) {')
398 g.writeln('\t\t${after_each_fn}();')
399 g.writeln('\t}else{')
400 g.writeln('\t\tfailed_${tnumber} = true;')
401 g.writeln('\t}')
402 }
403 g.writeln('\tif (failed_${tnumber}) {')
404 g.writeln('\t\t_vtrunner._method_fn_fail(_vtobj);')
405 g.writeln('\t}else{')
406 g.writeln('\t\t_vtrunner._method_fn_pass(_vtobj);')
407 g.writeln('\t}')
408 if g.pref.show_asserts {
409 g.writeln('\tmain__BenchedTests_testing_step_end(&bt);')
410 }
411 g.writeln('')
412 }
413 if g.pref.show_asserts {
414 g.writeln('\tmain__BenchedTests_end_testing(&bt);')
415 }
416 g.writeln2('', '\t_vtrunner._method_finish(_vtobj);')
417 g.writeln('\tint test_exit_code = _vtrunner._method_exit_code(_vtobj);')
418
419 g.writeln2('\t_vtrunner._method__v_free(_vtobj);', '')
420 g.writeln2('\t_vcleanup();', '')
421 g.writeln2('\treturn test_exit_code;', '}')
422 if g.pref.printfn_list.len > 0 && 'main' in g.pref.printfn_list {
423 println(g.out.after(main_fn_start_pos))
424 }
425}
426
427pub fn (mut g Gen) filter_only_matching_fn_names(fnames []string) []string {
428 if g.pref.run_only.len == 0 {
429 return fnames
430 }
431 mut res := []string{}
432 for tname in fnames {
433 if tname.contains('testsuite_') {
434 res << tname
435 continue
436 }
437 short_tname := if tname.contains('.') { tname.all_after_last('.') } else { tname }
438 mut is_matching := false
439 for fn_glob_pattern in g.pref.run_only {
440 if tname.match_glob(fn_glob_pattern) || short_tname.match_glob(fn_glob_pattern) {
441 is_matching = true
442 break
443 }
444 }
445 if !is_matching {
446 continue
447 }
448 res << tname
449 }
450 return res
451}
452
453pub fn (mut g Gen) gen_c_main_trace_calls_hook() {
454 if !g.pref.trace_calls {
455 return
456 }
457 should_trace_c_main := g.pref.should_trace_fn_name('C.main')
458 g.writeln('\tu8 bottom_of_stack = 0; g_stack_base = &bottom_of_stack; v__trace_calls__on_c_main(${should_trace_c_main});')
459}
460
461// gen_dll_main create DllMain() for windows .dll.
462pub fn (mut g Gen) gen_dll_main() {
463 g.writeln('VV_EXP BOOL DllMain(HINSTANCE hinst,DWORD fdwReason,LPVOID lpvReserved) {
464 switch (fdwReason) {
465 case DLL_PROCESS_ATTACH : {
466 _vinit_caller();
467 break;
468 }
469 case DLL_THREAD_ATTACH : {
470 break;
471 }
472 case DLL_THREAD_DETACH : {
473 break;
474 }
475 case DLL_PROCESS_DETACH : {
476 _vcleanup_caller();
477 break;
478 }
479 default:
480 return false;
481 }
482 return true;
483}
484 ')
485}
486