From 44b7d6bbba7ead7c2f2af967a130efb0a2c1cada 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 cf6bf1754..a060254cc 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() @@ -354,7 +359,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 @@ -364,17 +369,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 55f64a2adf9e2df121059017f281190d9e66962c 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 | 61 ++++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/meson.build b/meson.build index 05e74025d..f69dcf6db 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,26 @@ qjs_dep = declare_dependency( 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( @@ -282,12 +293,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 +318,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 +399,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 +515,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 +625,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 +644,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 ca095c8d01ffcdef7ff12fd3bd9bd23be3b7b4ef 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 a060254cc..410d4674c 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() @@ -344,9 +349,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() @@ -359,7 +364,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 @@ -369,7 +374,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 # @@ -387,7 +392,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 # @@ -399,7 +404,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 @@ -407,7 +412,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 @@ -416,7 +421,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() @@ -428,7 +433,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() @@ -440,7 +445,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 136e2416f937e794b6f828765e587b5a80d91fd7 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 the renamed QJS_EXTERN, QJS_LIBC_EXTERN and QJS_FORCE_INLINE. Signed-off-by: Zurab Kvachadze --- CMakeLists.txt | 15 +++++++++- meson.build | 19 ++++++++++-- quickjs-libc.h | 44 ++++++++++++--------------- quickjs.h | 80 ++++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 124 insertions(+), 34 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 410d4674c..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... @@ -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) @@ -282,7 +287,7 @@ 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 $ $ @@ -290,6 +295,14 @@ 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}) diff --git a/meson.build b/meson.build index f69dcf6db..0710a73bb 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: ['-DQUICKJS_NG_DLL'], gnu_symbol_visibility: 'hidden', install: true, @@ -195,7 +202,15 @@ 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(), @@ -346,7 +361,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 58c2525bc..06aae5b83 100644 --- a/quickjs-libc.h +++ b/quickjs-libc.h @@ -34,49 +34,41 @@ 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, JSValueConst attributes); -JS_EXTERN int js_module_check_attributes(JSContext *ctx, void *opaque, +JS_LIBC_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, +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_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 d66051324..ebfa8eca5 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; @@ -1325,7 +1396,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 d46376babeef4fc5ff9fc5395912c87256a1adb0 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 | 21 ++++++++++++--------- examples/fib.c | 19 +++---------------- examples/meson.build | 12 ++++++------ examples/point.c | 14 ++++---------- meson.build | 13 ++++--------- quickjs.h | 22 ++++++++++++++++++++++ 6 files changed, 51 insertions(+), 50 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b357449e1..5cc76bd04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -294,6 +294,11 @@ target_include_directories(qjs PUBLIC ) target_link_libraries(qjs PUBLIC ${qjs_libs}) target_link_libraries(qjs PRIVATE $) +if(QJS_BUILD_EXAMPLES) + # We shouldn't really support building examples with static qjs.. + # Anyways, this is required for fib.so and point.so to link properly. + set_target_properties(qjs PROPERTIES POSITION_INDEPENDENT_CODE ON) +endif() # Pass a compiler definition so that Windows gets its declspec's right. get_target_property(QJS_LIB_TYPE qjs TYPE) @@ -432,10 +437,9 @@ if(QJS_BUILD_EXAMPLES) 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) + target_compile_definitions(fib PRIVATE QUICKJS_NG_MODULE_BUILD) + if(APPLE) target_link_options(fib PRIVATE -undefined dynamic_lookup) endif() @@ -444,10 +448,9 @@ if(QJS_BUILD_EXAMPLES) 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) + target_compile_definitions(point PRIVATE QUICKJS_NG_MODULE_BUILD) + if(APPLE) target_link_options(point PRIVATE -undefined dynamic_lookup) endif() @@ -457,7 +460,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_compile_definitions(test_fib PRIVATE ${qjs_defines} QUICKJS_NG_MODULE_BUILD) target_link_libraries(test_fib PRIVATE qjs) endif() 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..dc7512032 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -3,9 +3,9 @@ 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', + c_args: qjs_module_args, + dependencies: qjs_dep, ) shared_module( @@ -13,7 +13,7 @@ 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', + c_args: qjs_module_args, + dependencies: qjs_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 0710a73bb..58ee2cb17 100644 --- a/meson.build +++ b/meson.build @@ -168,6 +168,8 @@ if not qjs_parser qjs_c_args += ['-DQJS_DISABLE_PARSER'] endif +qjs_module_args = ['-DQUICKJS_NG_MODULE_BUILD'] + cutils_lib = static_library( 'cutils', 'cutils.c', @@ -181,13 +183,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'], @@ -213,7 +208,7 @@ 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, ) @@ -660,7 +655,7 @@ if examples.allowed() 'examples/fib.c', 'gen/test_fib.c', - c_args: qjs_c_args, + c_args: [qjs_c_args, qjs_module_args], dependencies: [qjs_dep, qjs_libc_dep], export_dynamic: true, ) diff --git a/quickjs.h b/quickjs.h index ebfa8eca5..2dd41e42b 100644 --- a/quickjs.h +++ b/quickjs.h @@ -115,6 +115,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 77a85114334ff1f1704e398ce2089147466ed1f2 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 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 From 02070fd9f8dbec881801408bcbd765803c42c1e6 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 58ee2cb17..fa80447ec 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',