Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cgen: fix windows hot reload #23350

Merged
merged 16 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions examples/hot_reload/message.v
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
7 changes: 5 additions & 2 deletions vlib/builtin/builtin.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 5 additions & 1 deletion vlib/v/gen/c/cgen.v
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions vlib/v/gen/c/consts_and_globals.v
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand Down
14 changes: 10 additions & 4 deletions vlib/v/gen/c/live.v
Original file line number Diff line number Diff line change
Expand Up @@ -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')))
}
}
Expand Down Expand Up @@ -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('')
Expand Down
22 changes: 8 additions & 14 deletions vlib/v/live/common.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
}
7 changes: 5 additions & 2 deletions vlib/v/live/executable/reloader.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -28,13 +28,16 @@ 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
// the hot code reloader thread. start_reloader is executed in the context of
// 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:
Expand Down Expand Up @@ -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 {
Expand Down
31 changes: 21 additions & 10 deletions vlib/v/live/live_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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) {
Expand Down Expand Up @@ -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')
}

Expand Down
16 changes: 16 additions & 0 deletions vlib/v/live/sharedlib/live_sharedlib.v
Original file line number Diff line number Diff line change
@@ -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)}')
}
Loading