Skip to content

Commit

Permalink
examples,builtin,cgen,live: fix windows hot reload with -cc tcc, im…
Browse files Browse the repository at this point in the history
…prove the infrastructure, use a V global instead of a C one (fix #23214) (#23350)
  • Loading branch information
kbkpbot authored Jan 3, 2025
1 parent 1bfeda6 commit f821c65
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 43 deletions.
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)}')
}

0 comments on commit f821c65

Please sign in to comment.