mirror of
https://github.com/vlang/v.git
synced 2025-08-04 02:07:28 -04:00
examples,builtin,cgen,live: fix windows hot reload with -cc tcc
, improve the infrastructure, use a V global instead of a C one (fix #23214) (#23350)
This commit is contained in:
parent
1bfeda6256
commit
f821c657a7
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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('')
|
||||
|
@ -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.
|
||||
mut x := &LiveReloadInfo{}
|
||||
unsafe {
|
||||
mut p := &u64(&C.g_live_info)
|
||||
*p = &u64(x)
|
||||
_ = p
|
||||
g_live_reload_info = &LiveReloadInfo{}
|
||||
}
|
||||
return x
|
||||
return unsafe { &LiveReloadInfo(g_live_reload_info) }
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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')
|
||||
}
|
||||
|
||||
|
@ -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…
x
Reference in New Issue
Block a user