-
Notifications
You must be signed in to change notification settings - Fork 240
Add WASI reactor build and re-entrant event loop APIs #1308
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
Rework of #1307 - this exposes quickjs-ng as a library, with all of the C symbols properly exported. |
53c1239 to
f9b3d57
Compare
e63c573 to
cf50ed8
Compare
|
I tried minimizing (removing) the init_argv routine but had to add it back eventually. Setting up the module loader requires calling The
This lets hosts do: Now the question is how to reduce duplicated code by re-using the arg parsing logic from qjs.c. I am looking into that. Ok, I put a commit that does that as well. This is much more of a change than I had intended originally in #1307 - but this should at least be a good starting point to discuss what the "right" solution is. |
6cc707e to
aa950c0
Compare
|
This is quite a large change so I understand if you reject it outright, but for the sake of discussion... This pulls out all of the common logic from main() like parsing args into a struct, etc, so we can re-use it. Overall cleaner result but the changeset appears large (Due to moving things around) |
bnoordhuis
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly LGTM but I don't know about that last commit. @saghul?
I'd leave it out for now. Let's have the WASI reactor build brew a bit and we can DRY it out afterwards. |
|
@saghul fine by me - did you want me to force push with that commit removed? |
Yes please. |
Add js_std_loop_once() and js_std_poll_io() to quickjs-libc for embedding QuickJS in host environments where the host controls the event loop (browsers, Node.js, Deno, Bun, Go with wazero, etc). The standard js_std_loop() blocks until all work is complete, which freezes the host's event loop and prevents host callbacks from running. The new APIs enable cooperative scheduling: js_std_loop_once() - Run one iteration of the event loop (non-blocking) - Executes all pending promise jobs (microtasks) - Runs at most one expired timer callback - Returns >0: next timer fires in N ms, use setTimeout - Returns 0: more microtasks pending, call again immediately - Returns -1: idle, no pending work - Returns -2: error occurred js_std_poll_io(timeout_ms) - Poll for I/O and invoke read/write handlers - Separate from loop_once so host can call it only when I/O is ready - Avoids unnecessary poll() syscalls when host knows data is available - Required because loop_once only handles timers/microtasks, not I/O - Returns 0: success, -1: error, -2: exception in handler Add QJS_WASI_REACTOR cmake option that builds QuickJS as a WASI reactor module. Unlike the command model (which has _start and blocks in js_std_loop), reactors export library functions that can be called repeatedly by the host. Build: cmake -B build -DCMAKE_TOOLCHAIN_FILE=.../wasi-sdk.cmake -DQJS_WASI_REACTOR=ON cmake --build build --target qjs_wasi Output: qjs.wasm (reactor module with exported quickjs.h / quickjs-libc.h APIs) The reactor wasm is included in releases as qjs-wasi-reactor.wasm. Signed-off-by: Christian Stewart <[email protected]>
Address review feedback to eliminate code duplication between js_std_poll_io() and js_os_poll(). Both functions now call a shared js_os_poll_internal() with configurable behavior via flags: - JS_OS_POLL_RUN_TIMERS: process timer callbacks - JS_OS_POLL_WORKERS: include worker message pipes in poll - JS_OS_POLL_SIGNALS: check and dispatch pending signal handlers js_os_poll() passes all flags for full event loop behavior. js_std_poll_io() passes no flags to poll only I/O handlers, ensuring it does not unexpectedly run timers, worker handlers, or signal handlers. This reduces code by ~40 lines and ensures bug fixes apply to both paths. Signed-off-by: Christian Stewart <[email protected]>
Replace the long list of -Wl,--export=<symbol> flags with -Wl,--export-dynamic
which automatically exports all symbols with default visibility. Since JS_EXTERN
is defined as __attribute__((visibility("default"))), all public API functions
are exported automatically.
Only libc memory functions (malloc, free, realloc, calloc) still need explicit
exports since they don't have default visibility in wasi-libc.
This addresses review feedback about keeping export lists in sync manually and
reduces the CMakeLists.txt WASI reactor section from ~80 lines to ~15 lines.
Signed-off-by: Christian Stewart <[email protected]>
The WASI reactor build exports raw QuickJS C APIs, but setting up the module
loader requires calling JS_SetModuleLoaderFunc2() with js_module_loader - a C
function pointer that cannot be obtained or called from the host side (Go,
JavaScript, etc). Without the module loader, dynamic import() fails.
Add qjs_init_argv() to qjs.c which initializes the reactor with CLI argument
parsing (like main() but without blocking in the event loop). This:
- Creates runtime and context
- Sets up the module loader via JS_SetModuleLoaderFunc2()
- Sets up the promise rejection tracker
- Parses CLI flags: --std, -m/--module, -e/--eval, -I/--include
- Loads and evaluates the initial script file
The functions are added to qjs.c (guarded by #ifdef QJS_WASI_REACTOR) rather
than quickjs-libc.c because they depend on static functions in qjs.c like
eval_buf(), eval_file(), parse_limit(), and JS_NewCustomContext().
Exported functions:
- qjs_init() - Initialize with default args
- qjs_init_argv(argc, argv) - Initialize with CLI args
- qjs_get_context() - Get JSContext* for use with js_std_loop_once etc
- qjs_destroy() - Cleanup runtime
Example usage from host:
qjs_init_argv(3, ["qjs", "--std", "/boot/script.js"])
while running:
result = js_std_loop_once(qjs_get_context())
// handle result
qjs_destroy()
Signed-off-by: Christian Stewart <[email protected]>
nanosleep can return early with EINTR when interrupted by a signal. Loop until the sleep completes or a new signal arrives, using os_pending_signals to detect signal state changes. Signed-off-by: Christian Stewart <[email protected]>
aa950c0 to
828729c
Compare
|
@saghul all done and added +1 commit to address the nanosleep PR comment |
bnoordhuis
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks!
Want to give it one more quick look-over since it's a biggish change, @saghul?
saghul
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, nice work!
Add js_std_loop_once() and js_std_poll_io() functions to quickjs-libc for re-entrant event loop control. These enable embedding QuickJS in host environments where the host controls scheduling (browsers, Node.js, Deno, C programs).
js_std_loop_once() runs one iteration of the event loop:
js_std_poll_io() polls for I/O and invokes read/write handlers:
Add QJS_WASI_REACTOR cmake option that builds QuickJS as a WASI reactor module, exporting the quickjs.h and quickjs-libc.h library functions. Unlike the command model (which has _start and blocks), reactors export functions that can be called repeatedly by the host.
Build with:
cmake -B build -DCMAKE_TOOLCHAIN_FILE=.../wasi-sdk.cmake -DQJS_WASI_REACTOR=ON
cmake --build build --target qjs_wasi
Output: qjs.wasm (reactor module)
The reactor wasm will be included in releases as qjs-wasi-reactor.wasm.