diff --git a/cmd/tools/preludes/live_main.v b/cmd/tools/preludes/live_main.v index 0463ce6b6a..b9a06cc9ff 100644 --- a/cmd/tools/preludes/live_main.v +++ b/cmd/tools/preludes/live_main.v @@ -2,8 +2,10 @@ module main import os import time +import dl const ( os_used = os.MAX_PATH time_used = time.now() + dl_used = dl.version ) diff --git a/cmd/tools/vtest-fixed.v b/cmd/tools/vtest-fixed.v index 5192a46e44..815bbc7670 100644 --- a/cmd/tools/vtest-fixed.v +++ b/cmd/tools/vtest-fixed.v @@ -20,7 +20,6 @@ const ( 'vlib/clipboard/clipboard_test.v', 'vlib/sqlite/sqlite_test.v', - 'vlib/v/tests/live_test.v', // Linux & Solaris only; since live does not actually work for now with v2, just skip 'vlib/v/tests/asm_test.v', // skip everywhere for now, works on linux with cc != tcc ] skip_on_linux = []string{} diff --git a/cmd/v/v.v b/cmd/v/v.v index d445d19805..53f775a10d 100644 --- a/cmd/v/v.v +++ b/cmd/v/v.v @@ -123,14 +123,14 @@ fn parse_args(args []string) (&pref.Preferences, string) { '-cg' { res.is_debug = true } - '-live' { - res.is_live = true - } '-repl' { res.is_repl = true } + '-live' { + res.is_livemain = true + } '-sharedlive' { - res.is_live = true + res.is_liveshared = true res.is_shared = true } '-shared' { diff --git a/vlib/dl/dl.v b/vlib/dl/dl.v new file mode 100644 index 0000000000..e15c3353cb --- /dev/null +++ b/vlib/dl/dl.v @@ -0,0 +1,5 @@ +module dl + +pub const ( + version = 1 +) diff --git a/vlib/v/builder/cc.v b/vlib/v/builder/cc.v index c7cffa7430..9dc140badc 100644 --- a/vlib/v/builder/cc.v +++ b/vlib/v/builder/cc.v @@ -95,6 +95,7 @@ fn (mut v Builder) cc() { '-Wno-unused-parameter', '-Wno-unused-result', '-Wno-unused-function', '-Wno-missing-braces', '-Wno-unused-label' ] + mut linker_flags := []string{} // TCC on Linux by default, unless -cc was provided // TODO if -cc = cc, TCC is still used, default compiler should be // used instead. @@ -131,11 +132,15 @@ fn (mut v Builder) cc() { // linux_host := os.user_os() == 'linux' v.log('cc() isprod=$v.pref.is_prod outname=$v.pref.out_name') if v.pref.is_shared { - a << '-shared -fPIC ' // -Wl,-z,defs' + linker_flags << '-shared' + a << '-fPIC' // -Wl,-z,defs' v.pref.out_name += '.so' } if v.pref.is_bare { - a << '-fno-stack-protector -static -ffreestanding -nostdlib' + a << '-fno-stack-protector' + a << '-ffreestanding' + linker_flags << '-static' + linker_flags << '-nostdlib' } if v.pref.build_mode == .build_module { // Create the modules & out directory if it's not there. @@ -199,14 +204,14 @@ fn (mut v Builder) cc() { a << optimization_options } if debug_mode && os.user_os() != 'windows' { - a << ' -rdynamic ' // needed for nicer symbolic backtraces + linker_flags << ' -rdynamic ' // needed for nicer symbolic backtraces } if v.pref.ccompiler != 'msvc' && v.pref.os != .freebsd { a << '-Werror=implicit-function-declaration' } - if v.pref.is_shared || v.pref.is_live { + if v.pref.is_liveshared || v.pref.is_livemain { if v.pref.os == .linux || os.user_os() == 'linux' { - a << '-rdynamic' + linker_flags << '-rdynamic' } if v.pref.os == .mac || os.user_os() == 'mac' { a << '-flat_namespace' @@ -320,7 +325,8 @@ fn (mut v Builder) cc() { } if !is_cc_tcc { $if linux { - a << '-Xlinker -z -Xlinker muldefs' + linker_flags << '-Xlinker -z' + linker_flags << '-Xlinker muldefs' } } } @@ -328,20 +334,21 @@ fn (mut v Builder) cc() { // || os.user_os() == 'linux' if !v.pref.is_bare && v.pref.build_mode != .build_module && v.pref.os in [ .linux, .freebsd, .openbsd, .netbsd, .dragonfly, .solaris, .haiku] { - a << '-lm -lpthread ' + linker_flags << '-lm' + linker_flags << '-lpthread' // -ldl is a Linux only thing. BSDs have it in libc. if v.pref.os == .linux { - a << ' -ldl ' + linker_flags << '-ldl' } if v.pref.os == .freebsd { // FreeBSD: backtrace needs execinfo library while linking - a << ' -lexecinfo ' + linker_flags << '-lexecinfo' } } if !v.pref.is_bare && v.pref.os == .js && os.user_os() == 'linux' { - a << '-lm' + linker_flags << '-lm' } - args := a.join(' ') + args := a.join(' ') + linker_flags.join(' ') start: todo() // TODO remove diff --git a/vlib/v/builder/compile.v b/vlib/v/builder/compile.v index e620d2195d..47afc998eb 100644 --- a/vlib/v/builder/compile.v +++ b/vlib/v/builder/compile.v @@ -151,10 +151,10 @@ pub fn (v Builder) get_user_files() []string { // See cmd/tools/preludes/README.md for more info about what preludes are vroot := os.dir(pref.vexe_path()) preludes_path := os.join_path(vroot, 'cmd', 'tools', 'preludes') - if v.pref.is_live { + if v.pref.is_livemain { user_files << os.join_path(preludes_path, 'live_main.v') } - if v.pref.is_live && v.pref.is_shared { + if v.pref.is_liveshared { user_files << os.join_path(preludes_path, 'live_shared.v') } if v.pref.is_test { diff --git a/vlib/v/builder/live.v b/vlib/v/builder/live.v deleted file mode 100644 index 31b1a38fc0..0000000000 --- a/vlib/v/builder/live.v +++ /dev/null @@ -1,242 +0,0 @@ -module builder - -import os -import time - -fn (v &Builder) generate_hotcode_reloading_declarations() { - /* - QTODO - mut cgen := v.cgen - if v.pref.os != .windows { - if v.pref.is_so { - cgen.genln('pthread_mutex_t live_fn_mutex;') - } - if v.pref.is_live { - cgen.genln('pthread_mutex_t live_fn_mutex = PTHREAD_MUTEX_INITIALIZER;') - } - } - else { - if v.pref.is_so { - cgen.genln('HANDLE live_fn_mutex;') - cgen.genln(' -void pthread_mutex_lock(HANDLE *m) { - WaitForSingleObject(*m, INFINITE); -} - -void pthread_mutex_unlock(HANDLE *m) { - ReleaseMutex(*m); -}') - } - if v.pref.is_live { - cgen.genln('HANDLE live_fn_mutex = 0;') - } - } - */ -} - -fn (v &Builder) generate_hotcode_reloading_main_caller() { - // QTODO - /* - if !v.pref.is_live { - return - } - // We are in live code reload mode, so start the .so loader in the background - mut cgen := v.cgen - cgen.genln('') - file_base := os.file_name(v.pref.path).replace('.v', '') - if v.pref.os != .windows { - // unix: - so_name := file_base + '.so' - cgen.genln(' char *live_library_name = "$so_name";') - cgen.genln(' load_so(live_library_name);') - cgen.genln(' pthread_t _thread_so;') - cgen.genln(' pthread_create(&_thread_so , NULL, (void *)&reload_so, live_library_name);') - } - else { - // windows: - so_name := file_base + if v.pref.ccompiler == 'msvc' { '.dll' } else { '.so' } - cgen.genln(' char *live_library_name = "$so_name";') - cgen.genln(' live_fn_mutex = CreateMutexA(0, 0, 0);') - cgen.genln(' load_so(live_library_name);') - cgen.genln(' unsigned long _thread_so;') - cgen.genln(' _thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0);') - } - */ -} - -fn (v &Builder) generate_hot_reload_code() { - /* - QTODO - mut cgen := v.cgen - // Hot code reloading - if v.pref.is_live { - mut file := os.real_path(v.pref.path) - file_base := os.file_name(file).replace('.v', '') - so_name := file_base + '.so' - // Need to build .so file before building the live application - // The live app needs to load this .so file on initialization. - mut vexe := os.args[0] - if os.user_os() == 'windows' { - vexe = cescaped_path(vexe) - file = cescaped_path(file) - } - mut msvc := '' - if v.pref.ccompiler == 'msvc' { - msvc = '-cc msvc' - } - so_debug_flag := if v.pref.is_debug { '-g' } else { '' } - cmd_compile_shared_library := '$vexe $msvc $so_debug_flag -o $file_base -solive -shared $file' - if v.pref.verbosity.is_higher_or_equal(.level_one) { - println(cmd_compile_shared_library) - } - ticks := time.ticks() - so_compilation_result := os.system(cmd_compile_shared_library) - if v.pref.verbosity.is_higher_or_equal(.level_two) { - diff := time.ticks() - ticks - println('compiling shared library took $diff ms') - println('=========\n') - } - if so_compilation_result != 0 { - exit(1) - } - cgen.genln(' - -void lfnmutex_print(char *s){ -#if 0 - fflush(stderr); - fprintf(stderr,">> live_fn_mutex: %p | %s\\n", &live_fn_mutex, s); - fflush(stderr); -#endif -} -') - if v.pref.os != .windows { - cgen.genln(' -#define _POSIX_C_SOURCE 1 -#include -#ifndef PATH_MAX -#define PATH_MAX 1024 -#endif - -void* live_lib = 0; - -int load_so(byteptr path) { - char cpath[PATH_MAX] = {0}; - int res = snprintf(cpath, sizeof (cpath), "./%s", path); - if (res >= sizeof (cpath)) { - fprintf (stderr, "path is too long"); - return 0; - } - //printf("load_so %s\\n", cpath); - if (live_lib) dlclose(live_lib); - live_lib = dlopen(cpath, RTLD_LAZY); - if (!live_lib) { - fprintf(stderr, "open failed"); - exit(1); - return 0; - } -') - for so_fn in cgen.so_fns { - cgen.genln('$so_fn = dlsym(live_lib, "$so_fn"); ') - } - } - else { - cgen.genln(' - -#ifndef PATH_MAX -#define PATH_MAX 1024 -#endif - -void pthread_mutex_lock(HANDLE *m) { - WaitForSingleObject(*m, INFINITE); -} - -void pthread_mutex_unlock(HANDLE *m) { - ReleaseMutex(*m); -} - -void* live_lib = NULL; -int load_so(byteptr path) { - char cpath[PATH_MAX]; - int res = snprintf(cpath, sizeof (cpath), "./%s", path); - if (res >= sizeof(cpath)) { - puts("path is too long\\n"); - exit(1); - return 0; - } - if (live_lib) FreeLibrary(live_lib); - live_lib = LoadLibraryA(cpath); - if (!live_lib) { - puts("open failed\\n"); - exit(1); - return 0; - } -') - for so_fn in cgen.so_fns { - cgen.genln('$so_fn = (void *)GetProcAddress(live_lib, "$so_fn"); ') - } - } - cgen.genln('return 1; -} - -int _live_reloads = 0; -void reload_so() { - char new_so_base[PATH_MAX] = {0}; - char new_so_name[PATH_MAX] = {0}; - char compile_cmd[PATH_MAX] = {0}; - int last = os__file_last_mod_unix(tos2("$file")); - while (1) { - // TODO use inotify - int now = os__file_last_mod_unix(tos2("$file")); - if (now != last) { - last = now; - _live_reloads++; - - //v -o bounce -shared bounce.v - snprintf(new_so_base, sizeof (new_so_base), ".tmp.%d.${file_base}", _live_reloads); - #ifdef _WIN32 - // We have to make this directory becuase windows WILL NOT - // do it for us - os__mkdir(string_all_before_last(tos2(new_so_base), tos2("/"))); - #endif - #ifdef _MSC_VER - snprintf(new_so_name, sizeof (new_so_name), "%s.dll", new_so_base); - #else - snprintf(new_so_name, sizeof (new_so_name), "%s.so", new_so_base); - #endif - snprintf(compile_cmd, sizeof (compile_cmd), "$vexe $msvc -o %s -solive -shared $file", new_so_base); - os__system(tos2(compile_cmd)); - - if( !os__exists(tos2(new_so_name)) ) { - puts("Errors while compiling $file\\n"); - continue; - } - - lfnmutex_print("reload_so locking..."); - pthread_mutex_lock(&live_fn_mutex); - lfnmutex_print("reload_so locked"); - - load_so(new_so_name); - #ifndef _WIN32 - unlink(new_so_name); // removing the .so file from the filesystem after dlopen-ing it is safe, since it will still be mapped in memory. - #else - _unlink(new_so_name); - #endif - //if(0 == rename(new_so_name, "${so_name}")){ - // load_so("${so_name}"); - //} - - lfnmutex_print("reload_so unlocking..."); - pthread_mutex_unlock(&live_fn_mutex); - lfnmutex_print("reload_so unlocked"); - - } - time__sleep_ms(100); - } -} -') - } - if v.pref.is_so { - cgen.genln(' int load_so(byteptr path) { return 0; }') - } - */ -} diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 98b61ed1bd..5470da4cb1 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -56,6 +56,7 @@ struct Gen { auto_str_funcs strings.Builder // function bodies of all auto generated _str funcs comptime_defines strings.Builder // custom defines, given by -d/-define flags on the CLI pcs_declarations strings.Builder // -prof profile counter declarations for each function + hotcode_definitions strings.Builder // -live declarations & functions table &table.Table pref &pref.Preferences module_built string @@ -89,6 +90,9 @@ mut: pcs []ProfileCounterMeta // -prof profile counter fn_names => fn counter name attr string is_builtin_mod bool + hotcode_fn_names []string + // + fn_main &ast.FnDecl // the FnDecl of the main function. Needed in order to generate the main function code *last* } const ( @@ -120,9 +124,11 @@ pub fn cgen(files []ast.File, table &table.Table, pref &pref.Preferences) string comptime_defines: strings.new_builder(100) inits: strings.new_builder(100) pcs_declarations: strings.new_builder(100) + hotcode_definitions: strings.new_builder(100) table: table pref: pref fn_decl: 0 + fn_main: 0 autofree: true indent: -1 module_built: pref.path.after('vlib/') @@ -182,6 +188,8 @@ pub fn cgen(files []ast.File, table &table.Table, pref &pref.Preferences) string b.writeln(g.interface_table()) b.writeln('\n// V gowrappers:') b.writeln(g.gowrappers.str()) + b.writeln('\n// V hotcode definitions:') + b.writeln(g.hotcode_definitions.str()) b.writeln('\n// V stringliterals:') b.writeln(g.stringliterals.str()) b.writeln('\n// V auto str functions:') @@ -233,6 +241,9 @@ pub fn (mut g Gen) init() { if g.pref.is_debug || 'debug' in g.pref.compile_defines { g.comptime_defines.writeln('#define _VDEBUG (1)') } + if g.pref.is_livemain || g.pref.is_liveshared { + g.generate_hotcode_reloading_declarations() + } } pub fn (mut g Gen) finish() { @@ -244,6 +255,14 @@ pub fn (mut g Gen) finish() { if g.pref.is_prof { g.gen_vprint_profile_stats() } + if g.pref.is_livemain || g.pref.is_liveshared { + g.generate_hotcode_reloader_code() + } + if g.fn_main != 0 { + g.out.writeln('') + g.fn_decl = g.fn_main + g.gen_fn_decl( g.fn_main ) + } } pub fn (mut g Gen) write_typeof_functions() { @@ -449,11 +468,7 @@ fn (mut g Gen) stmt(node ast.Stmt) { } ast.Attr { g.attr = it.name - if it.name == 'inline' { - // g.writeln(it.name) - } else { - g.writeln('//[$it.name]') - } + g.writeln('// Attr: [$it.name]') } ast.Block { g.writeln('{') @@ -530,16 +545,20 @@ fn (mut g Gen) stmt(node ast.Stmt) { println('build module `$g.module_built` fn `$it.name`') } } - fn_start_pos := g.out.len g.fn_decl = it // &it - g.gen_fn_decl(it) - if g.pref.printfn_list.len > 0 && g.last_fn_c_name in g.pref.printfn_list { - println(g.out.after(fn_start_pos)) + if it.name == 'main' { + // just remember `it`; main code will be generated in finish() + g.fn_main = it + } else { + g.gen_fn_decl(it) } + g.fn_decl = 0 if skip { g.out.go_back_to(pos) } g.writeln('') + // g.attr has to be reset after each function + g.attr = '' } ast.ForCStmt { g.write('for (') @@ -2123,6 +2142,9 @@ fn verror(s string) { } fn (mut g Gen) write_init_function() { + if g.pref.is_liveshared { + return + } g.writeln('void _vinit() {') g.writeln('\tbuiltin_init();') g.writeln('\tvinit_string_literals();') diff --git a/vlib/v/gen/fn.v b/vlib/v/gen/fn.v index f672050da5..913508bc44 100644 --- a/vlib/v/gen/fn.v +++ b/vlib/v/gen/fn.v @@ -12,25 +12,40 @@ fn (mut g Gen) gen_fn_decl(it ast.FnDecl) { // || it.no_body { return } + is_main := it.name == 'main' + // + if is_main && g.pref.is_liveshared { + return + } + // + fn_start_pos := g.out.len if g.attr == 'inline' { g.write('inline ') - g.attr = '' } + // + is_livefn := g.attr == 'live' + is_livemain := g.pref.is_livemain && is_livefn + is_liveshared := g.pref.is_liveshared && is_livefn + is_livemode := g.pref.is_livemain || g.pref.is_liveshared + is_live_wrap := is_livefn && is_livemode + if is_livefn && !is_livemode { + eprintln('INFO: compile with `v -live $g.pref.path `, if you want to use the [live] function ${it.name} .') + } + // g.reset_tmp_count() - is_main := it.name == 'main' if is_main { if g.pref.os == .windows { if g.is_gui_app() { // GUI application - g.writeln('int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance, LPWSTR cmd_line, int show_cmd') + g.writeln('int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance, LPWSTR cmd_line, int show_cmd){') g.last_fn_c_name = 'wWinMain' } else { // Console application - g.writeln('int wmain(int ___argc, wchar_t* ___argv[], wchar_t* ___envp[]') + g.writeln('int wmain(int ___argc, wchar_t* ___argv[], wchar_t* ___envp[]){') g.last_fn_c_name = 'wmain' } } else { - g.write('int ${it.name}(int ___argc, char** ___argv') + g.writeln('int main(int ___argc, char** ___argv){') g.last_fn_c_name = it.name } } else { @@ -51,40 +66,67 @@ fn (mut g Gen) gen_fn_decl(it ast.FnDecl) { // println(name) // } // type_name := g.table.Type_to_str(it.return_type) - type_name := g.typ(it.return_type) - g.write('$type_name ${name}(') - g.last_fn_c_name = name - g.definitions.write('$type_name ${name}(') - } - // Receiver is the first argument - /* - if it.is_method { - mut styp := g.typ(it.receiver.typ) - // if it.receiver.typ.nr_muls() > 0 { - // if it.rec_mut { - // styp += '*' - // } - g.write('$styp $it.receiver.name ') - // TODO mut - g.definitions.write('$styp $it.receiver.name') - if it.args.len > 0 { - g.write(', ') - g.definitions.write(', ') + + // Live functions are protected by a mutex, because otherwise they + // can be changed by the live reload thread, *while* they are + // running, with unpredictable results (usually just crashing). + // For this purpose, the actual body of the live function, + // is put under a non publicly accessible function, that is prefixed + // with 'impl_live_' . + if is_livemain { + g.hotcode_fn_names << name + } + mut impl_fn_name := if is_live_wrap { 'impl_live_${name}' } else { name } + g.last_fn_c_name = impl_fn_name + type_name := g.typ(it.return_type) + // + if is_live_wrap { + if is_livemain { + g.definitions.write('$type_name (* ${impl_fn_name})(') + g.write('$type_name no_impl_${name}(') + } + if is_liveshared { + g.definitions.write('$type_name ${impl_fn_name}(') + g.write('$type_name ${impl_fn_name}(') + } + }else{ + g.definitions.write('$type_name ${name}(') + g.write('$type_name ${name}(') + } + fargs, fargtypes := g.fn_args(it.args, it.is_variadic) + if it.no_body || (g.pref.use_cache && it.is_builtin) { + // Just a function header. Builtin function bodies are defined in builtin.o + g.definitions.writeln(');') + g.writeln(');') + return } - } - */ - // - g.fn_args(it.args, it.is_variadic) - if it.no_body || (g.pref.use_cache && it.is_builtin) { - // Just a function header. - // Builtin function bodies are defined in builtin.o - g.definitions.writeln(');') - g.writeln(');') - return - } - g.writeln(') {') - if !is_main { g.definitions.writeln(');') + g.writeln(') {') + + if is_livemain { + // The live function just calls its implementation dual, while ensuring + // that the call is wrapped by the mutex lock & unlock calls. + // Adding the mutex lock/unlock inside the body of the implementation + // function is not reliable, because the implementation function can do + // an early exit, which will leave the mutex locked. + mut fn_args_list := []string{} + for ia, fa in fargs { + fn_args_list << '${fargtypes[ia]} ${fa}' + } + mut live_fncall := '${impl_fn_name}(' + fargs.join(', ') + ');' + mut live_fnreturn := '' + if type_name != 'void' { + live_fncall = '${type_name} res = ${live_fncall}' + live_fnreturn = 'return res;' + } + g.definitions.writeln('$type_name ${name}('+fn_args_list.join(', ')+');') + g.hotcode_definitions.writeln('$type_name ${name}('+fn_args_list.join(', ')+'){') + g.hotcode_definitions.writeln(' pthread_mutex_lock(&live_fn_mutex);') + g.hotcode_definitions.writeln(' $live_fncall') + g.hotcode_definitions.writeln(' pthread_mutex_unlock(&live_fn_mutex);') + g.hotcode_definitions.writeln(' $live_fnreturn') + g.hotcode_definitions.writeln('}') + } } if is_main { if g.pref.os == .windows && g.is_gui_app() { @@ -106,6 +148,10 @@ fn (mut g Gen) gen_fn_decl(it ast.FnDecl) { } } } + + if g.pref.is_livemain && is_main { + g.generate_hotcode_reloading_main_caller() + } // Profiling mode? Start counting at the beginning of the function (save current time). if g.pref.is_prof { g.profile_fn(it.name, is_main) @@ -130,7 +176,9 @@ fn (mut g Gen) gen_fn_decl(it ast.FnDecl) { } g.writeln('}') g.defer_stmts = [] - g.fn_decl = 0 + if g.pref.printfn_list.len > 0 && g.last_fn_c_name in g.pref.printfn_list { + println(g.out.after(fn_start_pos)) + } } fn (mut g Gen) write_defer_stmts_when_needed() { @@ -145,7 +193,9 @@ fn (mut g Gen) write_defer_stmts_when_needed() { } } -fn (mut g Gen) fn_args(args []table.Arg, is_variadic bool) { +fn (mut g Gen) fn_args(args []table.Arg, is_variadic bool) ([]string, []string) { + mut fargs := []string{} + mut fargtypes := []string{} no_names := args.len > 0 && args[0].name == 'arg_1' for i, arg in args { arg_type_sym := g.table.get_type_symbol(arg.typ) @@ -164,6 +214,8 @@ fn (mut g Gen) fn_args(args []table.Arg, is_variadic bool) { if !info.is_anon { g.write(arg_type_name + ' ' + arg.name) g.definitions.write(arg_type_name + ' ' + arg.name) + fargs << arg.name + fargtypes << arg_type_name } else { g.write('${g.typ(func.return_type)} (*$arg.name)(') g.definitions.write('${g.typ(func.return_type)} (*$arg.name)(') @@ -174,6 +226,8 @@ fn (mut g Gen) fn_args(args []table.Arg, is_variadic bool) { } else if no_names { g.write(arg_type_name) g.definitions.write(arg_type_name) + fargs << '' + fargtypes << arg_type_name } else { mut nr_muls := arg.typ.nr_muls() s := arg_type_name + ' ' + arg.name @@ -186,12 +240,15 @@ fn (mut g Gen) fn_args(args []table.Arg, is_variadic bool) { // } g.write(s) g.definitions.write(s) + fargs << arg.name + fargtypes << arg_type_name } if i < args.len - 1 { g.write(', ') g.definitions.write(', ') } } + return fargs, fargtypes } fn (mut g Gen) call_expr(node ast.CallExpr) { diff --git a/vlib/v/gen/live.v b/vlib/v/gen/live.v new file mode 100644 index 0000000000..67c65048cb --- /dev/null +++ b/vlib/v/gen/live.v @@ -0,0 +1,263 @@ +module gen + +import os +import time +import v.pref +import v.util + +fn (g &Gen) generate_hotcode_reloading_declarations() { + if g.pref.os != .windows { + if g.pref.is_livemain { + g.hotcode_definitions.writeln('pthread_mutex_t live_fn_mutex = PTHREAD_MUTEX_INITIALIZER;') + } + if g.pref.is_liveshared { + g.hotcode_definitions.writeln('pthread_mutex_t live_fn_mutex;') + } + } else { + if g.pref.is_livemain { + g.hotcode_definitions.writeln('HANDLE live_fn_mutex = 0;') + } + if g.pref.is_liveshared { + g.hotcode_definitions.writeln('HANDLE live_fn_mutex;') + g.hotcode_definitions.writeln(' +void pthread_mutex_lock(HANDLE *m) { + WaitForSingleObject(*m, INFINITE); +} + +void pthread_mutex_unlock(HANDLE *m) { + ReleaseMutex(*m); +}') + } + } +} + +fn (g &Gen) generate_hotcode_reloader_code() { + if g.pref.is_liveshared { + g.hotcode_definitions.writeln('') + g.hotcode_definitions.writeln('int load_so(byteptr path) { return 0; }') + g.hotcode_definitions.writeln('') + return + } + // Hot code reloading + if g.pref.is_livemain { + mut file := os.real_path(g.pref.path) + file_base := os.file_name(file).replace('.v', '') + // Need to build .so file before building the live application + // The live app needs to load this .so file on initialization. + mut vexe := pref.vexe_path() + mut so_dir := os.cache_dir() + if os.user_os() == 'windows' { + vexe = util.cescaped_path(vexe) + file = util.cescaped_path(file) + so_dir = util.cescaped_path(so_dir) + } + mut msvc := '' + if g.pref.ccompiler == 'msvc' { + msvc = '-cc msvc' + } + so_debug_flag := if g.pref.is_debug { '-cg' } else { '' } + cmd_compile_shared_library := '$vexe $msvc -cg -keepc $so_debug_flag -o ${so_dir}/${file_base} -sharedlive -shared $file' + if g.pref.is_verbose { + println(cmd_compile_shared_library) + } + ticks := time.ticks() + so_compilation_result := os.system(cmd_compile_shared_library) + if g.pref.is_verbose { + diff := time.ticks() - ticks + println('compiling shared library took $diff ms') + } + if so_compilation_result != 0 { + exit(1) + } + mut phd := '' + 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}");' + } + 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}"); ' + } + phd = windows_hotcode_definitions_1 + } + g.hotcode_definitions.writeln(phd.replace('@LOAD_FNS@', load_code.join('\n'))) + g.hotcode_definitions.writeln(' + +void lfnmutex_print(char *s){ +#if 0 + fflush(stderr); + fprintf(stderr,">> live_fn_mutex: %p | %s\\n", &live_fn_mutex, s); + fflush(stderr); +#endif +} + +void remove_so_file(char *s){ + // removing the .so file from the filesystem after dlopen-ing it is safe, since it will still be mapped in memory. + #ifndef _WIN32 + unlink(s); + #else + _unlink(s); + #endif +} + +int _live_reloads = 0; +void reload_so() { + char new_so_base[PATH_MAX] = {0}; + char new_so_name[PATH_MAX] = {0}; + char compile_cmd[PATH_MAX] = {0}; + int last = os__file_last_mod_unix(tos2("$file")); + while (1) { + // TODO use inotify + int now = os__file_last_mod_unix(tos2("$file")); + if (now != last) { + last = now; + _live_reloads++; + + //v -o bounce -sharedlive -shared bounce.v + snprintf(new_so_base, sizeof (new_so_base), "${so_dir}/tmp.%d.${file_base}", _live_reloads); + #ifdef _MSC_VER + snprintf(new_so_name, sizeof (new_so_name), "%s.dll", new_so_base); + #else + snprintf(new_so_name, sizeof (new_so_name), "%s.so", new_so_base); + #endif + snprintf(compile_cmd, sizeof (compile_cmd), "$vexe $msvc $so_debug_flag -cg -keepc -o %s -sharedlive -shared $file", new_so_base); + os__system(tos2(compile_cmd)); + + if( !os__exists(tos2(new_so_name)) ) { + puts("Errors while compiling $file\\n"); + continue; + } + + lfnmutex_print("reload_so locking..."); + pthread_mutex_lock(&live_fn_mutex); + lfnmutex_print("reload_so locked"); + + load_so(new_so_name); + remove_so_file( new_so_name ); + + lfnmutex_print("reload_so unlocking..."); + pthread_mutex_unlock(&live_fn_mutex); + lfnmutex_print("reload_so unlocked"); + } + time__sleep_ms(100); + } +} +') + } +} + + +const ( + posix_hotcode_definitions_1 = ' +#include +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif +void* live_lib = 0; +int load_so(byteptr path) { + char cpath[PATH_MAX] = {0}; + int res = snprintf(cpath, sizeof(cpath), "%s", path); + if (res >= sizeof (cpath)) { + fprintf (stderr, "path is too long"); + return 0; + } + if (live_lib) { + int closing_status = dlclose(live_lib); + //fprintf(stderr, "Closing status: %d\\n", closing_status); + live_lib = 0; + } + //fprintf (stderr, "Opening shared library at: %s\\n", cpath); + live_lib = dlopen(cpath, RTLD_LAZY); + if (!live_lib) { + fprintf(stderr, "open failed, reason: %s\\n", dlerror()); + exit(1); + return 0; + } + dlerror(); // clear errors + //fprintf(stderr, "live_lib: %p\\n", live_lib); + + @LOAD_FNS@ + + char *dlsym_error = dlerror(); + if (dlsym_error != NULL) { + fprintf(stderr, "dlsym failed, reason: %s\\n", dlsym_error); + } + return 1; +} +' + windows_hotcode_definitions_1 = ' +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif +void pthread_mutex_lock(HANDLE *m) { + WaitForSingleObject(*m, INFINITE); +} +void pthread_mutex_unlock(HANDLE *m) { + ReleaseMutex(*m); +} +void* live_lib = NULL; +int load_so(byteptr path) { + char cpath[PATH_MAX]; + int res = snprintf(cpath, sizeof(cpath), "%s", path); + if (res >= sizeof(cpath)) { + puts("path is too long\\n"); + exit(1); + return 0; + } + if (live_lib) FreeLibrary(live_lib); + live_lib = LoadLibraryA(cpath); + if (!live_lib) { + puts("open failed\\n"); + exit(1); + return 0; + } + @LOAD_FNS@ + return 1; +} +' +) + +// + +fn (g &Gen) generate_hotcode_reloading_main_caller() { + if !g.pref.is_livemain { + return + } + g.writeln('') + // We are in live code reload mode, so start the .so loader in the background + file_base := os.file_name(g.pref.path).replace('.v', '') + mut so_dir := os.cache_dir() + if os.user_os() == 'windows' { + so_dir = util.cescaped_path(so_dir) + } + + g.writeln('\t// live code initialization section:') + g.writeln('\t{') + g.writeln('\t\t// initialization of live function pointers') + for fname in g.hotcode_fn_names { + g.writeln('\t\timpl_live_${fname} = 0;') + } + + g.writeln('\t\t// start background reloading thread') + if g.pref.os != .windows { + // unix: + so_name := file_base + '.so' + g.writeln('\t\tchar *live_library_name = "${so_dir}/$so_name";') + g.writeln('\t\tload_so(live_library_name);') + g.writeln('\t\tpthread_t _thread_so;') + g.writeln('\t\tpthread_create(&_thread_so , NULL, (void *)&reload_so, live_library_name);') + } else { + // windows: + so_extension := if g.pref.ccompiler == 'msvc' { '.dll' } else { '.so' } + so_name := file_base + so_extension + g.writeln('\t\tchar *live_library_name = "${so_dir}/$so_name";') + g.writeln('\t\tlive_fn_mutex = CreateMutexA(0, 0, 0);') + g.writeln('\t\tload_so(live_library_name);') + g.writeln('\t\tunsigned long _thread_so;') + g.writeln('\t\t_thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0);') + } + g.writeln('\t}\t// end of live code initialization section') + g.writeln('') +} diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 69ed2457bb..ba7065acef 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -1058,7 +1058,7 @@ fn (mut p Parser) return_stmt() ast.Return { // left hand side of `=` or `:=` in `a,b,c := 1,2,3` fn (mut p Parser) global_decl() ast.GlobalDecl { - if !p.pref.translated && !p.pref.is_live && !p.builtin_mod && !p.pref.building_v && p.mod != + if !p.pref.translated && !p.pref.is_livemain && !p.builtin_mod && !p.pref.building_v && p.mod != 'ui' && p.mod != 'gg2' && p.mod != 'uiold' && !os.getwd().contains('/volt') && !p.pref.enable_globals { p.error('use `v --enable-globals ...` to enable globals') } diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index a03ea645c9..639333ecf9 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -28,8 +28,9 @@ pub mut: // nofmt bool // disable vfmt is_test bool // `v test string_test.v` is_script bool // single file mode (`v program.v`), main function can be skipped - is_live bool // main program that contains live/hot code - is_shared bool // an ordinary shared library, -shared, no matter if it is live or not + is_livemain bool // main program that contains live/hot code + is_liveshared bool // a shared library, that will be used in a -live main program + is_shared bool // an ordinary shared library, -shared, no matter if it is live or not is_prof bool // benchmark every function profile_file string // the profile results will be stored inside profile_file translated bool // `v translate doom.v` are we running V code translated from C? allow globals, ++ expressions, etc @@ -80,7 +81,7 @@ pub mut: mod string run_args []string // `v run x.v 1 2 3` => `1 2 3` printfn_list []string // a list of generated function names, whose source should be shown, for debugging - print_v_files bool // when true, just print the list of all parsed .v files then stop. + print_v_files bool // when true, just print the list of all parsed .v files then stop. } pub fn backend_from_string(s string) ?Backend { diff --git a/vlib/v/tests/live_test.v b/vlib/v/tests/live_test.v index c3aa7f60d5..738b5b06bc 100755 --- a/vlib/v/tests/live_test.v +++ b/vlib/v/tests/live_test.v @@ -16,7 +16,7 @@ fn pmessage() { fn main() { println('START') - for i := 0; i<6*100; i++ { + for i := 0; i<3*100; i++ { pmessage() time.sleep_ms(10) } @@ -41,7 +41,7 @@ fn testsuite_end() { eprintln('source: $source_file') eprintln('output: $output_file') $if !windows { - os.system('cat $output_file') + os.system('cat $output_file | sort | uniq -c | sort -n') } println('---------------------------------------------------------------------------') output_lines := os.read_lines(output_file) or { @@ -57,8 +57,8 @@ fn testsuite_end() { println('---------------------------------------------------------------------------') assert histogram['START'] > 0 assert histogram['END'] > 0 - assert histogram['CHANGED'] + histogram['ANOTHER'] > 0 assert histogram['ORIGINAL'] > 0 + assert histogram['CHANGED'] + histogram['ANOTHER'] > 0 } fn change_source(new string) { @@ -75,7 +75,7 @@ fn test_live_program_can_be_compiled() { eprintln('Compiling and running with: $cmd') res := os.system(cmd) eprintln('... running in the background') - time.sleep_ms(3000) + time.sleep_ms(1500) assert res == 0 } @@ -90,6 +90,6 @@ fn test_live_program_can_be_changed_2() { } fn test_live_program_has_ended() { - time.sleep_ms(10 * 1000) + time.sleep_ms(3500) assert true }