diff --git a/examples/hot_reload/message.v b/examples/hot_reload/message.v index 336a2cfaff4bf7..c6d7cdd07a61bf 100644 --- a/examples/hot_reload/message.v +++ b/examples/hot_reload/message.v @@ -4,15 +4,25 @@ module main import time import v.live +struct App { +mut: + x int + counter int +} + @[live] -fn print_message() { - info := live.info() - println('OK reloads: ${info.reloads_ok:4d} | Total reloads: ${info.reloads:4d} | Hello! Modify this message while the program is running.') +fn print_message(mut app App) { + i := live.info() + println('OK reloads: ${i.reloads_ok:4d} | Total reloads: ${i.reloads:4d} | Hello! Modify this message while the program is running. app: ${voidptr(app)} | app.x: ${app.x:6} | app.counter: ${app.counter:6}') + // app.x = 99 // try changing this to another value, while the program is running ... + app.counter++ } fn main() { + unbuffer_stdout() + mut app := &App{} for { - print_message() + print_message(mut app) time.sleep(500 * time.millisecond) } } diff --git a/vlib/builtin/builtin.c.v b/vlib/builtin/builtin.c.v index 2f8e5c9683ef51..831f7d543173b4 100644 --- a/vlib/builtin/builtin.c.v +++ b/vlib/builtin/builtin.c.v @@ -69,7 +69,7 @@ fn panic_debug(line_no int, file string, mod string, fn_name string, s string) { eprint(' function: '); eprint(fn_name); eprintln('()') eprint(' message: '); eprintln(s) eprint(' file: '); eprint(file); eprint(':'); - C.fprintf(C.stderr, c'%d\n', line_no) + C.fprintf(C.stderr, c'%d\n', line_no) eprint(' v hash: '); eprintln(@VCURRENTHASH) eprintln('=========================================') // vfmt on @@ -394,7 +394,7 @@ fn _memory_panic(fname string, size isize) { $if freestanding || vinix { eprint('size') // TODO: use something more informative here } $else { - C.fprintf(C.stderr, c'%ld', size) + C.fprintf(C.stderr, c'%p', voidptr(size)) } if size < 0 { eprint(' < 0') @@ -768,6 +768,9 @@ __global g_main_argc = int(0) @[markused] __global g_main_argv = unsafe { nil } +@[markused] +__global g_live_reload_info voidptr + // arguments returns the command line arguments, used for starting the current program as a V array of strings. // The first string in the array (index 0), is the name of the program, used for invoking the program. // The second string in the array (index 1), if it exists, is the first argument to the program, etc. diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 61da187473bd05..79a6bad86a3365 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -6094,9 +6094,13 @@ fn (mut g Gen) write_init_function() { defer { util.timing_measure(@METHOD) } - if g.pref.is_liveshared { + + // Force generate _vinit_caller, _vcleanup_caller , these are needed under Windows, + // because dl.open() / dl.close() will call them when loading/unloading shared dll. + if g.pref.is_liveshared && g.pref.os != .windows { return } + fn_vinit_start_pos := g.out.len // ___argv is declared as voidptr here, because that unifies the windows/unix logic diff --git a/vlib/v/gen/c/consts_and_globals.v b/vlib/v/gen/c/consts_and_globals.v index 2fb214b526f118..b6d6c4fca20a9c 100644 --- a/vlib/v/gen/c/consts_and_globals.v +++ b/vlib/v/gen/c/consts_and_globals.v @@ -440,7 +440,7 @@ fn (mut g Gen) global_decl(node ast.GlobalDecl) { fn_type_name := g.get_anon_fn_type_name(mut anon_fn_expr, field.name) g.global_const_defs[util.no_dots(fn_type_name)] = GlobalConstDef{ mod: node.mod - def: '${fn_type_name} = ${g.table.sym(field.typ).name}; // global2' + def: '${fn_type_name} = ${g.table.sym(field.typ).name}; // global 1' order: -1 } continue @@ -451,7 +451,7 @@ fn (mut g Gen) global_decl(node ast.GlobalDecl) { modifier := if field.is_volatile { ' volatile ' } else { '' } def_builder.write_string('${extern}${visibility_kw}${modifier}${styp} ${attributes}${field.name}') if cextern { - def_builder.writeln('; // global5') + def_builder.writeln('; // global 2') g.global_const_defs[util.no_dots(field.name)] = GlobalConstDef{ mod: node.mod def: def_builder.str() @@ -484,7 +484,7 @@ fn (mut g Gen) global_decl(node ast.GlobalDecl) { if field.name in ['g_main_argc', 'g_main_argv'] { init = '\t// skipping ${field.name}, it was initialised in main' } else { - init = '\t${field.name} = ${g.expr_string(field.expr)}; // 3global' + init = '\t${field.name} = ${g.expr_string(field.expr)}; // global 3' } } } else if !g.pref.translated { // don't zero globals from C code @@ -493,18 +493,18 @@ fn (mut g Gen) global_decl(node ast.GlobalDecl) { if default_initializer == '{0}' && should_init { def_builder.write_string(' = {0}') } else if default_initializer == '{EMPTY_STRUCT_INITIALIZATION}' && should_init { - init = '\tmemcpy(${field.name}, (${styp}){${default_initializer}}, sizeof(${styp})); // global' + init = '\tmemcpy(${field.name}, (${styp}){${default_initializer}}, sizeof(${styp})); // global 4' } else { if field.name !in ['as_cast_type_indexes', 'g_memory_block', 'global_allocator'] { decls := g.type_default_vars.str() if decls != '' { init = '\t${decls}' } - init += '\t${field.name} = *(${styp}*)&((${styp}[]){${default_initializer}}[0]); // global' + init += '\t${field.name} = *(${styp}*)&((${styp}[]){${default_initializer}}[0]); // global 5' } } } - def_builder.writeln('; // global4') + def_builder.writeln('; // global 6') g.global_const_defs[util.no_dots(field.name)] = GlobalConstDef{ mod: node.mod def: def_builder.str() diff --git a/vlib/v/gen/c/live.v b/vlib/v/gen/c/live.v index 5bf52521dea620..4f9aacfd1d8968 100644 --- a/vlib/v/gen/c/live.v +++ b/vlib/v/gen/c/live.v @@ -41,15 +41,21 @@ fn (mut g Gen) generate_hotcode_reloader_code() { mut load_code := []string{} if g.pref.os != .windows { for so_fn in g.hotcode_fn_names { - load_code << 'impl_live_${so_fn} = dlsym(live_lib, "impl_live_${so_fn}");' + load_code << '\timpl_live_${so_fn} = dlsym(live_lib, "impl_live_${so_fn}");' } + load_code << 'void (* fn_set_live_reload_pointer)(void *) = (void *)dlsym(live_lib, "set_live_reload_pointer");' phd = posix_hotcode_definitions_1 } else { for so_fn in g.hotcode_fn_names { - load_code << 'impl_live_${so_fn} = (void *)GetProcAddress(live_lib, "impl_live_${so_fn}"); ' + load_code << '\timpl_live_${so_fn} = (void *)GetProcAddress(live_lib, "impl_live_${so_fn}"); ' } + load_code << 'void (* fn_set_live_reload_pointer)(void *) = (void *)GetProcAddress(live_lib, "set_live_reload_pointer");' phd = windows_hotcode_definitions_1 } + // Ensure that g_live_reload_info from the executable is passed to the DLL . + // See also vlib/v/live/sharedlib/live_sharedlib.v . + load_code << 'if(fn_set_live_reload_pointer){ fn_set_live_reload_pointer( g_live_reload_info ); }' + g.hotcode_definitions.writeln(phd.replace('@LOAD_FNS@', load_code.join('\n'))) } } @@ -107,9 +113,9 @@ fn (mut g Gen) generate_hotcode_reloading_main_caller() { idx++ } g.writeln('') - // g_live_info gives access to the LiveReloadInfo methods, + // g_live_reload_info gives access to the LiveReloadInfo methods, // to the custom user code, through calling v_live_info() - g.writeln('\t\tg_live_info = (void*)live_info;') + g.writeln('\t\tg_live_reload_info = (void*)live_info;') g.writeln('\t\tv__live__executable__start_reloader(live_info);') g.writeln('\t}\t// end of live code initialization section') g.writeln('') diff --git a/vlib/v/live/common.c.v b/vlib/v/live/common.c.v index 9ba61c3bd9f704..d3ba3358442df7 100644 --- a/vlib/v/live/common.c.v +++ b/vlib/v/live/common.c.v @@ -48,19 +48,13 @@ pub mut: // live.info - give user access to program's LiveReloadInfo struct, // so that the user can set callbacks, read meta information, etc. pub fn info() &LiveReloadInfo { - if C.g_live_info != 0 { - return unsafe { &LiveReloadInfo(C.g_live_info) } + if g_live_reload_info == 0 { + // When the current program is not compiled with -live, simply + // return a new empty struct LiveReloadInfo in order to prevent + // crashes. In this case, the background reloader thread is not + // started, and the structure LiveReloadInfo will not get updated. + // All its fields will be 0, but still safe to access. + g_live_reload_info = &LiveReloadInfo{} } - // When the current program is not compiled with -live, simply - // return a new empty struct LiveReloadInfo in order to prevent - // crashes. In this case, the background reloader thread is not - // started, and the structure LiveReloadInfo will not get updated. - // All its fields will be 0, but still safe to access. - mut x := &LiveReloadInfo{} - unsafe { - mut p := &u64(&C.g_live_info) - *p = &u64(x) - _ = p - } - return x + return unsafe { &LiveReloadInfo(g_live_reload_info) } } diff --git a/vlib/v/live/executable/reloader.c.v b/vlib/v/live/executable/reloader.c.v index 8868fe4d748ef1..f5259df0f66038 100644 --- a/vlib/v/live/executable/reloader.c.v +++ b/vlib/v/live/executable/reloader.c.v @@ -16,7 +16,7 @@ pub fn new_live_reload_info(original string, vexe string, vopts string, live_fn_ so_extension = '.dylib' } // $if msvc { so_extension = '.dll' } $else { so_extension = '.so' } - return &live.LiveReloadInfo{ + res := &live.LiveReloadInfo{ original: original vexe: vexe vopts: vopts @@ -28,6 +28,8 @@ pub fn new_live_reload_info(original string, vexe string, vopts string, live_fn_ reloads: 0 reload_time_ms: 0 } + elog(res, @FN) + return res } // Note: start_reloader will be called by generated code inside main(), to start @@ -35,6 +37,7 @@ pub fn new_live_reload_info(original string, vexe string, vopts string, live_fn_ // the original main thread. @[markused] pub fn start_reloader(mut r live.LiveReloadInfo) { + elog(r, @FN) // The shared library should be loaded once in the main thread // If that fails, the program would crash anyway, just provide // an error message to the user and exit: @@ -62,7 +65,7 @@ pub fn add_live_monitored_file(mut lri live.LiveReloadInfo, path string) { @[if debuglive ?] fn elog(r &live.LiveReloadInfo, s string) { - eprintln(s) + eprintln('> debuglive r: ${voidptr(r)} &g_live_reload_info: ${voidptr(&g_live_reload_info)} | g_live_reload_info: ${voidptr(g_live_reload_info)} ${s}') } fn compile_and_reload_shared_lib(mut r live.LiveReloadInfo) !bool { diff --git a/vlib/v/live/live_test.v b/vlib/v/live/live_test.v index da2b51b30071bd..a708f21d5afc48 100644 --- a/vlib/v/live/live_test.v +++ b/vlib/v/live/live_test.v @@ -36,7 +36,7 @@ const vtmp_folder = os.join_path(os.vtmp_dir(), 'live_tests') const main_source_file = os.join_path(vtmp_folder, 'main.v') const tmp_file = os.join_path(vtmp_folder, 'mymodule', 'generated_live_module.tmp') const source_file = os.join_path(vtmp_folder, 'mymodule', 'mymodule.v') -const genexe_file = os.join_path(vtmp_folder, 'generated_live_program') +const genexe_file = os.join_path(vtmp_folder, 'generated_live_program.exe') const output_file = os.join_path(vtmp_folder, 'generated_live_program.output.txt') const res_original_file = os.join_path(vtmp_folder, 'ORIGINAL.txt') const res_changed_file = os.join_path(vtmp_folder, 'CHANGED.txt') @@ -46,7 +46,7 @@ const live_program_source = get_source_template() fn get_source_template() string { src := os.read_file(os.join_path(os.dir(@FILE), 'live_test_template.vv')) or { panic(err) } - return src.replace('#OUTPUT_FILE#', output_file) + return src.replace('#OUTPUT_FILE#', output_file.replace('\\', '\\\\')) } fn atomic_write_source(source string) { @@ -168,19 +168,30 @@ fn setup_cycles_environment() { os.setenv('WAIT_CYCLES', '${max_wait_cycles}', true) } -// +fn run_in_background(cmd string) { + spawn fn (cmd string) { + res := os.execute(cmd) + if res.exit_code != 0 { + eprintln('----------------------- background command failed: --------------------------') + eprintln('----- exit_code: ${res.exit_code}, cmd: ${cmd}, output:') + eprintln(res.output) + eprintln('-----------------------------------------------------------------------------') + } + assert res.exit_code == 0 + }(cmd) + time.sleep(1000 * time.millisecond) + eprintln('... run_in_background, cmd: ${cmd}') +} + fn test_live_program_can_be_compiled() { setup_cycles_environment() eprintln('Compiling...') compile_cmd := '${os.quoted_path(vexe)} -cg -keepc -nocolor -live -o ${os.quoted_path(genexe_file)} ${os.quoted_path(main_source_file)}' eprintln('> compile_cmd: ${compile_cmd}') - os.system(compile_cmd) - - cmd := '${os.quoted_path(genexe_file)} > /dev/null &' - eprintln('Running with: ${cmd}') - res := os.system(cmd) - assert res == 0 - eprintln('... running in the background') + time.sleep(1000 * time.millisecond) // improve chances of working on windows + compile_res := os.system(compile_cmd) + assert compile_res == 0 + run_in_background('${os.quoted_path(genexe_file)}') wait_for_file('ORIGINAL') } diff --git a/vlib/v/live/sharedlib/live_sharedlib.v b/vlib/v/live/sharedlib/live_sharedlib.v index e19b418b827483..819445235104f9 100644 --- a/vlib/v/live/sharedlib/live_sharedlib.v +++ b/vlib/v/live/sharedlib/live_sharedlib.v @@ -1,3 +1,19 @@ module sharedlib import v.live as _ + +@[export: 'set_live_reload_pointer'] +@[markused] +pub fn set_live_reload_pointer(p voidptr) { + // NOTE: the `g_live_reload_info` global on windows, in the DLL, has a different address itself, + // compared to the g_live_reload_info in the main executable. + // + // The code here, ensures that *its value* will be the same, + // since the executable, will make sure to load the DLL, and then call set_live_reload_pointer() + // after binding it, in its generaged `v_bind_live_symbols`, with the value of its own `g_live_reload_info` global. + // + // This is not necessary on macos and linux, but it is best to have the same code across systems anyway. + // eprintln('>>>>> before &g_live_reload_info: ${voidptr(&g_live_reload_info)} | g_live_reload_info: ${voidptr(g_live_reload_info)}') + g_live_reload_info = p + // eprintln('>>>>> after &g_live_reload_info: ${voidptr(&g_live_reload_info)} | g_live_reload_info: ${voidptr(g_live_reload_info)}') +}