module c import v.util import v.ast import strings pub const reset_dbg_line = '#line 999999999' pub fn (mut g Gen) gen_c_main() { if !g.has_main { return } if g.pref.is_liveshared { return } if g.pref.is_o { // no main in .o files return } if 'no_main' in g.pref.compile_defines { return } g.out.writeln('') main_fn_start_pos := g.out.len is_sokol := 'sokol' in g.table.imports if (g.pref.os == .android && g.pref.is_apk) || (g.pref.os == .ios && is_sokol) { g.gen_c_android_sokol_main() } else { g.gen_c_main_header() g.writeln('\tmain__main();') g.gen_c_main_footer() if g.pref.printfn_list.len > 0 && 'main' in g.pref.printfn_list { println(g.out.after(main_fn_start_pos)) } } } fn (mut g Gen) gen_vlines_reset() { if g.pref.is_vlines { // At this point, the v files are transpiled. // The rest is auto generated code, which will not have // different .v source file/line numbers. g.vlines_path = util.vlines_escape_path(g.pref.out_name_c, g.pref.ccompiler) g.writeln2('', '// Reset the C file/line numbers') g.writeln2('${reset_dbg_line} "${g.vlines_path}"', '') } } fn (mut g Gen) gen_windows_stdio_setup(force_console bool) { g.writeln('\tBOOL con_valid = FALSE;') if force_console { g.writeln('\tcon_valid = AllocConsole();') } else { g.writeln('\tcon_valid = AttachConsole(ATTACH_PARENT_PROCESS);') } g.writeln('\tFILE* res_fp = 0;') g.writeln('\terrno_t err;') g.writeln('\tif (con_valid) {') g.writeln('\t\terr = freopen_s(&res_fp, "CON", "w", stdout);') g.writeln('\t\terr = freopen_s(&res_fp, "CON", "w", stderr);') g.writeln('\t} else {') g.writeln('\t\terr = freopen_s(&res_fp, "NUL", "w", stdout);') g.writeln('\t\terr = freopen_s(&res_fp, "NUL", "w", stderr);') g.writeln('\t}') g.writeln('\t(void)err;') } pub fn fix_reset_dbg_line(src strings.Builder, out_file string) strings.Builder { util.timing_start(@FN) defer { util.timing_measure(@FN) } // Note: using src.index() + a line counting loop + src.replace() here is slower, // since it has to iterate over pretty much the entire src string several times. // The loop below, does it just once, combining counting the lines, and finding the reset line: mut dbg_reset_line_idx := 0 mut lines := 2 for idx, ob in src { if ob == `\n` { lines++ if unsafe { vmemcmp(&u8(src.data) + idx + 1, reset_dbg_line.str, reset_dbg_line.len) } == 0 { dbg_reset_line_idx = idx + 1 break } } } // find the position of the "..\..\..\src.tmp.c": mut first_quote_idx := 0 for idx := dbg_reset_line_idx; idx < src.len; idx++ { if unsafe { &u8(src.data)[idx] } == `"` { first_quote_idx = idx break } } // replace the reset line with the fixed line counter, keeping everything // before and after it unchanged: mut sb := strings.new_builder(src.len) unsafe { sb.write_ptr(&u8(src.data), dbg_reset_line_idx) sb.write_string('#line ') sb.write_decimal(lines) sb.write_ptr(&u8(src.data) + first_quote_idx - 1, src.len - first_quote_idx) } $if trace_reset_dbg_line ? { 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}') } return sb } fn (mut g Gen) gen_c_main_function_only_header() { if g.pref.cmain != '' { g.writeln('int ${g.pref.cmain}(int ___argc, char** ___argv){') return } if g.pref.os == .windows { if g.is_gui_app() { $if msvc { // This is kinda bad but I dont see a way that is better g.writeln('#pragma comment(linker, "/SUBSYSTEM:WINDOWS")') } // GUI application g.writeln('int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance, LPWSTR cmd_line, int show_cmd){') g.writeln('\tLPWSTR full_cmd_line = GetCommandLineW(); // Note: do not use cmd_line') g.writeln('\ttypedef LPWSTR*(WINAPI *cmd_line_to_argv)(LPCWSTR, int*);') g.writeln('\tHMODULE shell32_module = LoadLibrary(L"shell32.dll");') g.writeln('\tcmd_line_to_argv CommandLineToArgvW = (cmd_line_to_argv)GetProcAddress(shell32_module, "CommandLineToArgvW");') g.writeln('\tint ___argc;') g.writeln('\twchar_t** ___argv = CommandLineToArgvW(full_cmd_line, &___argc);') g.gen_windows_stdio_setup(g.force_main_console) return } // Console application g.writeln('int wmain(int ___argc, wchar_t* ___argv[], wchar_t* ___envp[]){') return } g.writeln('int main(int ___argc, char** ___argv){') } fn (mut g Gen) gen_c_main_function_header() { g.gen_c_main_function_only_header() g.gen_c_main_trace_calls_hook() if !g.pref.no_builtin { if _ := g.table.global_scope.find_global('g_main_argc') { g.writeln('\tg_main_argc = ___argc;') } if _ := g.table.global_scope.find_global('g_main_argv') { g.writeln('\tg_main_argv = ___argv;') } } } fn (mut g Gen) gen_boehm_gc_init() { g.writeln('#if defined(_VGCBOEHM)') if g.pref.gc_mode == .boehm_leak { g.writeln('\tGC_set_find_leak(1);') } if g.pref.os == .linux && !g.pref.no_builtin { g.writeln('\tbool __v_gc_debugger_workaround = builtin__gc_prepare_for_debugger_init();') } g.writeln('\tGC_set_pages_executable(0);') if g.pref.gc_mode in [.boehm_full_opt, .boehm_incr_opt] { g.writeln('\tGC_set_free_space_divisor(2);') } if g.pref.use_coroutines { g.writeln('\tGC_allow_register_threads();') } g.writeln('\tGC_INIT();') // Register V's array data header displacement so Boehm always recognises // the interior pointer kept in `array.data` (which is offset by // `array_data_header_size()` bytes from the GC allocation start). g.writeln('\tGC_register_displacement(sizeof(void*));') if g.pref.os == .linux && !g.pref.no_builtin { g.writeln('\tbuiltin__gc_restore_roots_after_debugger_init(__v_gc_debugger_workaround);') } if g.pref.gc_mode in [.boehm_incr, .boehm_incr_opt] { g.writeln('\tGC_enable_incremental();') } g.writeln('#endif') } fn (mut g Gen) gen_windows_shared_library_boehm_init() { if g.pref.gc_mode !in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] { return } g.writeln('#if defined(_VGCBOEHM)') g.writeln('\tGC_set_pages_executable(0);') if g.pref.gc_mode in [.boehm_full_opt, .boehm_incr_opt] { g.writeln('\tGC_set_free_space_divisor(2);') } g.writeln('\tGC_INIT();') g.writeln('\tGC_register_displacement(sizeof(void*));') g.writeln('#endif') } fn (g &Gen) has_user_defined_windows_dll_main() bool { return 'DllMain' in g.export_funcs } fn (mut g Gen) gen_c_main_header() { g.gen_c_main_function_header() if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] { g.gen_boehm_gc_init() } if g.pref.gc_mode == .vgc { g.writeln('\tbuiltin__vgc_init();') } if !g.pref.no_builtin { g.writeln('\t_vinit(___argc, (voidptr)___argv);') } g.gen_c_main_profile_hook() if g.pref.is_livemain { g.generate_hotcode_reloading_main_caller() } } pub fn (mut g Gen) gen_c_main_footer() { if !g.pref.no_builtin { g.writeln('\t_vcleanup();') } g.writeln2('\treturn 0;', '}') } pub fn (mut g Gen) gen_c_android_sokol_main() { // Weave autofree into sokol lifecycle callback(s) if g.is_autofree { g.writeln('// Wrapping cleanup/free callbacks for sokol to include _vcleanup() void (*_vsokol_user_cleanup_ptr)(void); void (*_vsokol_user_cleanup_cb_ptr)(void *); void (_vsokol_cleanup_cb)(void) { if (_vsokol_user_cleanup_ptr) { _vsokol_user_cleanup_ptr(); } _vcleanup(); } void (_vsokol_cleanup_userdata_cb)(void* user_data) { if (_vsokol_user_cleanup_cb_ptr) { _vsokol_user_cleanup_cb_ptr(g_desc.user_data); } _vcleanup(); } ') } g.writeln('// The sokol_main entry point on Android sapp_desc sokol_main(int argc, char* argv[]) { (void)argc; (void)argv;') g.gen_c_main_trace_calls_hook() if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] { g.gen_boehm_gc_init() } if g.pref.gc_mode == .vgc { g.writeln('\tbuiltin__vgc_init();') } if !g.pref.no_builtin { g.writeln('\t_vinit(argc, (voidptr)argv);') } g.gen_c_main_profile_hook() g.writeln('\tmain__main();') if g.is_autofree { g.writeln(' // Wrap user provided cleanup/free functions for sokol to be able to call _vcleanup() if (g_desc.cleanup_cb) { _vsokol_user_cleanup_ptr = g_desc.cleanup_cb; g_desc.cleanup_cb = _vsokol_cleanup_cb; } else if (g_desc.cleanup_userdata_cb) { _vsokol_user_cleanup_cb_ptr = g_desc.cleanup_userdata_cb; g_desc.cleanup_userdata_cb = _vsokol_cleanup_userdata_cb; } ') } g.writeln2(' return g_desc;', '}') } pub fn (mut g Gen) write_tests_definitions() { g.includes.writeln('#include // write_tests_main') g.definitions.writeln('jmp_buf g_jump_buffer;') } pub fn (mut g Gen) gen_failing_error_propagation_for_test_fn(or_block ast.OrExpr, cvar_name string) { // in test_() functions, an `opt()?` call is sugar for // `or { cb_propagate_test_error(@LINE, @FILE, @MOD, @FN, err.msg() ) }` // and the test is considered failed g.write_defer_stmts_when_needed(or_block.scope, true, or_block.pos) paline, pafile, pamod, pafn := g.panic_debug_info(or_block.pos) dot_or_ptr := if cvar_name in g.tmp_var_ptr { '->' } else { '.' } err_msg := 'IError_name_table[${cvar_name}${dot_or_ptr}err._typ]._method_msg(${cvar_name}${dot_or_ptr}err._object)' 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} );') g.writeln('\tlongjmp(g_jump_buffer, 1);') } pub fn (mut g Gen) gen_failing_return_error_for_test_fn(return_stmt ast.Return, cvar_name string) { // in test_() functions, a `return error('something')` is sugar for // `or { err := error('something') cb_propagate_test_error(@LINE, @FILE, @MOD, @FN, err.msg() ) return err }` // and the test is considered failed g.write_defer_stmts_when_needed(return_stmt.scope, true, return_stmt.pos) paline, pafile, pamod, pafn := g.panic_debug_info(return_stmt.pos) dot_or_ptr := if cvar_name in g.tmp_var_ptr { '->' } else { '.' } err_msg := 'IError_name_table[${cvar_name}${dot_or_ptr}err._typ]._method_msg(${cvar_name}${dot_or_ptr}err._object)' 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} );') g.writeln('\tlongjmp(g_jump_buffer, 1);') } pub fn (mut g Gen) gen_c_main_profile_hook() { if g.pref.is_prof { g.writeln2('', '\tsignal(SIGINT, vprint_profile_stats_on_signal);') g.writeln('\tsignal(SIGTERM, vprint_profile_stats_on_signal);') g.writeln2('\tatexit(vprint_profile_stats);', '') } if g.pref.profile_file != '' { if 'no_profile_startup' in g.pref.compile_defines { g.writeln('vreset_profile_stats();') } if g.pref.profile_fns.len > 0 { g.writeln('vreset_profile_stats();') // v__profile_enabled will be set true *inside* the fns in g.pref.profile_fns: g.writeln('v__profile_enabled = false;') } } } pub fn (mut g Gen) gen_c_main_for_tests() { main_fn_start_pos := g.out.len g.writeln('') g.gen_c_main_function_header() if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] { g.gen_boehm_gc_init() } if g.pref.gc_mode == .vgc { g.writeln('\tbuiltin__vgc_init();') } g.writeln('\tmain__vtest_init();') if !g.pref.no_builtin { g.writeln('\t_vinit(___argc, (voidptr)___argv);') } g.gen_c_main_profile_hook() mut before_each_fn := '' mut after_each_fn := '' for tname in g.test_function_names { short_tname := if tname.contains('.') { tname.all_after_last('.') } else { tname } if short_tname == 'before_each' { before_each_fn = util.no_dots(tname) continue } if short_tname == 'after_each' { after_each_fn = util.no_dots(tname) } } mut all_tfuncs := []string{} for tname in g.get_all_test_function_names() { all_tfuncs << tname } all_tfuncs = g.filter_only_matching_fn_names(all_tfuncs) g.writeln('\tstring v_test_file = ${ctoslit(g.pref.path)};') if g.pref.show_asserts { g.writeln('\tmain__BenchedTests bt = main__start_testing(${all_tfuncs.len}, v_test_file);') } g.writeln2('', '\tstruct _main__TestRunner_interface_methods _vtrunner = main__TestRunner_name_table[test_runner._typ];') g.writeln2('\tvoid * _vtobj = test_runner._object;', '') g.writeln('\tmain__VTestFileMetaInfo_free(test_runner.file_test_info);') g.writeln('\t*(test_runner.file_test_info) = main__vtest_new_filemetainfo(v_test_file, ${all_tfuncs.len});') g.writeln2('\t_vtrunner._method_start(_vtobj, ${all_tfuncs.len});', '') for tnumber, tname in all_tfuncs { tcname := util.no_dots(tname) testfn := unsafe { g.table.fns[tname] } short_tname := if tname.contains('.') { tname.all_after_last('.') } else { tname } is_test_fn := short_tname.starts_with('test_') lnum := testfn.pos.line_nr + 1 g.writeln('\tmain__VTestFnMetaInfo_free(test_runner.fn_test_info);') g.writeln('\tstring tcname_${tnumber} = _S("${tcname}");') g.writeln('\tstring tcmod_${tnumber} = _S("${testfn.mod}");') g.writeln('\tstring tcfile_${tnumber} = ${ctoslit(testfn.file)};') g.writeln('\t*(test_runner.fn_test_info) = main__vtest_new_metainfo(tcname_${tnumber}, tcmod_${tnumber}, tcfile_${tnumber}, ${lnum});') g.writeln('\t_vtrunner._method_fn_start(_vtobj);') g.writeln('\tbool failed_${tnumber} = false;') g.writeln('\tif (!setjmp(g_jump_buffer)) {') // if g.pref.show_asserts { g.writeln('\t\tmain__BenchedTests_testing_step_start(&bt, tcname_${tnumber});') } if is_test_fn && before_each_fn != '' { g.writeln('\t\t${before_each_fn}();') } g.writeln('\t\t${tcname}();') // g.writeln('\t}else{') // g.writeln('\t\tfailed_${tnumber} = true;') // g.writeln('\t}') if is_test_fn && after_each_fn != '' { g.writeln('\tif (!setjmp(g_jump_buffer)) {') g.writeln('\t\t${after_each_fn}();') g.writeln('\t}else{') g.writeln('\t\tfailed_${tnumber} = true;') g.writeln('\t}') } g.writeln('\tif (failed_${tnumber}) {') g.writeln('\t\t_vtrunner._method_fn_fail(_vtobj);') g.writeln('\t}else{') g.writeln('\t\t_vtrunner._method_fn_pass(_vtobj);') g.writeln('\t}') if g.pref.show_asserts { g.writeln('\tmain__BenchedTests_testing_step_end(&bt);') } g.writeln('') } if g.pref.show_asserts { g.writeln('\tmain__BenchedTests_end_testing(&bt);') } g.writeln2('', '\t_vtrunner._method_finish(_vtobj);') g.writeln('\tint test_exit_code = _vtrunner._method_exit_code(_vtobj);') g.writeln2('\t_vtrunner._method__v_free(_vtobj);', '') g.writeln2('\t_vcleanup();', '') g.writeln2('\treturn test_exit_code;', '}') if g.pref.printfn_list.len > 0 && 'main' in g.pref.printfn_list { println(g.out.after(main_fn_start_pos)) } } pub fn (mut g Gen) filter_only_matching_fn_names(fnames []string) []string { if g.pref.run_only.len == 0 { return fnames } mut res := []string{} for tname in fnames { if tname.contains('testsuite_') { res << tname continue } short_tname := if tname.contains('.') { tname.all_after_last('.') } else { tname } mut is_matching := false for fn_glob_pattern in g.pref.run_only { if tname.match_glob(fn_glob_pattern) || short_tname.match_glob(fn_glob_pattern) { is_matching = true break } } if !is_matching { continue } res << tname } return res } pub fn (mut g Gen) gen_c_main_trace_calls_hook() { if !g.pref.trace_calls { return } should_trace_c_main := g.pref.should_trace_fn_name('C.main') g.writeln('\tu8 bottom_of_stack = 0; g_stack_base = &bottom_of_stack; v__trace_calls__on_c_main(${should_trace_c_main});') } // gen_dll_main create DllMain() for windows .dll. pub fn (mut g Gen) gen_dll_main() { g.writeln('VV_EXP BOOL DllMain(HINSTANCE hinst,DWORD fdwReason,LPVOID lpvReserved) { switch (fdwReason) { case DLL_PROCESS_ATTACH : { _vinit_caller(); break; } case DLL_THREAD_ATTACH : { break; } case DLL_THREAD_DETACH : { break; } case DLL_PROCESS_DETACH : { _vcleanup_caller(); break; } default: return false; } return true; } ') }