diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7d897a1c..cad6e54b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -643,11 +643,14 @@ jobs: - name: clang+sanitize args: >- "-Db_sanitize=address,undefined" + "-Db_lundef=false" extra_envs: CC: clang CXX: clang++ - name: clang+msan - args: -Db_sanitize=memory + args: >- + "-Db_sanitize=memory" + "-Db_lundef=false" extra_envs: CC: clang CXX: clang++ @@ -657,6 +660,7 @@ jobs: - name: clang-cl+sanitize args: >- "-Db_sanitize=address,undefined" + "-Db_lundef=false" extra_envs: CC: clang-cl CXX: clang-cl diff --git a/CMakeLists.txt b/CMakeLists.txt index cf6bf1754..b357449e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,9 @@ set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_EXTENSIONS ON) set(CMAKE_C_STANDARD 11) +# Used to properly define QJS_LIBC_EXTERN. +add_compile_definitions(QUICKJS_NG_BUILD) + # MINGW doesn't exist in older cmake versions, newer versions don't know # about CMAKE_COMPILER_IS_MINGW, and there is no unique CMAKE_C_COMPILER_ID # for mingw-based compilers... @@ -232,7 +235,7 @@ endif() xoption(QJS_BUILD_LIBC "Build standard library modules as part of the library" OFF) macro(add_qjs_libc_if_needed target) if(NOT QJS_BUILD_LIBC) - target_sources(${target} PRIVATE quickjs-libc.c) + target_link_libraries(${target} PRIVATE qjs-libc) endif() endmacro() macro(add_static_if_needed target) @@ -245,7 +248,6 @@ macro(add_static_if_needed target) endmacro() set(qjs_sources - cutils.c dtoa.c libregexp.c libunicode.c @@ -254,6 +256,8 @@ set(qjs_sources if(QJS_BUILD_LIBC) list(APPEND qjs_sources quickjs-libc.c) + # The definition must be added to the entire project. + add_compile_definitions(QJS_BUILD_LIBC) endif() list(APPEND qjs_defines _GNU_SOURCE) if(WIN32) @@ -278,13 +282,32 @@ if(M_LIBRARIES OR CMAKE_C_COMPILER_ID STREQUAL "TinyCC") list(APPEND qjs_libs m) endif() +add_library(cutils STATIC cutils.c) +target_compile_definitions(cutils PRIVATE ${qjs_defines}) +target_link_libraries(cutils PRIVATE ${qjs_libs}) + add_library(qjs ${qjs_sources}) -target_compile_definitions(qjs PRIVATE ${qjs_defines}) +target_compile_definitions(qjs PRIVATE ${qjs_defines} QUICKJS_NG_QJS_INTERNAL) target_include_directories(qjs PUBLIC $ $ ) target_link_libraries(qjs PUBLIC ${qjs_libs}) +target_link_libraries(qjs PRIVATE $) + +# Pass a compiler definition so that Windows gets its declspec's right. +get_target_property(QJS_LIB_TYPE qjs TYPE) +if(QJS_LIB_TYPE STREQUAL "SHARED_LIBRARY") + # We use PUBLIC here because we want the consumers to also have this + # definition. + target_compile_definitions(qjs PUBLIC QUICKJS_NG_DLL) +endif() + +if(NOT QJS_BUILD_LIBC) + add_library(qjs-libc STATIC quickjs-libc.c) + target_compile_definitions(qjs-libc PRIVATE ${qjs_defines}) + target_link_libraries(qjs-libc PRIVATE ${qjs_libs} qjs cutils) +endif() if(EMSCRIPTEN) add_executable(qjs_wasm ${qjs_sources}) @@ -301,7 +324,7 @@ if(EMSCRIPTEN) -sEXPORTED_RUNTIME_METHODS=ccall,cwrap ) target_compile_definitions(qjs_wasm PRIVATE ${qjs_defines}) - target_link_libraries(qjs_wasm m) + target_link_libraries(qjs_wasm PRIVATE m) endif() @@ -314,7 +337,7 @@ add_executable(qjsc add_qjs_libc_if_needed(qjsc) add_static_if_needed(qjsc) target_compile_definitions(qjsc PRIVATE ${qjs_defines}) -target_link_libraries(qjsc qjs) +target_link_libraries(qjsc PRIVATE qjs cutils) # QuickJS CLI @@ -331,7 +354,7 @@ set_target_properties(qjs_exe PROPERTIES OUTPUT_NAME "qjs" ) target_compile_definitions(qjs_exe PRIVATE ${qjs_defines}) -target_link_libraries(qjs_exe qjs) +target_link_libraries(qjs_exe PRIVATE qjs cutils) if(NOT WIN32) set_target_properties(qjs_exe PROPERTIES ENABLE_EXPORTS TRUE) endif() @@ -339,9 +362,9 @@ if(QJS_BUILD_CLI_WITH_MIMALLOC OR QJS_BUILD_CLI_WITH_STATIC_MIMALLOC) find_package(mimalloc REQUIRED) # Upstream mimalloc doesn't provide a way to know if both libraries are supported. if(QJS_BUILD_CLI_WITH_STATIC_MIMALLOC) - target_link_libraries(qjs_exe mimalloc-static) + target_link_libraries(qjs_exe PRIVATE mimalloc-static) else() - target_link_libraries(qjs_exe mimalloc) + target_link_libraries(qjs_exe PRIVATE mimalloc) endif() endif() @@ -354,7 +377,7 @@ if(NOT EMSCRIPTEN) ) add_qjs_libc_if_needed(run-test262) target_compile_definitions(run-test262 PRIVATE ${qjs_defines}) - target_link_libraries(run-test262 qjs) + target_link_libraries(run-test262 PRIVATE qjs cutils) endif() # Interrupt test @@ -364,17 +387,17 @@ add_executable(api-test api-test.c ) target_compile_definitions(api-test PRIVATE ${qjs_defines}) -target_link_libraries(api-test qjs) +target_link_libraries(api-test PRIVATE qjs cutils) # Unicode generator # add_executable(unicode_gen EXCLUDE_FROM_ALL - cutils.c libunicode.c unicode_gen.c ) target_compile_definitions(unicode_gen PRIVATE ${qjs_defines}) +target_link_libraries(unicode_gen PRIVATE cutils) add_executable(function_source gen/function_source.c @@ -382,7 +405,7 @@ add_executable(function_source add_qjs_libc_if_needed(function_source) target_include_directories(function_source PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_definitions(function_source PRIVATE ${qjs_defines}) -target_link_libraries(function_source qjs) +target_link_libraries(function_source PRIVATE qjs) # Examples # @@ -394,7 +417,7 @@ if(QJS_BUILD_EXAMPLES) add_qjs_libc_if_needed(hello) target_include_directories(hello PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_definitions(hello PRIVATE ${qjs_defines}) - target_link_libraries(hello qjs) + target_link_libraries(hello PRIVATE qjs) add_executable(hello_module gen/hello_module.c @@ -402,7 +425,7 @@ if(QJS_BUILD_EXAMPLES) add_qjs_libc_if_needed(hello_module) target_include_directories(hello_module PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_definitions(hello_module PRIVATE ${qjs_defines}) - target_link_libraries(hello_module qjs) + target_link_libraries(hello_module PRIVATE qjs) add_library(fib MODULE examples/fib.c) set_target_properties(fib PROPERTIES @@ -411,7 +434,7 @@ if(QJS_BUILD_EXAMPLES) ) target_compile_definitions(fib PRIVATE JS_SHARED_LIBRARY) if(WIN32) - target_link_libraries(fib qjs) + target_link_libraries(fib PRIVATE qjs) elseif(APPLE) target_link_options(fib PRIVATE -undefined dynamic_lookup) endif() @@ -423,7 +446,7 @@ if(QJS_BUILD_EXAMPLES) ) target_compile_definitions(point PRIVATE JS_SHARED_LIBRARY) if(WIN32) - target_link_libraries(point qjs) + target_link_libraries(point PRIVATE qjs) elseif(APPLE) target_link_options(point PRIVATE -undefined dynamic_lookup) endif() @@ -435,7 +458,7 @@ if(QJS_BUILD_EXAMPLES) add_qjs_libc_if_needed(test_fib) target_include_directories(test_fib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_definitions(test_fib PRIVATE ${qjs_defines}) - target_link_libraries(test_fib qjs) + target_link_libraries(test_fib PRIVATE qjs) endif() # Install target diff --git a/meson.build b/meson.build index cf79060bc..33f0fe475 100644 --- a/meson.build +++ b/meson.build @@ -5,7 +5,6 @@ project( default_options: [ 'c_std=gnu11,c11', 'warning_level=3', - 'default_library=static', ], license: 'MIT', license_files: 'LICENSE', @@ -15,6 +14,8 @@ project( host_system = host_machine.system() cc = meson.get_compiler('c') +add_project_arguments('-DQUICKJS_NG_BUILD', language: 'c') + qjs_gcc_warning_args = [ '-Wno-unsafe-buffer-usage', '-Wno-sign-conversion', @@ -133,7 +134,6 @@ qjs_sys_deps += dependency('threads', required: false) qjs_sys_deps += dependency('dl', required: false) qjs_srcs = files( - 'cutils.c', 'dtoa.c', 'libregexp.c', 'libunicode.c', @@ -149,6 +149,10 @@ qjs_libc_hdrs = files('quickjs-libc.h') if qjs_libc qjs_hdrs += qjs_libc_hdrs + add_project_arguments( + '-DQJS_BUILD_LIBC', + language: 'c' + ) endif qjs_parser = get_option('parser') @@ -163,18 +167,18 @@ if not qjs_parser qjs_c_args += ['-DQJS_DISABLE_PARSER'] endif -qjs_libc_lib = static_library( - 'quickjs-libc', - qjs_libc_srcs, +cutils_lib = static_library( + 'cutils', + 'cutils.c', dependencies: qjs_sys_deps, - c_args: qjs_c_args, - gnu_symbol_visibility: 'hidden', + c_args: qjs_c_args ) qjs_lib = library( 'qjs', qjs_srcs, + qjs_libc ? qjs_libc_srcs : [], # export public headers generator( @@ -184,8 +188,9 @@ qjs_lib = library( ).process(qjs_hdrs), dependencies: qjs_sys_deps, - link_whole: qjs_libc ? qjs_libc_lib : [], - c_args: qjs_c_args, + link_with: cutils_lib, + c_args: [qjs_c_args, '-DQUICKJS_NG_QJS_INTERNAL'], + c_shared_args: ['-DQUICKJS_NG_DLL'], gnu_symbol_visibility: 'hidden', install: true, @@ -196,13 +201,41 @@ qjs_export_variables = [ f'have_parser=@qjs_parser@' ] +# Be conservative here: only pass -DQUICKJS_NG_DLL iff default_library == +# 'shared', thus qjs is guarranteed to be a DLL. Not passing -DQUICKJS_NG_DLL if +# qjs is a DLL is not harmful, but passing the definition for DLL qjs is desired. +# Alternatively, if -DQUICKJS_NG_DLL is passed for static qjs, the consumers +# will not compile. +qjs_dep_args = get_option('default_library') == 'shared' ? ['-DQUICKJS_NG_DLL'] : [] + qjs_dep = declare_dependency( + compile_args: qjs_dep_args, link_with: qjs_lib, dependencies: qjs_sys_deps, include_directories: qjs_lib.private_dir_include(), variables: qjs_export_variables, ) + +if qjs_libc + qjs_libc_dep = declare_dependency( + dependencies: qjs_dep + ) +else + qjs_libc_lib = static_library( + 'quickjs-libc', + qjs_libc_srcs, + + c_args: qjs_c_args, + link_with: cutils_lib, + dependencies: [qjs_sys_deps, qjs_dep], + gnu_symbol_visibility: 'hidden', + ) + qjs_libc_dep = declare_dependency( + link_with: qjs_libc_lib + ) +endif + if host_system == 'emscripten' qjs_wasm_export_name = 'getQuickJs' executable( @@ -274,9 +307,8 @@ qjsc_exe = executable( qjsc_srcs, c_args: qjs_c_args, - link_with: qjs_libc ? [] : qjs_libc_lib, - dependencies: qjs_dep, - + link_with: [cutils_lib], + dependencies: [qjs_dep, qjs_libc_dep], install: true, ) @@ -300,8 +332,8 @@ qjs_exe = executable( qjs_exe_srcs, c_args: qjs_c_args, - link_with: qjs_libc ? [] : qjs_libc_lib, - dependencies: [qjs_dep, mimalloc_dep], + link_with: [cutils_lib], + dependencies: [qjs_dep, qjs_libc_dep, mimalloc_dep], export_dynamic: true, install: true, @@ -328,7 +360,7 @@ if meson.is_cross_build() qjs_libc_srcs, dependencies: qjs_sys_native_deps, - c_args: qjs_c_args, + c_args: [qjs_c_args, '-DQUICKJS_NG_QJS_INTERNAL'], gnu_symbol_visibility: 'hidden', build_by_default: false, @@ -381,10 +413,10 @@ if tests.allowed() run262_exe = executable( 'run-test262', 'run-test262.c', - qjs_libc_srcs, + link_with: cutils_lib, c_args: qjs_c_args, - dependencies: qjs_dep, + dependencies: [qjs_dep, qjs_libc_dep] ) test( @@ -484,6 +516,7 @@ if tests.allowed() 'api-test', 'api-test.c', + link_with: cutils_lib, c_args: qjs_c_args, dependencies: qjs_dep, build_by_default: false, @@ -496,10 +529,9 @@ if tests.allowed() executable( 'function_source', 'gen/function_source.c', - qjs_libc_srcs, c_args: qjs_c_args, - dependencies: qjs_dep, + dependencies: [qjs_dep, qjs_libc_dep], build_by_default: false, ), ) @@ -508,9 +540,9 @@ endif # Unicode generator unicode_gen = executable( 'unicode_gen', - 'cutils.c', 'unicode_gen.c', + link_with: cutils_lib, c_args: qjs_c_args, build_by_default: false, ) @@ -607,19 +639,17 @@ if examples.allowed() executable( 'hello', 'gen/hello.c', - qjs_libc_srcs, c_args: qjs_c_args, - dependencies: qjs_dep, + dependencies: [qjs_dep, qjs_libc_dep] ) executable( 'hello_module', 'gen/hello_module.c', - qjs_libc_srcs, c_args: qjs_c_args, - dependencies: qjs_dep, + dependencies: [qjs_dep, qjs_libc_dep] ) subdir('examples') @@ -628,10 +658,9 @@ if examples.allowed() 'test_fib', 'examples/fib.c', 'gen/test_fib.c', - qjs_libc_srcs, c_args: qjs_c_args, - dependencies: qjs_dep, + dependencies: [qjs_dep, qjs_libc_dep], export_dynamic: true, ) endif diff --git a/quickjs-libc.h b/quickjs-libc.h index 8617700ef..a14e62fb2 100644 --- a/quickjs-libc.h +++ b/quickjs-libc.h @@ -34,45 +34,37 @@ extern "C" { #endif -#if defined(__GNUC__) || defined(__clang__) -#define JS_EXTERN __attribute__((visibility("default"))) -#else -#define JS_EXTERN /* nothing */ -#endif - -JS_EXTERN JSModuleDef *js_init_module_std(JSContext *ctx, +JS_LIBC_EXTERN JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name); -JS_EXTERN JSModuleDef *js_init_module_os(JSContext *ctx, +JS_LIBC_EXTERN JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name); -JS_EXTERN JSModuleDef *js_init_module_bjson(JSContext *ctx, +JS_LIBC_EXTERN JSModuleDef *js_init_module_bjson(JSContext *ctx, const char *module_name); -JS_EXTERN void js_std_add_helpers(JSContext *ctx, int argc, char **argv); -JS_EXTERN int js_std_loop(JSContext *ctx); -JS_EXTERN JSValue js_std_await(JSContext *ctx, JSValue obj); -JS_EXTERN void js_std_init_handlers(JSRuntime *rt); -JS_EXTERN void js_std_free_handlers(JSRuntime *rt); -JS_EXTERN void js_std_dump_error(JSContext *ctx); -JS_EXTERN uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, +JS_LIBC_EXTERN void js_std_add_helpers(JSContext *ctx, int argc, char **argv); +JS_LIBC_EXTERN int js_std_loop(JSContext *ctx); +JS_LIBC_EXTERN JSValue js_std_await(JSContext *ctx, JSValue obj); +JS_LIBC_EXTERN void js_std_init_handlers(JSRuntime *rt); +JS_LIBC_EXTERN void js_std_free_handlers(JSRuntime *rt); +JS_LIBC_EXTERN void js_std_dump_error(JSContext *ctx); +JS_LIBC_EXTERN uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename); -JS_EXTERN int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val, +JS_LIBC_EXTERN int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val, bool use_realpath, bool is_main); -JS_EXTERN JSModuleDef *js_module_loader(JSContext *ctx, +JS_LIBC_EXTERN JSModuleDef *js_module_loader(JSContext *ctx, const char *module_name, void *opaque); -JS_EXTERN void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, +JS_LIBC_EXTERN void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len, int flags); -JS_EXTERN void js_std_promise_rejection_tracker(JSContext *ctx, +JS_LIBC_EXTERN void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, JSValueConst reason, bool is_handled, void *opaque); // Defaults to JS_NewRuntime, no-op if compiled without worker support. // Call before creating the first worker thread. -JS_EXTERN void js_std_set_worker_new_runtime_func(JSRuntime *(*func)(void)); +JS_LIBC_EXTERN void js_std_set_worker_new_runtime_func(JSRuntime *(*func)(void)); // Defaults to JS_NewContext, no-op if compiled without worker support. // Call before creating the first worker thread. -JS_EXTERN void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)); - -#undef JS_EXTERN +JS_LIBC_EXTERN void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)); #ifdef __cplusplus } /* extern "C" { */ diff --git a/quickjs.h b/quickjs.h index 2c2e1cbb0..75fe64da5 100644 --- a/quickjs.h +++ b/quickjs.h @@ -39,12 +39,80 @@ extern "C" { #define QUICKJS_NG 1 +/* Helpers. */ +#if defined(_WIN32) || defined(__CYGWIN__) +# define QUICKJS_NG_PLAT_WIN32 1 +#endif /* defined(_WIN32) || defined(__CYGWIN__) */ + #if defined(__GNUC__) || defined(__clang__) -#define js_force_inline inline __attribute__((always_inline)) -#define JS_EXTERN __attribute__((visibility("default"))) +# define QUICKJS_NG_CC_GNULIKE 1 +#endif /* defined(__GNUC__) || defined(__clang__) */ + +/* + * `js_force_inline` -- helper macro forcing the inlining of a function. + */ +#ifdef QUICKJS_NG_PLAT_WIN32 +# ifdef QUICKJS_NG_CC_GNULIKE +# define js_force_inline inline __attribute__((always_inline)) +# else +# define js_force_inline __forceinline +# endif +#else +# ifdef QUICKJS_NG_CC_GNULIKE +# define js_force_inline inline __attribute__((always_inline)) +# else +# define js_force_inline inline +# endif +#endif /* QUICKJS_NG_PLAT_WIN32 */ + +/* + * `JS_EXTERN` -- helper macro that must be used to mark the external + * interfaces of libqjs. + * + * Explicitly undefine the macro first in case a previous header included this. + * This is done in order to avoid the redefinition warnings/errors upon a + * different definition of `JS_EXTERN`. + * + * Windows note: The `__declspec` syntax is supported by both Clang and GCC. + * The QUICKJS_NG_QJS_INTERNAL macro must be defined for libqjs (and only for + * it) to properly export symbols. + */ +#ifdef QUICKJS_NG_PLAT_WIN32 +/* Define QUICKJS_NG_DLL if building or using shared qjs. */ +# ifdef QUICKJS_NG_DLL +# ifdef QUICKJS_NG_QJS_INTERNAL +# define JS_EXTERN __declspec(dllexport) +# else +# define JS_EXTERN __declspec(dllimport) +# endif +# else +# define JS_EXTERN /* nothing */ +# endif #else -#define js_force_inline inline -#define JS_EXTERN /* nothing */ +# ifdef QUICKJS_NG_CC_GNULIKE +# define JS_EXTERN __attribute__((visibility("default"))) +# else +# define JS_EXTERN /* nothing */ +# endif +#endif /* QUICKJS_NG_PLAT_WIN32 */ + +/* + * `JS_LIBC_EXTERN` -- helper macro that must be used to mark the extern + * interfaces of quickjs-libc specifically. + */ +#if defined(QUICKJS_NG_BUILD) && !defined(QJS_BUILD_LIBC) && defined(QUICKJS_NG_PLAT_WIN32) +/* + * We are building QuickJS-NG, quickjs-libc is a static library and we are on + * Windows. Then, make sure to not export any interfaces. + */ +# define JS_LIBC_EXTERN /* nothing */ +#else +/* + * Otherwise, if we are either (1) not building QuickJS-NG, (2) libc is built as + * a part of libqjs, or (3) we are not on Windows, define JS_LIBC_EXTERN to + * JS_EXTERN. + */ +# define JS_LIBC_EXTERN JS_EXTERN #endif /* Borrowed from Folly */ @@ -65,6 +133,9 @@ extern "C" { #endif #endif +#undef QUICKJS_NG_CC_GNULIKE +#undef QUICKJS_NG_PLAT_WIN32 + typedef struct JSRuntime JSRuntime; typedef struct JSContext JSContext; typedef struct JSObject JSObject; @@ -1304,7 +1375,6 @@ JS_EXTERN const char* JS_GetVersion(void); /* Integration point for quickjs-libc.c, not for public use. */ JS_EXTERN uintptr_t js_std_cmd(int cmd, ...); -#undef JS_EXTERN #undef js_force_inline #ifdef __cplusplus