From 9ff8793666ab5edec3178ff4ac5ce9f4f6422d7b Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Mon, 19 Aug 2019 01:18:07 +0300 Subject: [PATCH] compiler: refactoring - extract cheaders.v and live.v from main.v . --- compiler/cheaders.v | 108 ++++++++++++++++ compiler/live.v | 182 +++++++++++++++++++++++++++ compiler/main.v | 298 ++++---------------------------------------- 3 files changed, 316 insertions(+), 272 deletions(-) create mode 100644 compiler/cheaders.v create mode 100644 compiler/live.v diff --git a/compiler/cheaders.v b/compiler/cheaders.v new file mode 100644 index 0000000000..b79775bb55 --- /dev/null +++ b/compiler/cheaders.v @@ -0,0 +1,108 @@ +module main + +const ( + +CommonCHeaders = ' + +#include // TODO remove all these includes, define all function signatures and types manually +#include +#include +#include // for va_list +#include // int64_t etc +#include // memcpy + +#define STRUCT_DEFAULT_VALUE {} +#define EMPTY_STRUCT_DECLARATION +#define EMPTY_STRUCT_INIT +#define OPTION_CAST(x) (x) + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include + +// must be included after +#include + +#include // _waccess +#include // _O_U8TEXT +#include // _wgetcwd +//#include +#ifdef _MSC_VER +// On MSVC these are the same (as long as /volatile:ms is passed) +#define _Atomic volatile + +// MSVC can\'t parse some things properly +#undef STRUCT_DEFAULT_VALUE +#define STRUCT_DEFAULT_VALUE {0} +#undef EMPTY_STRUCT_DECLARATION +#define EMPTY_STRUCT_DECLARATION void *____dummy_variable; +#undef EMPTY_STRUCT_INIT +#define EMPTY_STRUCT_INIT 0 +#undef OPTION_CAST +#define OPTION_CAST(x) +#endif + +void pthread_mutex_lock(HANDLE *m) { + WaitForSingleObject(*m, INFINITE); +} + +void pthread_mutex_unlock(HANDLE *m) { + ReleaseMutex(*m); +} +#else +#include +#endif + +//================================== TYPEDEFS ================================*/ + +typedef unsigned char byte; +typedef unsigned int uint; +typedef int64_t i64; +typedef int32_t i32; +typedef int16_t i16; +typedef int8_t i8; +typedef uint64_t u64; +typedef uint32_t u32; +typedef uint16_t u16; +typedef uint8_t u8; +typedef uint32_t rune; +typedef float f32; +typedef double f64; +typedef unsigned char* byteptr; +typedef int* intptr; +typedef void* voidptr; +typedef struct array array; +typedef struct map map; +typedef array array_string; +typedef array array_int; +typedef array array_byte; +typedef array array_uint; +typedef array array_float; +typedef array array_f32; +typedef array array_f64; +typedef map map_int; +typedef map map_string; +#ifndef bool + typedef int bool; + #define true 1 + #define false 0 +#endif + +//============================== HELPER C MACROS =============================*/ + +#define _PUSH(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array__push(arr, &tmp);} +#define _PUSH_MANY(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array__push_many(arr, tmp.data, tmp.len);} +#define _IN(typ, val, arr) array_##typ##_contains(arr, val) +#define _IN_MAP(val, m) map__exists(m, val) +#define ALLOC_INIT(type, ...) (type *)memdup((type[]){ __VA_ARGS__ }, sizeof(type)) + +//================================== GLOBALS =================================*/ +//int V_ZERO = 0; +byteptr g_str_buf; +int load_so(byteptr); +void reload_so(); +void init_consts(); + +' + +) \ No newline at end of file diff --git a/compiler/live.v b/compiler/live.v new file mode 100644 index 0000000000..220b114ec7 --- /dev/null +++ b/compiler/live.v @@ -0,0 +1,182 @@ +module main + +import os + +fn (v mut V) generate_hotcode_reloading_compiler_flags() []string { + mut a := []string + if v.pref.is_live || v.pref.is_so { + // See 'man dlopen', and test running a GUI program compiled with -live + if (v.os == .linux || os.user_os() == 'linux'){ + a << '-rdynamic' + } + if (v.os == .mac || os.user_os() == 'mac'){ + a << '-flat_namespace' + } + } + return a +} + +fn (v mut V) generate_hotcode_reloading_declarations() { + mut cgen := v.cgen + if v.os != .windows && v.os != .msvc { + 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;') + } + if v.pref.is_live { + cgen.genln('HANDLE live_fn_mutex = 0;') + } + } +} + +fn (v mut V) generate_hotcode_reloading_code() { + mut cgen := v.cgen + + // Hot code reloading + if v.pref.is_live { + file := v.dir + file_base := v.dir.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 = vexe.replace('\\', '\\\\') + } + + mut msvc := '' + if v.os == .msvc { + msvc = '-os msvc' + } + + mut debug := '' + + if v.pref.is_debug { + debug = '-debug' + } + + os.system('$vexe $msvc $debug -o $file_base -shared $file') + 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); + } +} +') + + if v.os != .windows && v.os != .msvc { + cgen.genln(' +#include +void* live_lib=0; +int load_so(byteptr path) { + char cpath[1024]; + sprintf(cpath,"./%s", path); + //printf("load_so %s\\n", cpath); + if (live_lib) dlclose(live_lib); + live_lib = dlopen(cpath, RTLD_LAZY); + if (!live_lib) { + puts("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(' +void* live_lib=0; +int load_so(byteptr path) { + char cpath[1024]; + sprintf(cpath, "./%s", path); + if (live_lib) FreeLibrary(live_lib); + live_lib = LoadLibraryA(cpath); + if (!live_lib) { + puts("open failed"); + 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[1024]; + char new_so_name[1024]; + char compile_cmd[1024]; + 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 + sprintf(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 + sprintf(new_so_name, "%s.dll", new_so_base); + #else + sprintf(new_so_name, "%s.so", new_so_base); + #endif + sprintf(compile_cmd, "$vexe $msvc -o %s -shared $file", new_so_base); + os__system(tos2(compile_cmd)); + + if( !os__file_exists(tos2(new_so_name)) ) { + fprintf(stderr, "Errors while compiling $file\\n"); + continue; + } + + lfnmutex_print("reload_so locking..."); + pthread_mutex_lock(&live_fn_mutex); + lfnmutex_print("reload_so locked"); + + live_lib = 0; // hack: force skipping dlclose/1, the code may be still used... + 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/compiler/main.v b/compiler/main.v index 1a39c739cc..8e3ddfa44b 100644 --- a/compiler/main.v +++ b/compiler/main.v @@ -243,123 +243,11 @@ fn (v mut V) compile() { if v.pref.is_debug { cgen.genln('#define VDEBUG (1) ') } - cgen.genln(' -#include // TODO remove all these includes, define all function signatures and types manually -#include -#include -#include // for va_list -#include // int64_t etc -#include // memcpy - -#define STRUCT_DEFAULT_VALUE {} -#define EMPTY_STRUCT_DECLARATION -#define EMPTY_STRUCT_INIT -#define OPTION_CAST(x) (x) - -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include - -// must be included after -#include - -#include // _waccess -#include // _O_U8TEXT -#include // _wgetcwd -//#include -#ifdef _MSC_VER -// On MSVC these are the same (as long as /volatile:ms is passed) -#define _Atomic volatile - -// MSVC can\'t parse some things properly -#undef STRUCT_DEFAULT_VALUE -#define STRUCT_DEFAULT_VALUE {0} -#undef EMPTY_STRUCT_DECLARATION -#define EMPTY_STRUCT_DECLARATION void *____dummy_variable; -#undef EMPTY_STRUCT_INIT -#define EMPTY_STRUCT_INIT 0 -#undef OPTION_CAST -#define OPTION_CAST(x) -#endif - -void pthread_mutex_lock(HANDLE *m) { - WaitForSingleObject(*m, INFINITE); -} - -void pthread_mutex_unlock(HANDLE *m) { - ReleaseMutex(*m); -} -#else -#include -#endif - -//================================== TYPEDEFS ================================*/ - -typedef unsigned char byte; -typedef unsigned int uint; -typedef int64_t i64; -typedef int32_t i32; -typedef int16_t i16; -typedef int8_t i8; -typedef uint64_t u64; -typedef uint32_t u32; -typedef uint16_t u16; -typedef uint8_t u8; -typedef uint32_t rune; -typedef float f32; -typedef double f64; -typedef unsigned char* byteptr; -typedef int* intptr; -typedef void* voidptr; -typedef struct array array; -typedef struct map map; -typedef array array_string; -typedef array array_int; -typedef array array_byte; -typedef array array_uint; -typedef array array_float; -typedef array array_f32; -typedef array array_f64; -typedef map map_int; -typedef map map_string; -#ifndef bool - typedef int bool; - #define true 1 - #define false 0 -#endif - -//============================== HELPER C MACROS =============================*/ - -#define _PUSH(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array__push(arr, &tmp);} -#define _PUSH_MANY(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array__push_many(arr, tmp.data, tmp.len);} -#define _IN(typ, val, arr) array_##typ##_contains(arr, val) -#define _IN_MAP(val, m) map__exists(m, val) -#define ALLOC_INIT(type, ...) (type *)memdup((type[]){ __VA_ARGS__ }, sizeof(type)) - -//================================== GLOBALS =================================*/ -//int V_ZERO = 0; -byteptr g_str_buf; -int load_so(byteptr); -void reload_so(); -void init_consts();') - - if v.os != .windows && v.os != .msvc { - 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;') - } - if v.pref.is_live { - cgen.genln('HANDLE live_fn_mutex = 0;') - } - } - + + cgen.genln(CommonCHeaders) + v.generate_hotcode_reloading_declarations() + imports_json := v.table.imports.contains('json') // TODO remove global UI hack if v.os == .mac && ((v.pref.build_mode == .embed_vlib && v.table.imports.contains('ui')) || @@ -422,6 +310,22 @@ void init_consts();') } dd := d.str() cgen.lines.set(defs_pos, dd)// TODO `def.str()` doesn't compile + + v.generate_main() + + v.generate_hotcode_reloading_code() + + cgen.save() + if v.pref.is_verbose { + v.log('flags=') + println(v.table.flags) + } + v.cc() +} + +fn (v mut V) generate_main() { + mut cgen := v.cgen + // if v.build_mode in [.default, .embed_vlib] { if v.pref.build_mode == .default_mode || v.pref.build_mode == .embed_vlib { mut consts_init_body := cgen.consts_init.join_lines() @@ -477,6 +381,7 @@ string _STR_TMP(const char *fmt, ...) { ') } + // Make sure the main function exists // Obviously we don't need it in libraries if v.pref.build_mode != .build { @@ -496,7 +401,7 @@ string _STR_TMP(const char *fmt, ...) { // Generate `main` which calls every single test function else if v.pref.is_test { cgen.genln('int main() { init_consts();') - for key, f in v.table.fns { + for _, f in v.table.fns { if f.name.starts_with('test_') { cgen.genln('$f.name();') } @@ -504,153 +409,6 @@ string _STR_TMP(const char *fmt, ...) { cgen.genln('return g_test_ok == 0; }') } } - // Hot code reloading - if v.pref.is_live { - file := v.dir - file_base := v.dir.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 = vexe.replace('\\', '\\\\') - } - - mut msvc := '' - if v.os == .msvc { - msvc = '-os msvc' - } - - mut debug := '' - - if v.pref.is_debug { - debug = '-debug' - } - - os.system('$vexe $msvc $debug -o $file_base -shared $file') - 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); - } -} -') - - if v.os != .windows && v.os != .msvc { - cgen.genln(' -#include -void* live_lib=0; -int load_so(byteptr path) { - char cpath[1024]; - sprintf(cpath,"./%s", path); - //printf("load_so %s\\n", cpath); - if (live_lib) dlclose(live_lib); - live_lib = dlopen(cpath, RTLD_LAZY); - if (!live_lib) { - puts("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(' -void* live_lib=0; -int load_so(byteptr path) { - char cpath[1024]; - sprintf(cpath, "./%s", path); - if (live_lib) FreeLibrary(live_lib); - live_lib = LoadLibraryA(cpath); - if (!live_lib) { - puts("open failed"); - 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[1024]; - char new_so_name[1024]; - char compile_cmd[1024]; - 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 - sprintf(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 - sprintf(new_so_name, "%s.dll", new_so_base); - #else - sprintf(new_so_name, "%s.so", new_so_base); - #endif - sprintf(compile_cmd, "$vexe $msvc -o %s -shared $file", new_so_base); - os__system(tos2(compile_cmd)); - - if( !os__file_exists(tos2(new_so_name)) ) { - fprintf(stderr, "Errors while compiling $file\\n"); - continue; - } - - lfnmutex_print("reload_so locking..."); - pthread_mutex_lock(&live_fn_mutex); - lfnmutex_print("reload_so locked"); - - live_lib = 0; // hack: force skipping dlclose/1, the code may be still used... - 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; }') - } - cgen.save() - if v.pref.is_verbose { - v.log('flags=') - println(v.table.flags) - } - v.cc() } fn final_target_out_name(out_name string) string { @@ -790,15 +548,11 @@ fn (v mut V) cc() { else { a << '-g' } - if v.pref.is_live || v.pref.is_so { - // See 'man dlopen', and test running a GUI program compiled with -live - if (v.os == .linux || os.user_os() == 'linux'){ - a << '-rdynamic' - } - if (v.os == .mac || os.user_os() == 'mac'){ - a << '-flat_namespace' - } + + for f in v.generate_hotcode_reloading_compiler_flags() { + a << f } + mut libs := ''// builtin.o os.o http.o etc if v.pref.build_mode == .build { a << '-c'