From ea486ec39d8f4d3791bd1657ba90089356653c37 Mon Sep 17 00:00:00 2001 From: Zurab Kvachadze Date: Fri, 26 Dec 2025 13:04:33 +0100 Subject: [PATCH 1/7] CMake/meson: Unbundle cutils from libqjs as a static lib Functions declared by cutils.h are used by most of the source files in the project. However, they are not part of libqjs API and, thus, the functions are not marked as API. Apart from the fact that cutils do not belong in libqjs, since they do not constitute a public API and are just helpers, they break shared qjs.dll builds on Windows where DLLs must explicitly mark their interfaces. As cutils.h does not mark its functions with JS_EXTERN or alike, the Windows linker is unable to resolve references to the cutils functions. This commit makes cutils a separate static library, which is linked with the dependent executables/libraries. Signed-off-by: Zurab Kvachadze --- CMakeLists.txt | 17 +++++++++++------ meson.build | 24 ++++++++++++++++++++---- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 76ab2a519..0595c78a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -233,6 +233,7 @@ xoption(QJS_BUILD_LIBC "Build standard library modules as part of the library" O macro(add_qjs_libc_if_needed target) if(NOT QJS_BUILD_LIBC) target_sources(${target} PRIVATE quickjs-libc.c) + target_link_libraries(${target} PRIVATE cutils) endif() endmacro() macro(add_static_if_needed target) @@ -245,7 +246,6 @@ macro(add_static_if_needed target) endmacro() set(qjs_sources - cutils.c dtoa.c libregexp.c libunicode.c @@ -278,6 +278,10 @@ 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_include_directories(qjs PUBLIC @@ -285,6 +289,7 @@ target_include_directories(qjs PUBLIC $ ) target_link_libraries(qjs PUBLIC ${qjs_libs}) +target_link_libraries(qjs PRIVATE $) if(EMSCRIPTEN) add_executable(qjs_wasm ${qjs_sources}) @@ -314,7 +319,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 qjs cutils) # QuickJS CLI @@ -331,7 +336,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 qjs cutils) if(NOT WIN32) set_target_properties(qjs_exe PROPERTIES ENABLE_EXPORTS TRUE) endif() @@ -384,7 +389,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 qjs cutils) endif() # Interrupt test @@ -394,17 +399,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 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 diff --git a/meson.build b/meson.build index cf79060bc..05e74025d 100644 --- a/meson.build +++ b/meson.build @@ -133,7 +133,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', @@ -172,6 +171,14 @@ qjs_libc_lib = static_library( gnu_symbol_visibility: 'hidden', ) +cutils_lib = static_library( + 'cutils', + 'cutils.c', + + dependencies: qjs_sys_deps, + c_args: qjs_c_args +) + qjs_lib = library( 'qjs', qjs_srcs, @@ -185,6 +192,7 @@ qjs_lib = library( dependencies: qjs_sys_deps, link_whole: qjs_libc ? qjs_libc_lib : [], + link_with: cutils_lib, c_args: qjs_c_args, gnu_symbol_visibility: 'hidden', @@ -274,7 +282,10 @@ qjsc_exe = executable( qjsc_srcs, c_args: qjs_c_args, - link_with: qjs_libc ? [] : qjs_libc_lib, + link_with: [ + qjs_libc ? [] : qjs_libc_lib, + cutils_lib + ], dependencies: qjs_dep, install: true, @@ -300,7 +311,10 @@ qjs_exe = executable( qjs_exe_srcs, c_args: qjs_c_args, - link_with: qjs_libc ? [] : qjs_libc_lib, + link_with: [ + qjs_libc ? [] : qjs_libc_lib, + cutils_lib + ], dependencies: [qjs_dep, mimalloc_dep], export_dynamic: true, @@ -383,6 +397,7 @@ if tests.allowed() 'run-test262.c', qjs_libc_srcs, + link_with: cutils_lib, c_args: qjs_c_args, dependencies: qjs_dep, ) @@ -484,6 +499,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, @@ -508,9 +524,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, ) From 9e824af2684a7a4478299a3ae94ced4ea73fd5f0 Mon Sep 17 00:00:00 2001 From: Zurab Kvachadze Date: Sun, 4 Jan 2026 23:39:41 +0100 Subject: [PATCH 2/7] meson: Do not build quickjs-libc as static library if -Dlibc=true Previously, irrespective of the setting of -Dlibc, quickjs-libc would be built as a static library. In case -Dlibc=true is set, the static library is linked in libqjs only. In case -Dlibc=false is set, the archive is linked to every quickjs-libc consumer. In the former case (-Dlibc=true), building quickjs-libc as static library introduces useless indirection, since quickjs-libc is going to be linked only to libqjs anyway. CMake just adds the libc's sources to qjs's and calls it a day. This commit changes meson.build. If -Dlibc=true is set, quickjs-libc's sources are compiled as a part of libqjs. If not, it compiled as a static library, as was done before. In both cases, a direct dependency of quickjs-libc has been changed into a `dependency` on qjs_libc_dep which conveniently abstracts both configuration. So, if quickjs-libc needs to be linked to, just add `dependencies: qjs_libc_dep`. Signed-off-by: Zurab Kvachadze --- meson.build | 64 +++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/meson.build b/meson.build index 05e74025d..aff6685cb 100644 --- a/meson.build +++ b/meson.build @@ -162,15 +162,6 @@ if not qjs_parser qjs_c_args += ['-DQJS_DISABLE_PARSER'] endif -qjs_libc_lib = static_library( - 'quickjs-libc', - qjs_libc_srcs, - - dependencies: qjs_sys_deps, - c_args: qjs_c_args, - gnu_symbol_visibility: 'hidden', -) - cutils_lib = static_library( 'cutils', 'cutils.c', @@ -182,6 +173,7 @@ cutils_lib = static_library( qjs_lib = library( 'qjs', qjs_srcs, + qjs_libc ? qjs_libc_srcs : [], # export public headers generator( @@ -191,7 +183,6 @@ qjs_lib = library( ).process(qjs_hdrs), dependencies: qjs_sys_deps, - link_whole: qjs_libc ? qjs_libc_lib : [], link_with: cutils_lib, c_args: qjs_c_args, gnu_symbol_visibility: 'hidden', @@ -211,6 +202,29 @@ qjs_dep = declare_dependency( variables: qjs_export_variables, ) + +if qjs_libc + qjs_libc_dep = declare_dependency( + include_directories: include_directories('.'), + 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, + # Pick up quickjs-libc.h. + include_directories: include_directories('.'), + ) +endif + if host_system == 'emscripten' qjs_wasm_export_name = 'getQuickJs' executable( @@ -282,12 +296,8 @@ qjsc_exe = executable( qjsc_srcs, c_args: qjs_c_args, - link_with: [ - qjs_libc ? [] : qjs_libc_lib, - cutils_lib - ], - dependencies: qjs_dep, - + link_with: [cutils_lib], + dependencies: [qjs_dep, qjs_libc_dep], install: true, ) @@ -311,11 +321,8 @@ qjs_exe = executable( qjs_exe_srcs, c_args: qjs_c_args, - link_with: [ - qjs_libc ? [] : qjs_libc_lib, - cutils_lib - ], - dependencies: [qjs_dep, mimalloc_dep], + link_with: [cutils_lib], + dependencies: [qjs_dep, qjs_libc_dep, mimalloc_dep], export_dynamic: true, install: true, @@ -395,11 +402,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( @@ -512,10 +518,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, ), ) @@ -623,19 +628,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') @@ -644,10 +647,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 From 60ce1528821d0c4c936b3398d00ff96a0cf045a3 Mon Sep 17 00:00:00 2001 From: Zurab Kvachadze Date: Mon, 5 Jan 2026 15:00:17 +0100 Subject: [PATCH 3/7] CMake: Build quickjs-libc as a static lib if -DQJS_BUILD_LIBC=false Previously, if QJS_BUILD_LIBC was false, quickjs-libc would be built with every consumer. This made it harder to specify and trace the dependencies/compiler definitions of quickjs-libc itself. With this change, if -DQJS_BUILD_LIBC=false is passed to CMake, quickjs-libc is built as a normal static library, with its own compiler definitions and dependencies. This changes CMake to match the Meson behaviour. Additionally, since CMake does not allow mixing keyword and non-keyword declarations, this commit adds explicit `PRIVATE` to all `target_link_libraries` declarations. Signed-off-by: Zurab Kvachadze --- CMakeLists.txt | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0595c78a0..66afd295f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -232,8 +232,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 cutils) + target_link_libraries(${target} PRIVATE qjs-libc) endif() endmacro() macro(add_static_if_needed target) @@ -291,6 +290,12 @@ target_include_directories(qjs PUBLIC target_link_libraries(qjs PUBLIC ${qjs_libs}) target_link_libraries(qjs PRIVATE $) +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}) target_link_options(qjs_wasm PRIVATE @@ -306,7 +311,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() @@ -319,7 +324,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 cutils) +target_link_libraries(qjsc PRIVATE qjs cutils) # QuickJS CLI @@ -336,7 +341,7 @@ set_target_properties(qjs_exe PROPERTIES OUTPUT_NAME "qjs" ) target_compile_definitions(qjs_exe PRIVATE ${qjs_defines}) -target_link_libraries(qjs_exe qjs cutils) +target_link_libraries(qjs_exe PRIVATE qjs cutils) if(NOT WIN32) set_target_properties(qjs_exe PROPERTIES ENABLE_EXPORTS TRUE) endif() @@ -374,9 +379,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() @@ -389,7 +394,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 cutils) + target_link_libraries(run-test262 PRIVATE qjs cutils) endif() # Interrupt test @@ -399,7 +404,7 @@ add_executable(api-test api-test.c ) target_compile_definitions(api-test PRIVATE ${qjs_defines}) -target_link_libraries(api-test qjs cutils) +target_link_libraries(api-test PRIVATE qjs cutils) # Unicode generator # @@ -417,7 +422,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 # @@ -429,7 +434,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 @@ -437,7 +442,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 @@ -446,7 +451,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() @@ -458,7 +463,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() @@ -470,7 +475,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 From c8f11d6c5a333f153ba2e70bcd857df93d14b2f4 Mon Sep 17 00:00:00 2001 From: Zurab Kvachadze Date: Mon, 5 Jan 2026 15:40:02 +0100 Subject: [PATCH 4/7] Consolidate JS_EXTERN, js_force_inline in quickjs.h, add Windows support Since the macros like JS_EXTERN are used in more than one place, it is quite reasonable and helpful to declare them in one place. Additionally, the Windows support with the __declspec tango was missing. This commit, in addition to reorganising definitions, also plumbs Windows support for JS_EXTERN and JS_LIBC_EXTERN. Signed-off-by: Zurab Kvachadze --- CMakeLists.txt | 18 ++++++++++-- meson.build | 22 +++++++++++++-- quickjs-libc.h | 74 ++++++++++++++++++++++-------------------------- quickjs.h | 76 ++++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 139 insertions(+), 51 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 66afd295f..7d6df6ea8 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 JS_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... @@ -253,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) @@ -290,6 +295,15 @@ 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") + target_compile_definitions(qjs + PRIVATE BUILDING_QJS_SHARED + PUBLIC USING_QJS_SHARED + ) +endif() + if(NOT QJS_BUILD_LIBC) add_library(qjs-libc STATIC quickjs-libc.c) target_compile_definitions(qjs-libc PRIVATE ${qjs_defines}) @@ -342,9 +356,7 @@ set_target_properties(qjs_exe PROPERTIES ) target_compile_definitions(qjs_exe PRIVATE ${qjs_defines}) target_link_libraries(qjs_exe PRIVATE qjs cutils) -if(NOT WIN32) - set_target_properties(qjs_exe PROPERTIES ENABLE_EXPORTS TRUE) -endif() +set_target_properties(qjs_exe PROPERTIES ENABLE_EXPORTS TRUE) # WASI Reactor # diff --git a/meson.build b/meson.build index aff6685cb..cedeee375 100644 --- a/meson.build +++ b/meson.build @@ -15,6 +15,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', @@ -148,6 +150,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') @@ -184,7 +190,8 @@ qjs_lib = library( dependencies: qjs_sys_deps, link_with: cutils_lib, - c_args: qjs_c_args, + c_args: [qjs_c_args, '-DQUICKJS_NG_QJS_INTERNAL'], + c_shared_args: ['-DBUILDING_QJS_SHARED'], gnu_symbol_visibility: 'hidden', install: true, @@ -195,7 +202,16 @@ qjs_export_variables = [ f'have_parser=@qjs_parser@' ] +# This note is only of interest on Windows. +# Be conservative here: only pass -DUSING_QJS_SHARED iff default_library == +# 'shared', thus qjs is guarranteed to be a DLL. Not passing -DUSING_QJS_SHARED if +# qjs is a DLL is not harmful, but passing the definition for DLL qjs is desired. +# Conversely, if -DUSING_QJS_SHARED is passed for static qjs, the consumers +# will not compile. +qjs_dep_args = get_option('default_library') == 'shared' ? ['-DUSING_QJS_SHARED'] : [] + qjs_dep = declare_dependency( + compile_args: qjs_dep_args, link_with: qjs_lib, dependencies: qjs_sys_deps, include_directories: qjs_lib.private_dir_include(), @@ -281,6 +297,8 @@ import('pkgconfig').generate( url: 'https://github.com/quickjs-ng/quickjs', version: meson.project_version(), variables: qjs_export_variables, + # Export -DUSING_QJS_SHARED, if needed, in the pkgconfig file. + extra_cflags: qjs_dep_args, ) if not qjs_parser @@ -349,7 +367,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, diff --git a/quickjs-libc.h b/quickjs-libc.h index abfd6c2c4..6cded280f 100644 --- a/quickjs-libc.h +++ b/quickjs-libc.h @@ -34,51 +34,43 @@ 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, - const char *module_name); -JS_EXTERN JSModuleDef *js_init_module_os(JSContext *ctx, - const char *module_name); -JS_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 int js_std_loop_once(JSContext *ctx); -JS_EXTERN int js_std_poll_io(JSContext *ctx, int timeout_ms); -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, - const char *filename); -JS_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, - const char *module_name, void *opaque, - JSValueConst attributes); -JS_EXTERN int js_module_check_attributes(JSContext *ctx, void *opaque, - JSValueConst attributes); -JS_EXTERN int js_module_test_json(JSContext *ctx, JSValueConst attributes); -JS_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, - JSValueConst promise, - JSValueConst reason, - bool is_handled, - void *opaque); +JS_LIBC_EXTERN JSModuleDef *js_init_module_std(JSContext *ctx, + const char *module_name); +JS_LIBC_EXTERN JSModuleDef *js_init_module_os(JSContext *ctx, + const char *module_name); +JS_LIBC_EXTERN JSModuleDef *js_init_module_bjson(JSContext *ctx, + const char *module_name); +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 int js_std_loop_once(JSContext *ctx); +JS_LIBC_EXTERN int js_std_poll_io(JSContext *ctx, int timeout_ms); +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_LIBC_EXTERN int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val, + bool use_realpath, bool is_main); +JS_LIBC_EXTERN JSModuleDef *js_module_loader(JSContext *ctx, + const char *module_name, void *opaque, + JSValueConst attributes); +JS_LIBC_EXTERN int js_module_check_attributes(JSContext *ctx, void *opaque, + JSValueConst attributes); +JS_LIBC_EXTERN int js_module_test_json(JSContext *ctx, JSValueConst attributes); +JS_LIBC_EXTERN void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, + size_t buf_len, int flags); +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 d66051324..e7c1b96da 100644 --- a/quickjs.h +++ b/quickjs.h @@ -39,12 +39,76 @@ 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. + * + * Define BUILDING_QJS_SHARED when building and USING_QJS_SHARED when using + * shared libqjs. + * + * 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 +# if defined(BUILDING_QJS_SHARED) +# define JS_EXTERN __declspec(dllexport) +# elif defined(USING_QJS_SHARED) +# define JS_EXTERN __declspec(dllimport) +# 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 +129,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; @@ -1325,7 +1392,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 From 34ad4c99520d0ea31c5c6601093f7552f7e673ae Mon Sep 17 00:00:00 2001 From: Zurab Kvachadze Date: Fri, 16 Jan 2026 17:53:07 +0100 Subject: [PATCH 5/7] quickjs.h: Add JS_MODULE_EXTERN macro and use it in examples/ The new macro is a counterpart to JS_EXTERN, but for binary C modules. This commit introduces one more preprocessor define that must be set when building C modules. This commit changes examples/fib.c and examples/point.c to use the new macro, while also removing redundant JS_SHARED_LIBRARY. Additionally, the includes use the angled brackets now since quickjs.h is supposed to be external to the binary modules. meson.build is fixed to properly add the include directory; the manual header copying has also been removed as it is redundant. Signed-off-by: Zurab Kvachadze --- CMakeLists.txt | 43 ++++++++++++++++++++++++++++++++----------- examples/fib.c | 19 +++---------------- examples/meson.build | 10 ++++------ examples/point.c | 14 ++++---------- meson.build | 31 ++++++++++++++++++++++--------- quickjs.h | 22 ++++++++++++++++++++++ 6 files changed, 87 insertions(+), 52 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d6df6ea8..e41754555 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -304,6 +304,23 @@ if(QJS_LIB_TYPE STREQUAL "SHARED_LIBRARY") ) endif() +# An interface library for modules. +add_library(qjs_module_lib INTERFACE) +target_include_directories(qjs_module_lib INTERFACE + $ +) +target_compile_definitions(qjs_module_lib INTERFACE + QUICKJS_NG_MODULE_BUILD + $ +) +if(WIN32) + # Since Windows cannot resolve symbols at load time, we need to + # explicitly link it to qjs. + target_link_libraries(qjs_module_lib INTERFACE + qjs + ) +endif() + if(NOT QJS_BUILD_LIBC) add_library(qjs-libc STATIC quickjs-libc.c) target_compile_definitions(qjs-libc PRIVATE ${qjs_defines}) @@ -440,6 +457,15 @@ target_link_libraries(function_source PRIVATE qjs) # if(QJS_BUILD_EXAMPLES) + if(WIN32 AND NOT (QJS_LIB_TYPE STREQUAL "SHARED_LIBRARY")) + message(AUTHOR_WARNING + "Binary modules used with static qjs on Windows. There might be\n" + "runtime errors when the module is used due to two copies of qjs\n" + "in memory.\n" + "Please use \"-DBUILD_SHARED_LIBS=true\" if possible." + ) + endif() + add_executable(hello gen/hello.c ) @@ -459,35 +485,30 @@ if(QJS_BUILD_EXAMPLES) add_library(fib MODULE examples/fib.c) set_target_properties(fib PROPERTIES PREFIX "" - C_VISIBILITY_PRESET default ) - target_compile_definitions(fib PRIVATE JS_SHARED_LIBRARY) - if(WIN32) - target_link_libraries(fib PRIVATE qjs) - elseif(APPLE) + target_link_libraries(fib PRIVATE qjs_module_lib) + if(APPLE) target_link_options(fib PRIVATE -undefined dynamic_lookup) endif() add_library(point MODULE examples/point.c) set_target_properties(point PROPERTIES PREFIX "" - C_VISIBILITY_PRESET default ) - target_compile_definitions(point PRIVATE JS_SHARED_LIBRARY) - if(WIN32) - target_link_libraries(point PRIVATE qjs) - elseif(APPLE) + target_link_libraries(point PRIVATE qjs_module_lib) + if(APPLE) target_link_options(point PRIVATE -undefined dynamic_lookup) endif() add_executable(test_fib - examples/fib.c gen/test_fib.c ) 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 PRIVATE qjs) + # Loads fib.so which depends on symbols from libqjs. + set_target_properties(test_fib PROPERTIES ENABLE_EXPORTS TRUE) endif() # Install target diff --git a/examples/fib.c b/examples/fib.c index b965acc5b..a9eb29d92 100644 --- a/examples/fib.c +++ b/examples/fib.c @@ -21,7 +21,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#include "../quickjs.h" + +#include #define countof(x) (sizeof(x) / sizeof((x)[0])) @@ -55,21 +56,7 @@ static int js_fib_init(JSContext *ctx, JSModuleDef *m) countof(js_fib_funcs)); } -#ifdef JS_SHARED_LIBRARY -#define JS_INIT_MODULE js_init_module -#else -#define JS_INIT_MODULE js_init_module_fib -#endif - -#ifndef JS_EXTERN -#ifdef _WIN32 -#define JS_EXTERN __declspec(dllexport) -#else -#define JS_EXTERN -#endif -#endif - -JS_EXTERN JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) +JS_MODULE_EXTERN JSModuleDef *js_init_module(JSContext *ctx, const char *module_name) { JSModuleDef *m; m = JS_NewCModule(ctx, module_name, js_fib_init); diff --git a/examples/meson.build b/examples/meson.build index 08c9d87b6..08f0329f1 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -3,9 +3,8 @@ shared_module( 'fib.c', name_prefix: '', - gnu_symbol_visibility: 'default', - c_args: ['-DJS_SHARED_LIBRARY'], - dependencies: host_system == 'windows' ? qjs_dep : [], + gnu_symbol_visibility: 'hidden', + dependencies: qjs_module_dep, ) shared_module( @@ -13,7 +12,6 @@ shared_module( 'point.c', name_prefix: '', - gnu_symbol_visibility: 'default', - c_args: ['-DJS_SHARED_LIBRARY'], - dependencies: host_system == 'windows' ? qjs_dep : [], + gnu_symbol_visibility: 'hidden', + dependencies: qjs_module_dep, ) diff --git a/examples/point.c b/examples/point.c index a82df1519..baa06388c 100644 --- a/examples/point.c +++ b/examples/point.c @@ -21,9 +21,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#include "../quickjs.h" + #include +#include + #define countof(x) (sizeof(x) / sizeof((x)[0])) /* Point Class */ @@ -141,15 +143,7 @@ static int js_point_init(JSContext *ctx, JSModuleDef *m) return 0; } -#ifndef JS_EXTERN -#ifdef _WIN32 -#define JS_EXTERN __declspec(dllexport) -#else -#define JS_EXTERN -#endif -#endif - -JS_EXTERN JSModuleDef *js_init_module(JSContext *ctx, const char *module_name) +JS_MODULE_EXTERN JSModuleDef *js_init_module(JSContext *ctx, const char *module_name) { JSModuleDef *m; m = JS_NewCModule(ctx, module_name, js_point_init); diff --git a/meson.build b/meson.build index cedeee375..f1f14f6fa 100644 --- a/meson.build +++ b/meson.build @@ -181,13 +181,6 @@ qjs_lib = library( qjs_srcs, qjs_libc ? qjs_libc_srcs : [], - # export public headers - generator( - find_program('cp', 'xcopy'), - output: ['@PLAINNAME@'], - arguments: ['@INPUT@', '@OUTPUT@'], - ).process(qjs_hdrs), - dependencies: qjs_sys_deps, link_with: cutils_lib, c_args: [qjs_c_args, '-DQUICKJS_NG_QJS_INTERNAL'], @@ -214,10 +207,23 @@ qjs_dep = declare_dependency( compile_args: qjs_dep_args, link_with: qjs_lib, dependencies: qjs_sys_deps, - include_directories: qjs_lib.private_dir_include(), + include_directories: include_directories('.'), variables: qjs_export_variables, ) +if host_system != 'windows' + qjs_module_dep = declare_dependency( + compile_args: [qjs_dep_args, '-DQUICKJS_NG_MODULE_BUILD'], + include_directories: include_directories('.'), + ) +else + qjs_module_dep = declare_dependency( + compile_args: [qjs_dep_args, '-DQUICKJS_NG_MODULE_BUILD'], + include_directories: include_directories('.'), + # Windows can't resolve symbols at runtime thus we explicitly link to qjs. + dependencies: qjs_dep, + ) +endif if qjs_libc qjs_libc_dep = declare_dependency( @@ -643,6 +649,14 @@ alias_target('codegen', ) if examples.allowed() + if host_system == 'windows' and get_option('default_library') != 'shared' + warning('Binary modules used with static qjs on Windows.') + warning( + 'There might be runtime errors when the modules are used due to', + 'two copies of qjs in memory.' + ) + warning('Please use "-Ddefault_library=shared" if possible.') + endif executable( 'hello', 'gen/hello.c', @@ -663,7 +677,6 @@ if examples.allowed() executable( 'test_fib', - 'examples/fib.c', 'gen/test_fib.c', c_args: qjs_c_args, diff --git a/quickjs.h b/quickjs.h index e7c1b96da..0a42abfe6 100644 --- a/quickjs.h +++ b/quickjs.h @@ -111,6 +111,28 @@ extern "C" { # define JS_LIBC_EXTERN JS_EXTERN #endif +/* + * `JS_MODULE_EXTERN` -- helper macro that must be used to mark `js_init_module` + * and other public functions of the binary modules. See examples/ for examples + * of the usage. + * + * Windows note: -DQUICKJS_NG_MODULE_BUILD must be set when building a binary + * module to properly set __declspec. + */ +#ifdef QUICKJS_NG_PLAT_WIN32 +# ifdef QUICKJS_NG_MODULE_BUILD +# define JS_MODULE_EXTERN __declspec(dllexport) +# else +# define JS_MODULE_EXTERN __declspec(dllimport) +# endif +#else +# ifdef QUICKJS_NG_CC_GNULIKE +# define JS_MODULE_EXTERN __attribute__((visibility("default"))) +# else +# define JS_MODULE_EXTERN /* nothing */ +# endif +#endif /* QUICKJS_NG_PLAT_WIN32 */ + /* Borrowed from Folly */ #ifndef JS_PRINTF_FORMAT #ifdef _MSC_VER From 81371940eba4b5648644d2f5a70997ef46f56c34 Mon Sep 17 00:00:00 2001 From: Zurab Kvachadze Date: Wed, 19 Nov 2025 03:31:30 +0100 Subject: [PATCH 6/7] ci/meson: Set -Db_lundef=false when building with sanitisers under Clang Clang cannot handle building shared libraries with sanitizers and -Wl,--no-undefined (set by default unless explicitly disabled with -Db_lundef=false). This commit prefixes CI in case shared libraries are built with sanitisers. Signed-off-by: Zurab Kvachadze --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f37978dd..3507db09b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -648,11 +648,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++ @@ -662,6 +665,7 @@ jobs: - name: clang-cl+sanitize args: >- "-Db_sanitize=address,undefined" + "-Db_lundef=false" extra_envs: CC: clang-cl CXX: clang-cl From 0028b639419f23e0e80dc0fc83d65bf3814827fd Mon Sep 17 00:00:00 2001 From: Zurab Kvachadze Date: Sat, 1 Nov 2025 17:41:37 +0100 Subject: [PATCH 7/7] meson: Do not hardcode default_library=static During the build, the default library can be overridden via the -Ddefault_library=type flag. Presetting this key in meson.build makes life harder for distributions which almost always want to build shared libraries. Those requiring static libraries can always force that via the aforementioned flag. Signed-off-by: Zurab Kvachadze --- meson.build | 1 - 1 file changed, 1 deletion(-) diff --git a/meson.build b/meson.build index f1f14f6fa..bff731a23 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',