Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ See docs/process.md for more on how version tagging works.
FS-backend handler signature changed from `poll(stream, timeout)` to
`poll(stream)` returning the current readiness mask; out-of-tree custom FS
backends with a `poll` handler must update. (#27226)
- Added support for `epoll` (`epoll_create1`/`epoll_ctl`/`epoll_wait`/
`epoll_pwait`) on the legacy (non-WASMFS) JS filesystem, including
level- and edge-triggered modes, `EPOLLONESHOT`, `EPOLLEXCLUSIVE`,
`EPOLLRDHUP`, nesting, and blocking waits under `PROXY_TO_PTHREAD`,
`ASYNCIFY`, and `JSPI`. Also added `emscripten_epoll_set_callback`
(in the new `<emscripten/epoll.h>`, experimental), a non-blocking variant
that delivers an epoll set's readiness to a JS callback with no
`ASYNCIFY`/`JSPI`. (#27207)
- compiler-rt and libunwind were updated to LLVM 22.1.8. (#27245, #27246)
- `-fcoverage-mapping` is currently broken due to a mismatch between the version
of LLVM used and the imported version of compiler-rt. We hope to fix this
Expand All @@ -54,6 +62,15 @@ See docs/process.md for more on how version tagging works.
process, or pthreads required. Supports incoming and outgoing TCP, UDP, IPv6,
and `-pthread` with `PROXY_TO_PTHREAD`. Uses the public node APIs where
available, falling back to `tcp_wrap`/`udp_wrap` on older Node.js. (#27080)
- The socket data syscalls (`connect`, `accept`, `recv*`, `send*`) on the legacy
(non-WASMFS) JS filesystem now block on the per-inode readiness wait-queue
rather than always returning `EAGAIN`, so blocking sockets work under
`PROXY_TO_PTHREAD`, `ASYNCIFY`, and `JSPI` (purely-synchronous builds keep the
old immediate-`EAGAIN` behaviour). `MSG_DONTWAIT` is honored for a single
non-blocking `recv`/`send`. (#NNNNN)
- Added `emscripten_async_call_promising`, a variant of `emscripten_async_call`
whose callback may suspend the wasm stack (e.g. perform blocking calls) under
`JSPI`. Without `JSPI` it is identical to `emscripten_async_call`. (#NNNNN)
- The following symbols are no longer included in `INCOMING_MODULE_JS_API`
by default:
- GL_MAX_TEXTURE_IMAGE_UNITS
Expand Down
547 changes: 547 additions & 0 deletions src/lib/libepoll.js

Large diffs are not rendered by default.

20 changes: 16 additions & 4 deletions src/lib/libeventloop.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,9 @@ LibraryJSEventLoop = {
clearInterval(id);
},

emscripten_async_call__deps: ['$safeSetTimeout', '$safeRequestAnimationFrame'],
emscripten_async_call: (func, arg, millis) => {
var wrapper = () => {{{ makeDynCall('vp', 'func') }}}(arg);

$scheduleAsyncCall__internal: true,
$scheduleAsyncCall__deps: ['$safeSetTimeout', '$safeRequestAnimationFrame'],
$scheduleAsyncCall: (wrapper, millis) => {
if (millis >= 0
#if ENVIRONMENT_MAY_BE_NODE
// node does not support requestAnimationFrame
Expand All @@ -186,6 +185,19 @@ LibraryJSEventLoop = {
}
},

emscripten_async_call__deps: ['$scheduleAsyncCall'],
emscripten_async_call: (func, arg, millis) => {
scheduleAsyncCall(() => {{{ makeDynCall('vp', 'func') }}}(arg), millis);
},

// Like emscripten_async_call, but the callback is dispatched so it may suspend
// the wasm stack (e.g. perform blocking calls) under JSPI. Without JSPI this
// is identical to emscripten_async_call.
emscripten_async_call_promising__deps: ['$scheduleAsyncCall'],
emscripten_async_call_promising: (func, arg, millis) => {
scheduleAsyncCall(() => {{{ makeDynCall('vp', 'func', ASYNCIFY == 2) }}}(arg), millis);
},

$registerPostMainLoop: (f) => {
// Does nothing unless $MainLoop is included/used.
typeof MainLoop != 'undefined' && MainLoop.postMainLoop.push(f);
Expand Down
58 changes: 58 additions & 0 deletions src/lib/libfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,64 @@
*/

var LibraryFS = {
// Retry a stream op until it stops reporting EAGAIN, blocking on the stream
// node's readiness wait-queue in between (the same queue poll()/epoll consume
// and that SOCKFS/PIPEFS emit into). `op` attempts the operation once and
// returns the syscall result, throwing FS.ErrnoError (EAGAIN = would-block)
// exactly as the underlying sock/stream op already does; the helper owns the
// blocking-vs-nonblocking decision from the stream's own O_NONBLOCK flag (or a
// per-call `nonblocking` hint for MSG_DONTWAIT). It is fd-generic: sockets,
// pipes and ttys all share it. Callers are __async:'auto', so a ready op
// resolves synchronously and only a genuine would-block yields a Promise.
//
// The first op() attempt runs to completion before we register the waiter, so
// (JS being single-threaded) no readiness event can slip in between - the same
// safety $doPollAsync relies on. On a purely-synchronous build there is no way
// to suspend, so it degrades to a single attempt (status quo EAGAIN).
$streamWaitOp__internal: true,
$streamWaitOp__deps: ['$FS'],
$streamWaitOp: (fd, op, nonblocking = 0) => {
function attempt() {
try {
return op();
} catch (e) {
if (typeof FS == 'undefined' || e.name !== 'ErrnoError') throw e;
return -e.errno;
}
}
var r = attempt();
#if ASYNCIFY || PTHREADS
#if !ASYNCIFY
// PTHREADS without ASYNCIFY/JSPI: the stack can only suspend when this runs
// as the target of a worker's sync-proxy, whose result is delivered by
// promise resolution. A direct main-thread call cannot block, so degrade to
// a single attempt (mirrors $doPollAsync's isAsyncContext gate).
if (!PThread.currentProxiedOperationCallerThread) return r;
#endif
var stream = FS.getStream(fd);
var wait = r == -{{{ cDefs.EAGAIN }}} && !nonblocking && !(stream.flags & {{{ cDefs.O_NONBLOCK }}});
#if ASYNCIFY
// JSPI/Asyncify: handleAsync accepts a plain value or a Promise, so a ready
// op resolves synchronously without suspending.
if (!wait) return r;
#else
// A proxied result must be a thenable even when ready synchronously.
if (!wait) return Promise.resolve(r);
#endif
return new Promise((resolve) => {
var reg;
function wake() {
var r = attempt();
if (r == -{{{ cDefs.EAGAIN }}}) return; // spurious wake: keep waiting
reg.listeners.delete(reg.entry);
resolve(r);
}
reg = stream.node.addListener(wake);
});
#else
return r;
#endif
},
$FS__deps: ['$randomFill', '$PATH', '$PATH_FS', '$TTY', '$MEMFS',
'$FS_modeStringToFlags',
'$FS_fileDataToTypedArray',
Expand Down
4 changes: 4 additions & 0 deletions src/lib/libsigs.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ sigs = {
__syscall_epoll_create1__sig: 'ii',
__syscall_epoll_ctl__sig: 'iiiip',
__syscall_epoll_pwait__sig: 'iipiipp',
__syscall_epoll_pwait_nonblocking__sig: 'iipi',
__syscall_faccessat__sig: 'iipii',
__syscall_fallocate__sig: 'iiijj',
__syscall_fchdir__sig: 'ii',
Expand Down Expand Up @@ -324,6 +325,7 @@ sigs = {
_emscripten_create_wasm_worker__sig: 'iipip',
_emscripten_dlopen_js__sig: 'vpppp',
_emscripten_dlsync_threads__sig: 'v',
_emscripten_epoll_delivery_done__sig: 'vi',
_emscripten_fetch_get_response_headers__sig: 'pipp',
_emscripten_fetch_get_response_headers_length__sig: 'pi',
_emscripten_fs_load_embedded_files__sig: 'vp',
Expand Down Expand Up @@ -588,6 +590,7 @@ sigs = {
emscripten_asm_const_ptr__sig: 'pppp',
emscripten_asm_const_ptr_sync_on_main_thread__sig: 'pppp',
emscripten_async_call__sig: 'vppi',
emscripten_async_call_promising__sig: 'vppi',
emscripten_async_load_script__sig: 'vppp',
emscripten_async_run_script__sig: 'vpi',
emscripten_async_wget__sig: 'vpppp',
Expand Down Expand Up @@ -637,6 +640,7 @@ sigs = {
emscripten_destroy_web_audio_node__sig: 'vi',
emscripten_destroy_worker__sig: 'vi',
emscripten_enter_soft_fullscreen__sig: 'ipp',
emscripten_epoll_set_callback__sig: 'iiipp',
emscripten_err__sig: 'vp',
emscripten_errn__sig: 'vpp',
emscripten_exit_fullscreen__sig: 'i',
Expand Down
Loading
Loading