From 83099c9392fd6918aa2a702dcbe46e9b31f4379a Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Fri, 3 Apr 2026 08:58:02 -0700 Subject: [PATCH 01/42] [test] Add overrides to SingleLineTestResult to support expected failures and unexpected successes. NFC (#26539) I suppose this went unnoticed upstream in python because expected failures and unexpected successes are relatively unusual. We have a bunch of them in the posixtest suite here. --- test/single_line_runner.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/single_line_runner.py b/test/single_line_runner.py index a9bf6d0480cc4..e6a782172ddfa 100644 --- a/test/single_line_runner.py +++ b/test/single_line_runner.py @@ -4,6 +4,7 @@ # found in the LICENSE file. import shutil +from unittest import TextTestResult from color_runner import ColorTextResult, ColorTextRunner @@ -67,6 +68,27 @@ def printErrors(self): self.stream.write('\n') super().printErrors() + # Override addExpectedFailure and addUnexpectedSuccess since, for some reason + # these methods in TextTestResult do not use `_write_status` like the other ones. + # TODO(sbc): Send a patch to upstream python to sue `_write_status` in TextTestResult. + def addExpectedFailure(self, test, err): + super(TextTestResult, self).addExpectedFailure(test, err) + if self.showAll: + self._write_status(test, "expected failure") + self.stream.flush() + elif self.dots: + self.stream.write("x") + self.stream.flush() + + def addUnexpectedSuccess(self, test): + super(TextTestResult, self).addUnexpectedSuccess(test) + if self.showAll: + self._write_status(test, "unexpected success") + self.stream.flush() + elif self.dots: + self.stream.write("u") + self.stream.flush() + class SingleLineTestRunner(ColorTextRunner): """Subclass of TextTestResult that uses SingleLineTestResult""" From 9a23e374b4e57af0491cdb0097ef5f5d21c50ef0 Mon Sep 17 00:00:00 2001 From: emscripten-bot <179889221+emscripten-bot@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:15:34 -0700 Subject: [PATCH 02/42] Mark 5.0.5 as released (#26620) Update changelog and emscripten-version.txt [ci skip] --- ChangeLog.md | 5 ++++- emscripten-version.txt | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 86c661d6dae88..4f39c28c75cc3 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -18,8 +18,11 @@ to browse the changes between the tags. See docs/process.md for more on how version tagging works. -5.0.5 (in development) +5.0.6 (in development) ---------------------- + +5.0.5 - 04/03/26 +---------------- - C++ exceptions are now always thrown as CppException objects rather than raw pointers/numbers. However, the `.message` and `.stack` fields of the thrown object will only be populated if `-sEXCEPTION_STACK_TRACES` is set. (#26523) diff --git a/emscripten-version.txt b/emscripten-version.txt index 887cf5c9b4315..aff2882b0fa03 100644 --- a/emscripten-version.txt +++ b/emscripten-version.txt @@ -1 +1 @@ -5.0.5-git +5.0.6-git From 9c765740e331bdffe20c4c50f36a84f48a549076 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Fri, 3 Apr 2026 11:38:55 -0700 Subject: [PATCH 03/42] Remove dead code from src/deterministic.js. NFC (#26618) The `hashMemory` function was added back 2013 (156fe33bf) but has never had any testing of documenation. The `hashString` function was added in 2015 (e1344f0df) also without any testing of example uses. I'm hoping to simply remove the `-sDETERMINISTIC` setting, but as part of that I'm first trying to strip it down to whats actually used/tested. --- src/deterministic.js | 21 ++------------------- tools/link.py | 3 +-- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/src/deterministic.js b/src/deterministic.js index 874f8407005c5..9c4b3ca5bd74f 100644 --- a/src/deterministic.js +++ b/src/deterministic.js @@ -23,22 +23,5 @@ Date.now = deterministicNow; // See getPerformanceNow in parseTools.mjs for how we deal with this. if (globalThis.performance) performance.now = deterministicNow; -Module['thisProgram'] = 'thisProgram'; // for consistency between different builds than between runs of the same build - -function hashMemory(id) { - var ret = 0; - var len = _sbrk(0); - for (var i = 0; i < len; i++) { - ret = (ret*17 + HEAPU8[i])|0; - } - return id + ':' + ret; -} - -function hashString(s) { - var ret = 0; - for (var i = 0; i < s.length; i++) { - ret = (ret*17 + s.charCodeAt(i))|0; - } - return ret; -} - +// for consistency between different builds than between runs of the same build +Module['thisProgram'] = 'thisProgram'; diff --git a/tools/link.py b/tools/link.py index eb850b597b87e..aaffe9eae7066 100644 --- a/tools/link.py +++ b/tools/link.py @@ -1286,8 +1286,7 @@ def limit_incoming_module_api(): settings.ALLOW_TABLE_GROWTH = 1 # various settings require sbrk() access - if settings.DETERMINISTIC or \ - settings.EMSCRIPTEN_TRACING or \ + if settings.EMSCRIPTEN_TRACING or \ settings.SAFE_HEAP or \ settings.MEMORYPROFILER: settings.REQUIRED_EXPORTS += ['sbrk'] From 07d59b6710a7e8459e1bede89cea196099204bb2 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sat, 4 Apr 2026 11:50:49 +0200 Subject: [PATCH 04/42] [WasmFS] Add missing dependency in the Node backend. NFC (#26596) Split out from #24733. --- src/lib/libwasmfs_node.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib/libwasmfs_node.js b/src/lib/libwasmfs_node.js index 02295c10a164f..82da9077d92f9 100644 --- a/src/lib/libwasmfs_node.js +++ b/src/lib/libwasmfs_node.js @@ -157,15 +157,15 @@ addToLibrary({ }, _wasmfs_node_truncate__i53abi: true, - _wasmfs_node_truncate__deps : ['$wasmfsTry'], - _wasmfs_node_truncate : (path_p, len) => { + _wasmfs_node_truncate__deps: ['$wasmfsTry'], + _wasmfs_node_truncate: (path_p, len) => { if (isNaN(len)) return -{{{ cDefs.EOVERFLOW }}}; return wasmfsTry(() => fs.truncateSync(UTF8ToString(path_p), len)); }, _wasmfs_node_ftruncate__i53abi: true, - _wasmfs_node_ftruncate__deps : ['$wasmfsTry'], - _wasmfs_node_ftruncate : (fd, len) => { + _wasmfs_node_ftruncate__deps: ['$wasmfsTry'], + _wasmfs_node_ftruncate: (fd, len) => { if (isNaN(len)) return -{{{ cDefs.EOVERFLOW }}}; return wasmfsTry(() => fs.ftruncateSync(fd, len)); }, @@ -193,7 +193,7 @@ addToLibrary({ }); }, - _wasmfs_node_close__deps: [], + _wasmfs_node_close__deps: ['$wasmfsTry'], _wasmfs_node_close: (fd) => { return wasmfsTry(() => { fs.closeSync(fd); @@ -212,8 +212,8 @@ addToLibrary({ }); }, - _wasmfs_node_write__deps : ['$wasmfsTry'], - _wasmfs_node_write : (fd, buf_p, len, pos, nwritten_p) => { + _wasmfs_node_write__deps: ['$wasmfsTry'], + _wasmfs_node_write: (fd, buf_p, len, pos, nwritten_p) => { return wasmfsTry(() => { // TODO: Cache open file descriptors to guarantee that opened files will // still exist when we try to access them. From be9f95b7fecd6be196a8488a85484b25c42743cc Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sat, 4 Apr 2026 11:52:17 +0200 Subject: [PATCH 05/42] [WasmFS] Make the Node backend optional for web builds (#26608) Previously, the WasmFS Node backend assumed Node.js was always available. This change allows building binaries that include Node backend stubs, so they can run in web environments. Split out from #24733. --- src/lib/libwasmfs_node.js | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/lib/libwasmfs_node.js b/src/lib/libwasmfs_node.js index 82da9077d92f9..7f4680bc3013d 100644 --- a/src/lib/libwasmfs_node.js +++ b/src/lib/libwasmfs_node.js @@ -4,8 +4,8 @@ * SPDX-License-Identifier: MIT */ -addToLibrary({ - $wasmfsNodeIsWindows: !!process.platform.match(/^win/), +var wasmFSNodeLibrary = { + $wasmfsNodeIsWindows: "!!process.platform.match(/^win/)", $wasmfsNodeConvertNodeCode__deps: ['$ERRNO_CODES'], $wasmfsNodeConvertNodeCode: (e) => { @@ -29,8 +29,8 @@ addToLibrary({ $wasmfsNodeFixStat__deps: ['$wasmfsNodeIsWindows'], $wasmfsNodeFixStat: (stat) => { if (wasmfsNodeIsWindows) { - // Node.js on Windows never represents permission bit 'x', so - // propagate read bits to execute bits + // Windows does not report the 'x' permission bit, so propagate read + // bits to execute bits. stat.mode |= (stat.mode & {{{ cDefs.S_IRUGO }}}) >> 2; } return stat; @@ -222,5 +222,30 @@ addToLibrary({ // implicitly return 0 }); }, +}; + +#if !ENVIRONMENT_MAY_BE_NODE +function makeStub(x, library) { + if (isJsOnlySymbol(x) || isDecorator(x)) { + return; + } + + var t = library[x]; + if (typeof t == 'string') return; + t = t.toString(); + + delete library[x + '__i53abi']; + delete library[x + '__deps']; + library[x] = modifyJSFunction(t, (args, body) => { + return `(${args}) => {\n` + + (ASSERTIONS ? "abort('attempt to call Node.js backend function without ENVIRONMENT_MAY_BE_NODE');\n" : '') + + '}'; + }); +} + +for (const name of Object.keys(wasmFSNodeLibrary)) { + makeStub(name, wasmFSNodeLibrary); +} +#endif -}); +addToLibrary(wasmFSNodeLibrary); From a7804eae3aefb0d174b63cdf6b3616a7cae86c27 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sat, 4 Apr 2026 12:53:08 +0200 Subject: [PATCH 06/42] [WasmFS] Ensure error codes are positive in `_wasmfs_node_*` APIs (#26616) Follow-up to #26598. --- src/lib/libwasmfs_node.js | 4 ++-- system/lib/wasmfs/backends/node_backend.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/libwasmfs_node.js b/src/lib/libwasmfs_node.js index 7f4680bc3013d..97d83a393cb08 100644 --- a/src/lib/libwasmfs_node.js +++ b/src/lib/libwasmfs_node.js @@ -159,14 +159,14 @@ var wasmFSNodeLibrary = { _wasmfs_node_truncate__i53abi: true, _wasmfs_node_truncate__deps: ['$wasmfsTry'], _wasmfs_node_truncate: (path_p, len) => { - if (isNaN(len)) return -{{{ cDefs.EOVERFLOW }}}; + if (isNaN(len)) return {{{ cDefs.EOVERFLOW }}}; return wasmfsTry(() => fs.truncateSync(UTF8ToString(path_p), len)); }, _wasmfs_node_ftruncate__i53abi: true, _wasmfs_node_ftruncate__deps: ['$wasmfsTry'], _wasmfs_node_ftruncate: (fd, len) => { - if (isNaN(len)) return -{{{ cDefs.EOVERFLOW }}}; + if (isNaN(len)) return {{{ cDefs.EOVERFLOW }}}; return wasmfsTry(() => fs.ftruncateSync(fd, len)); }, diff --git a/system/lib/wasmfs/backends/node_backend.cpp b/system/lib/wasmfs/backends/node_backend.cpp index b6b95eeb61b40..4bfd0f12cd70a 100644 --- a/system/lib/wasmfs/backends/node_backend.cpp +++ b/system/lib/wasmfs/backends/node_backend.cpp @@ -124,9 +124,9 @@ class NodeFile : public DataFile { int setSize(off_t size) override { if (state.isOpen()) { - return _wasmfs_node_ftruncate(state.getFD(), size); + return -_wasmfs_node_ftruncate(state.getFD(), size); } - return _wasmfs_node_truncate(state.path.c_str(), size); + return -_wasmfs_node_truncate(state.path.c_str(), size); } int open(oflags_t flags) override { return state.open(flags); } From 5a25d4245d9a5c55aaa56b91c7bcbe1e0f217af4 Mon Sep 17 00:00:00 2001 From: BoryaGames <67229357+BoryaGames@users.noreply.github.com> Date: Sun, 5 Apr 2026 22:25:23 +0500 Subject: [PATCH 07/42] Fix asyncify mistake in docs (#26627) There's two methods with the same name - `fetch_v2`, I think the first one was supposed to be called `fetch_v1`. Edit: I also have fixed a spelling mistake in the word `included`. --- site/source/docs/porting/asyncify.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/source/docs/porting/asyncify.rst b/site/source/docs/porting/asyncify.rst index 1961ce9611da6..2209d8e990cac 100644 --- a/site/source/docs/porting/asyncify.rst +++ b/site/source/docs/porting/asyncify.rst @@ -168,7 +168,7 @@ Marking JS library functions as async If you mark a JS library function as async using the ``__async`` decorator then the compiler will take a care of all the details of using the ``Asyncify`` API -for you. The function will also automatically be incldued in +for you. The function will also automatically be included in :ref:`ASYNCIFY_IMPORTS`. All you need to do is write normal async JS function (either using the explict ``async`` JS keyword or returning a ``Promise`` object). For example: @@ -177,7 +177,7 @@ object). For example: addToLibrary({ fetch_v1__async: 'auto', - fetch_v2: async (url) => { + fetch_v1: async (url) => { const response = await fetch(UTF8ToString(url); const json_data = await response.json(); return stringToNewUTF8(json_data); From bee9a1c2bc39cd33ff2f2b6b4642951eb473dcea Mon Sep 17 00:00:00 2001 From: Konstantin Podsvirov Date: Sun, 5 Apr 2026 22:45:51 +0300 Subject: [PATCH 08/42] Fix mistake in `create_entry_points.py` (#26623) `.pa1` -> `.ps1` --- tools/maint/create_entry_points.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/maint/create_entry_points.py b/tools/maint/create_entry_points.py index 0c7ca5fbec6e5..9a97b98f5fac1 100755 --- a/tools/maint/create_entry_points.py +++ b/tools/maint/create_entry_points.py @@ -121,7 +121,7 @@ def generate_entry_points(cmd, path): shutil.copyfile(windows_exe, launcher + '.exe') else: write_file(launcher + '.bat', bat_data) - write_file(launcher + '.pa1', ps1_data) + write_file(launcher + '.ps1', ps1_data) generate_entry_points(entry_points, os.path.join(__scriptdir__, 'run_python')) generate_entry_points(compiler_entry_points, os.path.join(__scriptdir__, 'run_python_compiler')) From 557c3fb0e32b4f0633b8ced2f885b18b65e10d5e Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Mon, 6 Apr 2026 18:19:26 +0200 Subject: [PATCH 09/42] [WasmFS] Use `const uint8_t*` in JS API impl. NFC (#24706) In line with #23825. --- system/lib/wasmfs/js_api.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/lib/wasmfs/js_api.cpp b/system/lib/wasmfs/js_api.cpp index dba5fdb6dd93d..48a241768242e 100644 --- a/system/lib/wasmfs/js_api.cpp +++ b/system/lib/wasmfs/js_api.cpp @@ -64,7 +64,7 @@ int _wasmfs_read_file(const char* path, uint8_t** out_buf, off_t* out_size) { // Writes to a file, possibly creating it, and returns the number of bytes // written successfully. If the file already exists, appends to it. -int _wasmfs_write_file(const char* pathname, char* data, size_t data_size) { +int _wasmfs_write_file(const char* pathname, const uint8_t* data, size_t data_size) { auto parsedParent = path::parseParent(pathname); if (parsedParent.getError()) { return 0; @@ -100,7 +100,7 @@ int _wasmfs_write_file(const char* pathname, char* data, size_t data_size) { } auto offset = lockedFile.getSize(); - auto result = lockedFile.write((uint8_t*)data, data_size, offset); + auto result = lockedFile.write(data, data_size, offset); if (result != __WASI_ERRNO_SUCCESS) { return 0; } From 49f63cab344c3bf497ad1cdd62363d15e2864f8c Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Mon, 6 Apr 2026 18:24:21 +0200 Subject: [PATCH 10/42] [WasmFS] Make the Node backend multi-env compatible (#26624) Follow-up to #26608. --- src/lib/libwasmfs_node.js | 2 +- test/test_browser.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/lib/libwasmfs_node.js b/src/lib/libwasmfs_node.js index 97d83a393cb08..8922899e31d22 100644 --- a/src/lib/libwasmfs_node.js +++ b/src/lib/libwasmfs_node.js @@ -5,7 +5,7 @@ */ var wasmFSNodeLibrary = { - $wasmfsNodeIsWindows: "!!process.platform.match(/^win/)", + $wasmfsNodeIsWindows: "!!globalThis.process?.platform.match(/^win/)", $wasmfsNodeConvertNodeCode__deps: ['$ERRNO_CODES'], $wasmfsNodeConvertNodeCode: (e) => { diff --git a/test/test_browser.py b/test/test_browser.py index 7f43824793515..bf76a59441722 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -5347,6 +5347,35 @@ def test_wasmfs_opfs_errors(self): args = ['-sWASMFS', '-pthread', '-sPROXY_TO_PTHREAD', '--post-js', postjs] self.btest(test, cflags=args, expected='0') + def test_wasmfs_multi_environment(self): + # Test that WasmFS's Node backend can be enabled conditionally, allowing + # the same binaries to run on both web and Node.js environments. + create_file('main.c', r''' + #include + #include + #include + + #include + #include + + EM_JS(bool, is_node, (), { return ENVIRONMENT_IS_NODE; }); + + // This is equivalent to building with `-sWASMFS -sNODERAWFS`, except + // that the Wasm binary can also be used on the web. + backend_t wasmfs_create_root_dir() { + return is_node() ? wasmfs_create_node_backend("") + : wasmfs_create_memory_backend(); + } + + int main(int argc, char** argv) { + printf("testing access to /tmp\n"); + int rtn = access("/tmp", F_OK); + assert(rtn == 0); + return 0; + } + ''') + self.btest_exit('main.c', cflags=['-sWASMFS', '-sENVIRONMENT=web,node']) + @no_firefox('no 4GB support yet') def test_emmalloc_memgrowth(self): if not self.is_4gb(): From ecb9e39053ac695fab0b9aa2d208d4e3c77bc514 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Mon, 6 Apr 2026 11:46:10 -0700 Subject: [PATCH 11/42] Add docs/design directory along with one initial design doc. (#26621) --- docs/design/01-precise-futex-wakeups.md | 140 ++++++++++++++++++++++++ docs/design/README.md | 10 ++ 2 files changed, 150 insertions(+) create mode 100644 docs/design/01-precise-futex-wakeups.md create mode 100644 docs/design/README.md diff --git a/docs/design/01-precise-futex-wakeups.md b/docs/design/01-precise-futex-wakeups.md new file mode 100644 index 0000000000000..6a8beb210e8b4 --- /dev/null +++ b/docs/design/01-precise-futex-wakeups.md @@ -0,0 +1,140 @@ +# Design Doc: Precise Futex Wakeups in Emscripten + +- **Status**: Draft +- **Bug**: https://github.com/emscripten-core/emscripten/issues/26633 + +## Context +Currently, `emscripten_futex_wait` (in +`system/lib/pthread/emscripten_futex_wait.c`) relies on a periodic wakeup loop +for pthreads and the main runtime thread. This is done for two primary reasons: + +1. **Thread Cancellation**: To check if the calling thread has been cancelled while it is blocked. +2. **Main Runtime Thread Events**: To allow the main runtime thread (even when not the main browser thread) to process its mailbox/event queue. + +The current implementation uses a 1ms wakeup interval for the main runtime +thread and a 100ms interval for cancellable pthreads. This leads to unnecessary +CPU wakeups and increased latency for events. + +## Goals +- Remove the periodic wakeup loop from `emscripten_futex_wait`. +- Implement precise, event-driven wakeups for cancellation and mailbox events. +- Maintain the existing `emscripten_futex_wait` API signature. +- Focus implementation on threads that support `atomic.wait` (pthreads and workers). + +## Non-Goals +- **Main Browser Thread**: Changes to the busy-wait loop in `futex_wait_main_browser_thread` are out of scope. +- **Direct Atomics Usage**: Threads that call `atomic.wait` directly (bypassing `emscripten_futex_wait`) will remain un-interruptible. +- **Wasm Workers**: Wasm Worker do not have a `pthread` structure, they are not covered by this design. + +## Proposed Design + +The core idea is to allow "side-channel" wakeups (cancellation, mailbox events) +to interrupt the `atomic.wait` call by having the waker call `atomic.wake` on the +same address the waiter is currently blocked on. + +As part of this design we will need to explicitly state that +`emscripten_futex_wait` now supports spurious wakeups. i.e. it may return `0` +(success) even if the underlying futex was not explicitly woken by the +application. + +### 1. `struct pthread` Extensions +We will add the following fields to `struct pthread` (in +`system/lib/libc/musl/src/internal/pthread_impl.h`). All operations on these +fields must use `memory_order_seq_cst` to ensure the handshake is robust. + +```c +// The address the thread is currently waiting on in emscripten_futex_wait. +// NULL if the thread is not currently in a futex wait. +_Atomic(void*) waiting_on_address; + +// A counter that is incremented every time the thread wakes up from a futex wait. +// Used by wakers to ensure the target thread has actually acknowledged the wake. +_Atomic(uint32_t) wait_counter; + +// A bitmask of reasons why the thread was woken for a side-channel event. +_Atomic(uint32_t) wait_reasons; + +#define WAIT_REASON_CANCEL (1 << 0) +#define WAIT_REASON_MAILBOX (1 << 1) +``` + +### 2. Waiter Logic (`emscripten_futex_wait`) +The waiter will follow this logic (using `SEQ_CST` for all atomic accesses): + +1. **Pre-check**: Check `wait_reasons`. If non-zero, handle the reasons (e.g., process mailbox or handle cancellation). +2. **Publish**: Set `waiting_on_address = addr`. +3. **Counter Snapshot**: Read `current_counter = wait_counter`. +4. **Double-check**: This is critical to avoid the race where a reason was added just before `waiting_on_address` was set. If `wait_reasons` is now non-zero, clear `waiting_on_address` and go to step 1. +5. **Wait**: Call `ret = __builtin_wasm_memory_atomic_wait32(addr, val, timeout)`. +6. **Unpublish**: + - Set `waiting_on_address = NULL`. + - Atomically increment `wait_counter`. +7. **Post-check**: Check `wait_reasons`. If non-zero, handle the reasons. +8. **Return**: Return the result of the wait to the caller. + - If `ret == ATOMICS_WAIT_OK`, return `0`. + - If `ret == ATOMICS_WAIT_TIMED_OUT`, return `-ETIMEDOUT`. + - If `ret == ATOMICS_WAIT_NOT_EQUAL`, return `-EWOULDBLOCK`. + +Note: We do **not** loop internally if `ret == ATOMICS_WAIT_OK`. Even if we suspect the wake was caused by a side-channel event, we must return to the user to avoid "swallowing" a simultaneous real application wake that might not have changed the memory value. + +### 3. Waker Logic +When a thread needs to wake another thread for a side-channel event (e.g., in `pthread_cancel` or `em_task_queue_enqueue`): + +1. Atomically OR the appropriate bit into the target thread's `wait_reasons` (`SEQ_CST`). +2. Read `target_addr = target->waiting_on_address` (`SEQ_CST`). +3. If `target_addr` is not NULL: + - Read `start_c = target->wait_counter` (`SEQ_CST`). + - Enter a loop: + - Call `emscripten_futex_wake(target_addr, 1)`. + - Exit loop if `target->wait_counter != start_c` OR `target->waiting_on_address != target_addr`. + - **Yield**: Call `sched_yield()` (or a small sleep) to allow the target thread to proceed if it is currently being scheduled. + +### 4. Handling the Race Condition +The "Lost Wakeup" race is handled by the combination of: +- The waiter double-checking `wait_reasons` after publishing its `waiting_on_address`. +- The waker looping `atomic.wake` until the waiter increments its `wait_counter`. + +Even if the waker's first `atomic.wake` occurs after the waiter's double-check +but *before* the waiter actually enters the `atomic.wait` instruction, the waker +will continue to loop and call `atomic.wake` again. The subsequent call(s) will +successfully wake the waiter once it is actually sleeping. + +Multiple wakers can safely call this logic simultaneously; they will all exit +the loop as soon as the waiter acknowledges the wake by incrementing the +counter. + +### 5. Overlapping and Spurious Wakeups +The design must handle cases where "real" wakeups (triggered by the application) and "side-channel" wakeups (cancellation/mailbox) occur simultaneously. + +1. **Spurious Wakeups for Other Threads**: If multiple threads are waiting on the same address (e.g., a shared mutex), a side-channel `atomic_wake(addr, 1)` targeted at Thread A might be delivered by the kernel to Thread B. + - **Thread B's response**: It will wake up, increment its `wait_counter`, see that its `wait_reasons` are empty, and return `0` to its caller. + - **Thread C (the waker)**: It will see that Thread A's `wait_counter` has *not* changed and `waiting_on_address` is still `addr`. It will therefore continue its loop and call `atomic_wake` again until Thread A is finally woken. + - **Result**: Thread B experiences a "spurious" wakeup. This is acceptable and expected behavior for futex-based synchronization. +2. **Handling Side-Channel Success**: If Thread A is woken by the side-channel, it handles the event and returns `0`. The user's code will typically see that its own synchronization condition is not yet met and immediately call `emscripten_futex_wait` again. This effectively "resumes" the wait from the user's perspective while having allowed the side-channel event to be processed. +3. **No Lost "Real" Wakeups**: By returning to the caller whenever `atomic.wait` returns `OK`, we ensure that we never miss or swallow a real application-level `atomic.wake`. + +### 6. Counter Wrap-around +The `wait_counter` is a `uint32_t` and will wrap around to zero after $2^{32}$ wakeups. This is safe because: +1. **Impossibility of Racing**: For the waker to "miss" a wake-up due to wrap-around, the waiter would have to wake up and re-enter a sleep state exactly $2^{32}$ times in the very brief window between the waker's `atomic_wake` and its subsequent check of `wait_counter`. Even at extreme wakeup frequencies (e.g., 1 million per second), this would take over an hour. +2. **Address Change Check**: The waker loop also checks `target->waiting_on_address != target_addr`. If the waiter wakes up and either stops waiting or starts waiting on a *different* address, the waker will exit the loop regardless of the counter value. + +### 6. Benefits +- **Lower Power Consumption**: Threads can sleep indefinitely (or for the full duration of a user-requested timeout) without periodic wakeups. +- **Lower Latency**: Mailbox events and cancellation requests are processed immediately rather than waiting for the next 1ms or 100ms tick. +- **Simpler Loop**: The complex logic for calculating remaining timeout slices in `emscripten_futex_wait` is removed. + +## Alternatives Considered +- **Signal-based wakeups**: Not currently feasible in Wasm as signals are not + implemented in a way that can interrupt `atomic.wait`. +- **A single global "wake-up" address per thread**: This would require the + waiter to wait on *two* addresses simultaneously (the user's futex and its + own wakeup address), which `atomic.wait` does not support. The proposed + design works around this by having the waker use the *user's* futex address. + +## Security/Safety Considerations +- The `waiting_on_address` must be managed carefully to ensure wakers don't + call `atomic.wake` on stale addresses. The `wait_counter` and clearing the + address upon wake mitigate this. +- The waker loop should have a reasonable fallback (like a yield) to prevent a + busy-wait deadlock if the waiter is somehow prevented from waking up (though + `atomic.wait` is generally guaranteed to wake if `atomic.wake` is called). diff --git a/docs/design/README.md b/docs/design/README.md new file mode 100644 index 0000000000000..89adac3a508ab --- /dev/null +++ b/docs/design/README.md @@ -0,0 +1,10 @@ +# Emscripten Design Documents + +This directory contains design documents for emscripten features and major +changes/refactors. + +We are experimenting with keeping these document here under source control with +the hope that this will increase understandability of the codebase. This has +some advantages over doing all planning in Google Docs or GitHub issues. For +example, it allows us to track the history of designs and it allows them to be +searchable using standard tools like `git grep`. From 2b986931aeece04bb4100b143fca20980b7f0c2e Mon Sep 17 00:00:00 2001 From: Han Jiang Date: Tue, 7 Apr 2026 12:35:42 +1200 Subject: [PATCH 12/42] Remove outdated note in "Interacting with code" docs (#26638) `DEFAULT_LIBRARY_FUNCS_TO_INCLUDE` is not needed when `EXPORTED_FUNCTIONS` is used Fixes: #26629 --- AUTHORS | 1 + .../connecting_cpp_and_javascript/Interacting-with-code.rst | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 3baa0068ef86b..aa0e91eebc7ef 100644 --- a/AUTHORS +++ b/AUTHORS @@ -602,3 +602,4 @@ a license to everyone to use it as detailed in LICENSE.) * Christian Lloyd (copyright owned by Teladoc Health, Inc.) * Sean Morris * Mitchell Wills (copyright owned by Google, Inc.) +* Han Jiang diff --git a/site/source/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.rst b/site/source/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.rst index 9a0fa631c0e0b..2d3df59fb45c6 100644 --- a/site/source/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.rst +++ b/site/source/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.rst @@ -146,11 +146,6 @@ parameters to pass to the function: including function names. Exporting functions allows you to continue to access them using the original name through the global ``Module`` object. - - If you want to export a JS library function (something from a - ``src/library*.js`` file, for example), then in addition to - ``EXPORTED_FUNCTIONS``, you need to add it to ``DEFAULT_LIBRARY_FUNCS_TO_INCLUDE``, - as the latter will force the method to actually be included in - the build. - The compiler will remove code it does not see is used, to improve code size. If you use ``ccall`` in a place it sees, like code in a ``--pre-js`` From 8659483756853ce71d811316de8122d5f4e9a7e0 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Tue, 7 Apr 2026 09:28:14 -0700 Subject: [PATCH 13/42] Cleanup test_emscripten_thread_sleep. NFC (#26637) - Rename file - clang-format - Consistent naming and usage of emscripten_out --- test/pthread/emscripten_thread_sleep.c | 44 --------------------- test/pthread/test_emscripten_thread_sleep.c | 41 +++++++++++++++++++ test/test_browser.py | 2 +- 3 files changed, 42 insertions(+), 45 deletions(-) delete mode 100644 test/pthread/emscripten_thread_sleep.c create mode 100644 test/pthread/test_emscripten_thread_sleep.c diff --git a/test/pthread/emscripten_thread_sleep.c b/test/pthread/emscripten_thread_sleep.c deleted file mode 100644 index d0183fb121da1..0000000000000 --- a/test/pthread/emscripten_thread_sleep.c +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include -#include -#include -#include -#include - -void Sleep(double msecs) -{ - double t1 = emscripten_get_now(); - emscripten_thread_sleep(msecs); - double t2 = emscripten_get_now(); - printf("emscripten_thread_sleep() slept for %f msecs.\n", t2 - t1); - - assert(t2 - t1 >= 0.9 * msecs); // Should have slept ~ the requested time. -} - -void *thread_main(void *arg) -{ - emscripten_out("hello from thread!"); - - Sleep(1); - Sleep(10); - Sleep(100); - Sleep(1000); - Sleep(5000); - - emscripten_force_exit(0); - return NULL; -} - -int main() -{ - // Bad bad bad to sleep on the main thread, but test that it works. - Sleep(1); - Sleep(10); - Sleep(100); - Sleep(1000); - Sleep(5000); - pthread_t thread; - pthread_create(&thread, NULL, thread_main, NULL); - emscripten_exit_with_live_runtime(); - __builtin_trap(); -} diff --git a/test/pthread/test_emscripten_thread_sleep.c b/test/pthread/test_emscripten_thread_sleep.c new file mode 100644 index 0000000000000..268ec2ed272f9 --- /dev/null +++ b/test/pthread/test_emscripten_thread_sleep.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include +#include + +void do_sleep(double msecs) { + double t1 = emscripten_get_now(); + emscripten_thread_sleep(msecs); + double t2 = emscripten_get_now(); + emscripten_outf("emscripten_thread_sleep() slept for %f msecs.\n", t2 - t1); + + assert(t2 - t1 >= 0.9 * msecs); // Should have slept ~ the requested time. +} + +void* thread_main(void* arg) { + emscripten_out("hello from thread!"); + + do_sleep(1); + do_sleep(10); + do_sleep(100); + do_sleep(1000); + do_sleep(5000); + + emscripten_force_exit(0); + return NULL; +} + +int main() { + // Bad bad bad to sleep on the main thread, but test that it works. + do_sleep(1); + do_sleep(10); + do_sleep(100); + do_sleep(1000); + do_sleep(5000); + pthread_t thread; + pthread_create(&thread, NULL, thread_main, NULL); + emscripten_exit_with_live_runtime(); + __builtin_trap(); +} diff --git a/test/test_browser.py b/test/test_browser.py index bf76a59441722..a28805e0bedd8 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -4815,7 +4815,7 @@ def test_unicode_html_shell(self): # Tests the functionality of the emscripten_thread_sleep() function. def test_emscripten_thread_sleep(self): - self.btest_exit('pthread/emscripten_thread_sleep.c', cflags=['-pthread']) + self.btest_exit('pthread/test_emscripten_thread_sleep.c', cflags=['-pthread']) # Tests that Emscripten-compiled applications can be run from a relative path in browser that is different than the address of the current page def test_browser_run_from_different_directory(self): From b69cb85d736632e30249d3d8b1d1df25d01eb0f3 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Tue, 7 Apr 2026 10:19:55 -0700 Subject: [PATCH 14/42] Bump minimum supported node version v12.22.9 to v18.3.0 (#26604) Bump minimum supported node version v12.22.9 to v18.3.0. This change updates the minimum support node version for the generated code. v18.3.0 was chosen because that is currently minimum supported version for running emscripten itself. --- .circleci/config.yml | 7 ++- ChangeLog.md | 2 + .../tools_reference/settings_reference.rst | 7 +-- src/babel-plugins/strip-node-prefix.mjs | 48 ------------------- src/runtime_common.js | 12 ----- src/settings.js | 7 +-- test/codesize/test_codesize_hello_O0.json | 4 +- .../test_codesize_minimal_O0.expected.js | 4 +- test/codesize/test_codesize_minimal_O0.json | 4 +- test/codesize/test_unoptimized_code_size.json | 6 +-- test/common.py | 47 +----------------- test/test_core.py | 1 - test/test_other.py | 9 ---- test/test_sanity.py | 4 +- tools/building.py | 7 --- tools/feature_matrix.py | 4 +- tools/link.py | 3 +- 17 files changed, 29 insertions(+), 147 deletions(-) delete mode 100644 src/babel-plugins/strip-node-prefix.mjs diff --git a/.circleci/config.yml b/.circleci/config.yml index cd96285b981b5..713713b1006cf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -106,11 +106,11 @@ commands: cat ~/emsdk/.emscripten echo "export PATH=\"$HOME/node-v${version}-linux-x64/bin:\$PATH\"" >> $BASH_ENV install-node-oldest: - description: "install node 12.22.9 (oldest)" + description: "install node 18.3.0 (oldest)" steps: - install-node-version: # Keep this in sync with `OLDEST_SUPPORTED_NODE` in `feature_matrix.py` - node_version: "12.22.9" + node_version: "18.3.0" install-node-lts: description: "install node 22.21.0 (current LTS)" steps: @@ -970,8 +970,7 @@ jobs: # support in the generated code. - install-node-oldest - run-tests: - title: "node (oldest / 12.22.9)" - extra-cflags: "-sMIN_NODE_VERSION=122209" + title: "node (oldest / 18.3.0)" # We include most but not all of the nodefs and node rawfs tests here. # test_fs_nodefs_rw, test_fs_nodefs_statvfs, and test_unistd_io_nodefs_bigint fail. test_targets: " diff --git a/ChangeLog.md b/ChangeLog.md index 4f39c28c75cc3..15700a09b91e7 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -20,6 +20,8 @@ See docs/process.md for more on how version tagging works. 5.0.6 (in development) ---------------------- +- The minimum version of node supported by the generated code was bumped from + v12.22.0 to v18.3.0. (#26604) 5.0.5 - 04/03/26 ---------------- diff --git a/site/source/docs/tools_reference/settings_reference.rst b/site/source/docs/tools_reference/settings_reference.rst index b7777880f414b..a8de5a33b2acd 100644 --- a/site/source/docs/tools_reference/settings_reference.rst +++ b/site/source/docs/tools_reference/settings_reference.rst @@ -2919,10 +2919,11 @@ MIN_NODE_VERSION Specifies minimum node version to target for the generated code. This is distinct from the minimum version required to run the emscripten compiler. Version is encoded in MMmmVV, e.g. 181401 denotes Node 18.14.01. -Minimum supported value is 122209, which was released 2022-01-11 (see -feature_matrix.py). This version aligns with the Ubuntu TLS 22.04 (Jammy). +Minimum supported value is 180300, which was released 2022-05-18 (see +feature_matrix.py). This version aligns with the version available in +debian/stable (bookworm). -Default value: 160000 +Default value: 180300 .. _minimal_runtime: diff --git a/src/babel-plugins/strip-node-prefix.mjs b/src/babel-plugins/strip-node-prefix.mjs deleted file mode 100644 index 191fce47951ee..0000000000000 --- a/src/babel-plugins/strip-node-prefix.mjs +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @license - * Copyright 2026 The Emscripten Authors - * SPDX-License-Identifier: MIT - */ - -// A babel plugin to remove the leading `node:` prefix from all imports. - -export default function ({ types: t, targets }) { - // Skip this plugin for Node.js >= 16 - if (+targets().node.split('.')[0] >= 16) { - return {}; - } - - return { - name: 'strip-node-prefix', - visitor: { - // e.g. `import fs from 'node:fs'` - ImportDeclaration({ node }) { - if (node.source.value.startsWith('node:')) { - node.source.value = node.source.value.slice(5); - } - }, - - // e.g. `await import('node:fs')` - // Note: only here for reference, it's mangled with EMSCRIPTEN$AWAIT$IMPORT below. - ImportExpression({ node }) { - if (t.isStringLiteral(node.source) && node.source.value.startsWith('node:')) { - node.source.value = node.source.value.slice(5); - } - }, - - // e.g. `require('node:fs')` or EMSCRIPTEN$AWAIT$IMPORT('node:fs') - CallExpression({ node }) { - if ( - (t.isIdentifier(node.callee, { name: 'require' }) || - // Special placeholder for `await import` - // FIXME: Remove after PR https://github.com/emscripten-core/emscripten/pull/23730 is landed. - t.isIdentifier(node.callee, { name: 'EMSCRIPTEN$AWAIT$IMPORT' })) && - t.isStringLiteral(node.arguments[0]) && - node.arguments[0].value.startsWith('node:') - ) { - node.arguments[0].value = node.arguments[0].value.slice(5); - } - }, - }, - }; -} diff --git a/src/runtime_common.js b/src/runtime_common.js index 13f56e427a012..80d504ffa7139 100644 --- a/src/runtime_common.js +++ b/src/runtime_common.js @@ -136,18 +136,6 @@ function updateMemoryViews() { #endif } -#if ENVIRONMENT_MAY_BE_NODE && MIN_NODE_VERSION < 160000 -// The performance global was added to node in v16.0.0: -// https://nodejs.org/api/globals.html#performance -if (ENVIRONMENT_IS_NODE) { - // This is needed for emscripten_get_now and for pthreads support which - // depends on it for accurate timing. - // Use `global` rather than `globalThis` here since older versions of node - // don't have `globalThis`. - global.performance ??= require('perf_hooks').performance; -} -#endif - #if IMPORTED_MEMORY // In non-standalone/normal mode, we create the memory here. #include "runtime_init_memory.js" diff --git a/src/settings.js b/src/settings.js index 2aa74c60e20d1..fc50d8e0983f5 100644 --- a/src/settings.js +++ b/src/settings.js @@ -1920,9 +1920,10 @@ var MIN_CHROME_VERSION = 85; // Specifies minimum node version to target for the generated code. This is // distinct from the minimum version required to run the emscripten compiler. // Version is encoded in MMmmVV, e.g. 181401 denotes Node 18.14.01. -// Minimum supported value is 122209, which was released 2022-01-11 (see -// feature_matrix.py). This version aligns with the Ubuntu TLS 22.04 (Jammy). -var MIN_NODE_VERSION = 160000; +// Minimum supported value is 180300, which was released 2022-05-18 (see +// feature_matrix.py). This version aligns with the version available in +// debian/stable (bookworm). +var MIN_NODE_VERSION = 180300; // If true, uses minimal sized runtime without POSIX features, Module, // preRun/preInit/etc., Emscripten built-in XHR loading or library_browser.js. diff --git a/test/codesize/test_codesize_hello_O0.json b/test/codesize/test_codesize_hello_O0.json index ed7a13ce715c6..7a5d20ab1398f 100644 --- a/test/codesize/test_codesize_hello_O0.json +++ b/test/codesize/test_codesize_hello_O0.json @@ -1,10 +1,10 @@ { "a.out.js": 24261, - "a.out.js.gz": 8714, + "a.out.js.gz": 8720, "a.out.nodebug.wasm": 14850, "a.out.nodebug.wasm.gz": 7311, "total": 39111, - "total_gz": 16025, + "total_gz": 16031, "sent": [ "fd_write" ], diff --git a/test/codesize/test_codesize_minimal_O0.expected.js b/test/codesize/test_codesize_minimal_O0.expected.js index ad72f50b73f66..41117166dff11 100644 --- a/test/codesize/test_codesize_minimal_O0.expected.js +++ b/test/codesize/test_codesize_minimal_O0.expected.js @@ -19,8 +19,8 @@ // Note: We use a typeof check here instead of optional chaining using // globalThis because older browsers might not have globalThis defined. var currentNodeVersion = typeof process !== 'undefined' && process.versions?.node ? humanReadableVersionToPacked(process.versions.node) : TARGET_NOT_SUPPORTED; - if (currentNodeVersion < 160000) { - throw new Error(`This emscripten-generated code requires node v${ packedVersionToHumanReadable(160000) } (detected v${packedVersionToHumanReadable(currentNodeVersion)})`); + if (currentNodeVersion < 180300) { + throw new Error(`This emscripten-generated code requires node v${ packedVersionToHumanReadable(180300) } (detected v${packedVersionToHumanReadable(currentNodeVersion)})`); } var userAgent = typeof navigator !== 'undefined' && navigator.userAgent; diff --git a/test/codesize/test_codesize_minimal_O0.json b/test/codesize/test_codesize_minimal_O0.json index 8d4cc47adfaba..ffaad48b0a942 100644 --- a/test/codesize/test_codesize_minimal_O0.json +++ b/test/codesize/test_codesize_minimal_O0.json @@ -1,10 +1,10 @@ { "a.out.js": 19452, - "a.out.js.gz": 6998, + "a.out.js.gz": 7004, "a.out.nodebug.wasm": 1015, "a.out.nodebug.wasm.gz": 602, "total": 20467, - "total_gz": 7600, + "total_gz": 7606, "sent": [], "imports": [], "exports": [ diff --git a/test/codesize/test_unoptimized_code_size.json b/test/codesize/test_unoptimized_code_size.json index 3fd577e8caa13..60b458d2f280f 100644 --- a/test/codesize/test_unoptimized_code_size.json +++ b/test/codesize/test_unoptimized_code_size.json @@ -1,6 +1,6 @@ { "hello_world.js": 57078, - "hello_world.js.gz": 17753, + "hello_world.js.gz": 17757, "hello_world.wasm": 14850, "hello_world.wasm.gz": 7311, "no_asserts.js": 26654, @@ -8,9 +8,9 @@ "no_asserts.wasm": 12010, "no_asserts.wasm.gz": 5880, "strict.js": 54896, - "strict.js.gz": 17061, + "strict.js.gz": 17065, "strict.wasm": 14850, "strict.wasm.gz": 7311, "total": 180338, - "total_gz": 64217 + "total_gz": 64225 } diff --git a/test/common.py b/test/common.py index 33f538c8c7829..a72420fd524e4 100644 --- a/test/common.py +++ b/test/common.py @@ -28,7 +28,7 @@ import line_endings from retryable_unittest import RetryableTestCase -from tools import building, config, feature_matrix, shared, utils +from tools import building, config, shared, utils from tools.feature_matrix import Feature from tools.settings import COMPILE_TIME_SETTINGS from tools.shared import DEBUG, EMCC, EMXX, get_canonical_temp_dir @@ -147,15 +147,6 @@ def record_flaky_test(test_name, attempt_count, max_attempts, exception_msg): f.write(f'{test_name}\n') -def node_bigint_flags(node_version): - # The --experimental-wasm-bigint flag was added in v12, and then removed (enabled by default) - # in v16. - if node_version and node_version < (16, 0, 0): - return ['--experimental-wasm-bigint'] - else: - return [] - - @contextlib.contextmanager def env_modify(updates): """A context manager that updates os.environ.""" @@ -457,11 +448,7 @@ def require_pthreads(self): if self.get_setting('MINIMAL_RUNTIME'): self.skipTest('non-browser pthreads not yet supported with MINIMAL_RUNTIME') for engine in self.js_engines: - if engine_is_node(engine): - if not self.try_require_node_version(16, 0, 0): - self.fail('node v16 required to run this test') - return - elif engine_is_bun(engine) or engine_is_deno(engine): + if engine_is_node(engine) or engine_is_bun(engine) or engine_is_deno(engine): self.require_engine(engine) return self.fail('no JS engine found capable of running pthreads') @@ -531,23 +518,6 @@ def try_require_node_version(self, major, minor=0, revision=0): self.require_engine(nodejs) return True - def require_simd(self): - if 'EMTEST_SKIP_SIMD' in os.environ: - self.skipTest('test requires node >= 16 or d8 (and EMTEST_SKIP_SIMD is set)') - if self.is_browser_test(): - return - - if self.try_require_node_version(16): - return - - v8 = get_v8() - if v8: - self.cflags.append('-sENVIRONMENT=shell') - self.require_engine(v8) - return - - self.fail('either d8 or node >= 16 required to run wasm64 tests. Use EMTEST_SKIP_SIMD to skip') - def require_wasm_legacy_eh(self): if 'EMTEST_SKIP_WASM_LEGACY_EH' in os.environ: self.skipTest('test requires node >= 17 or d8 (and EMTEST_SKIP_WASM_LEGACY_EH is set)') @@ -701,13 +671,6 @@ def setUp(self): nodejs = get_nodejs() if nodejs: node_version = shared.get_node_version(nodejs) - if node_version < (13, 0, 0): - self.node_args.append('--unhandled-rejections=strict') - elif node_version < (15, 0, 0): - # Opt in to node v15 default behaviour: - # https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode - self.node_args.append('--unhandled-rejections=throw') - self.node_args += node_bigint_flags(node_version) # If the version we are running tests in is lower than the version that # emcc targets then we need to tell emcc to target that older version. @@ -719,12 +682,6 @@ def setUp(self): ) if node_version < emcc_min_node_version: self.cflags.append('-sMIN_NODE_VERSION=%02d%02d%02d' % node_version) - self.cflags.append('-Wno-transpile') - - # This allows much of the test suite to be run on older versions of node that don't - # support wasm bigint integration - if node_version[0] < feature_matrix.min_browser_versions[feature_matrix.Feature.JS_BIGINT_INTEGRATION]['node'] / 10000: - self.cflags.append('-sWASM_BIGINT=0') self.v8_args = ['--wasm-staging'] self.env = {} diff --git a/test/test_core.py b/test/test_core.py index c3f4e7fa1ed17..60314248ca320 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -138,7 +138,6 @@ def wasm_simd(func): @wraps(func) def decorated(self, *args, **kwargs): - self.require_simd() if self.is_wasm2js(): self.skipTest('wasm2js only supports MVP for now') if '-O3' in self.cflags: diff --git a/test/test_other.py b/test/test_other.py index 81d892921162c..3ac05d902c1ee 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -13414,15 +13414,6 @@ def check_for_es6(filename, expect): self.do_runf('test.c', expected, cflags=['--closure=1'], output_basename='test_closure') check_for_es6('test_closure.js', False) - def test_node_prefix_transpile(self): - self.run_process([EMCC, test_file('hello_world.c'), '-sEXPORT_ES6']) - content = read_file('a.out.js') - self.assertContained('node:', content) - - self.run_process([EMCC, test_file('hello_world.c'), '-sEXPORT_ES6', '-sMIN_NODE_VERSION=150000', '-Wno-transpile']) - content = read_file('a.out.js') - self.assertNotContained('node:', content) - def test_gmtime_noleak(self): # Confirm that gmtime_r does not leak when called in isolation. self.do_other_test('test_gmtime_noleak.c', cflags=['-fsanitize=leak']) diff --git a/test/test_sanity.py b/test/test_sanity.py index 26e54be4319e0..e3385e42792eb 100644 --- a/test/test_sanity.py +++ b/test/test_sanity.py @@ -313,8 +313,8 @@ def test_node(self): ('v4.1.0', False), ('v10.18.0', False), ('v16.20.0', False), - ('v18.3.0', True), - ('v18.3.1-pre', True), + ('v18.19.1', True), + ('v18.19.1-pre', True), ('cheez', False)): print(version, succeed) delete_file(SANITY_FILE) diff --git a/tools/building.py b/tools/building.py index bc09df6d26012..0405a8813dcef 100644 --- a/tools/building.py +++ b/tools/building.py @@ -543,13 +543,7 @@ def transpile(filename): config = { 'sourceType': 'script', 'presets': ['@babel/preset-env'], - 'plugins': [], 'targets': {}, - 'parserOpts': { - # FIXME: Remove when updating to Babel 8, see: - # https://babeljs.io/docs/v8-migration-api#javascript-nodes - 'createImportExpressions': True, - }, } if settings.MIN_CHROME_VERSION != UNSUPPORTED: config['targets']['chrome'] = str(settings.MIN_CHROME_VERSION) @@ -559,7 +553,6 @@ def transpile(filename): config['targets']['safari'] = version_split(settings.MIN_SAFARI_VERSION) if settings.MIN_NODE_VERSION != UNSUPPORTED: config['targets']['node'] = version_split(settings.MIN_NODE_VERSION) - config['plugins'] = [path_from_root('src/babel-plugins/strip-node-prefix.mjs')] config_json = json.dumps(config, indent=2) outfile = shared.get_temp_files().get('babel.js').name config_file = shared.get_temp_files().get('babel_config.json').name diff --git a/tools/feature_matrix.py b/tools/feature_matrix.py index 6ad0d7145c8a7..0c0e01fa93449 100644 --- a/tools/feature_matrix.py +++ b/tools/feature_matrix.py @@ -25,9 +25,9 @@ OLDEST_SUPPORTED_CHROME = 74 # Released on 2019-04-23 OLDEST_SUPPORTED_FIREFOX = 68 # Released on 2019-07-09 OLDEST_SUPPORTED_SAFARI = 120200 # Released on 2019-03-25 -# 12.22.09 is the oldest version of node that we do any testing with. +# This is the oldest version of node that we do any testing with. # Keep this in sync with the test-node-compat in .circleci/config.yml. -OLDEST_SUPPORTED_NODE = 122209 +OLDEST_SUPPORTED_NODE = 180300 class Feature(IntEnum): diff --git a/tools/link.py b/tools/link.py index aaffe9eae7066..cbb3013a3c117 100644 --- a/tools/link.py +++ b/tools/link.py @@ -1380,8 +1380,7 @@ def limit_incoming_module_api(): # TODO: replace this with feature matrix in the future. settings.TRANSPILE = (settings.MIN_FIREFOX_VERSION < 79 or settings.MIN_CHROME_VERSION < 85 or - settings.MIN_SAFARI_VERSION < 140000 or - settings.MIN_NODE_VERSION < 160000) + settings.MIN_SAFARI_VERSION < 140000) if settings.STB_IMAGE: settings.EXPORTED_FUNCTIONS += ['_stbi_load', '_stbi_load_from_memory', '_stbi_image_free'] From 426fb2df42aadcebf49a47420956da24227609e4 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Tue, 7 Apr 2026 10:54:39 -0700 Subject: [PATCH 15/42] Design doc feedback from #26621. NFC (#26636) --- docs/design/01-precise-futex-wakeups.md | 174 ++++++++++++------------ docs/design/README.md | 19 ++- 2 files changed, 101 insertions(+), 92 deletions(-) diff --git a/docs/design/01-precise-futex-wakeups.md b/docs/design/01-precise-futex-wakeups.md index 6a8beb210e8b4..70fbf22d6a2a4 100644 --- a/docs/design/01-precise-futex-wakeups.md +++ b/docs/design/01-precise-futex-wakeups.md @@ -1,4 +1,4 @@ -# Design Doc: Precise Futex Wakeups in Emscripten +# Design Doc: Precise Futex Wakeups - **Status**: Draft - **Bug**: https://github.com/emscripten-core/emscripten/issues/26633 @@ -24,7 +24,7 @@ CPU wakeups and increased latency for events. ## Non-Goals - **Main Browser Thread**: Changes to the busy-wait loop in `futex_wait_main_browser_thread` are out of scope. - **Direct Atomics Usage**: Threads that call `atomic.wait` directly (bypassing `emscripten_futex_wait`) will remain un-interruptible. -- **Wasm Workers**: Wasm Worker do not have a `pthread` structure, they are not covered by this design. +- **Wasm Workers**: Wasm Workers do not have a `pthread` structure, so they are not covered by this design. ## Proposed Design @@ -38,103 +38,101 @@ As part of this design we will need to explicitly state that application. ### 1. `struct pthread` Extensions -We will add the following fields to `struct pthread` (in -`system/lib/libc/musl/src/internal/pthread_impl.h`). All operations on these -fields must use `memory_order_seq_cst` to ensure the handshake is robust. +We will add a single atomic `wait_addr` field to `struct pthread` (in +`system/lib/libc/musl/src/internal/pthread_impl.h`). ```c // The address the thread is currently waiting on in emscripten_futex_wait. -// NULL if the thread is not currently in a futex wait. -_Atomic(void*) waiting_on_address; - -// A counter that is incremented every time the thread wakes up from a futex wait. -// Used by wakers to ensure the target thread has actually acknowledged the wake. -_Atomic(uint32_t) wait_counter; - -// A bitmask of reasons why the thread was woken for a side-channel event. -_Atomic(uint32_t) wait_reasons; - -#define WAIT_REASON_CANCEL (1 << 0) -#define WAIT_REASON_MAILBOX (1 << 1) +// +// This field encodes the state using the following bitmask: +// - NULL: Not waiting, no pending notification. +// - NOTIFY_BIT (0x1): Not waiting, but a notification was sent. +// - addr: Waiting on `addr`, no pending notification. +// - addr | NOTIFY_BIT: Waiting on `addr`, notification sent. +// +// Since futex addresses must be 4-byte aligned, the low bit is safe to use. +_Atomic uintptr_t wait_addr; + +#define NOTIFY_BIT (1 << 0) ``` ### 2. Waiter Logic (`emscripten_futex_wait`) -The waiter will follow this logic (using `SEQ_CST` for all atomic accesses): - -1. **Pre-check**: Check `wait_reasons`. If non-zero, handle the reasons (e.g., process mailbox or handle cancellation). -2. **Publish**: Set `waiting_on_address = addr`. -3. **Counter Snapshot**: Read `current_counter = wait_counter`. -4. **Double-check**: This is critical to avoid the race where a reason was added just before `waiting_on_address` was set. If `wait_reasons` is now non-zero, clear `waiting_on_address` and go to step 1. -5. **Wait**: Call `ret = __builtin_wasm_memory_atomic_wait32(addr, val, timeout)`. -6. **Unpublish**: - - Set `waiting_on_address = NULL`. - - Atomically increment `wait_counter`. -7. **Post-check**: Check `wait_reasons`. If non-zero, handle the reasons. -8. **Return**: Return the result of the wait to the caller. - - If `ret == ATOMICS_WAIT_OK`, return `0`. - - If `ret == ATOMICS_WAIT_TIMED_OUT`, return `-ETIMEDOUT`. - - If `ret == ATOMICS_WAIT_NOT_EQUAL`, return `-EWOULDBLOCK`. - -Note: We do **not** loop internally if `ret == ATOMICS_WAIT_OK`. Even if we suspect the wake was caused by a side-channel event, we must return to the user to avoid "swallowing" a simultaneous real application wake that might not have changed the memory value. +The waiter will follow this logic: + +1. **Notification Loop**: + ```c + uintptr_t expected_null = 0; + while (!atomic_compare_exchange_strong(&self->wait_addr, &expected_null, (uintptr_t)addr)) { + // If the CAS failed, it means NOTIFY_BIT was set by another thread. + assert(expected_null == NOTIFY_BIT); + // Let the notifier know that we received the wakeup notification by + // resetting wait_addr. + self->wait_addr = 0; + handle_wakeup(); // Process mailbox or handle cancellation + // Reset expected_null because CAS updates it to the observed value on failure. + expected_null = 0; + } + ``` +2. **Wait**: Call `ret = __builtin_wasm_memory_atomic_wait32(addr, val, timeout)`. +3. **Unpublish & Check**: + ```c + // Clear wait_addr and check if a notification arrived while we were sleeping. + if ((atomic_exchange(&self->wait_addr, 0) & NOTIFY_BIT) != 0) { + handle_wakeup(); + } + ``` +4. **Return**: Return the result of the wait. + +Note: We do **not** loop internally if `ret == ATOMICS_WAIT_OK`. Even if we +suspect the wake was caused by a side-channel event, we must return to the user +to avoid "swallowing" a simultaneous real application wake. ### 3. Waker Logic -When a thread needs to wake another thread for a side-channel event (e.g., in `pthread_cancel` or `em_task_queue_enqueue`): - -1. Atomically OR the appropriate bit into the target thread's `wait_reasons` (`SEQ_CST`). -2. Read `target_addr = target->waiting_on_address` (`SEQ_CST`). -3. If `target_addr` is not NULL: - - Read `start_c = target->wait_counter` (`SEQ_CST`). - - Enter a loop: - - Call `emscripten_futex_wake(target_addr, 1)`. - - Exit loop if `target->wait_counter != start_c` OR `target->waiting_on_address != target_addr`. - - **Yield**: Call `sched_yield()` (or a small sleep) to allow the target thread to proceed if it is currently being scheduled. +When a thread needs to wake another thread for a side-channel event: + +1. **Enqueue Work**: Add the task to the target's mailbox or set the cancellation flag. +2. **Signal**: + ```c + uintptr_t addr = atomic_fetch_or(&target->wait_addr, NOTIFY_BIT); + if (addr == 0 || (addr & NOTIFY_BIT) != 0) { + // Either the thread wasn't waiting (it will see NOTIFY_BIT later), + // or someone else is already in the process of notifying it. + return; + } + // We set the bit and are responsible for waking the target. + // The target is currently waiting on `addr`. + while (target->wait_addr == (addr | NOTIFY_BIT)) { + emscripten_futex_wake((void*)addr, INT_MAX); + sched_yield(); + } + ``` ### 4. Handling the Race Condition -The "Lost Wakeup" race is handled by the combination of: -- The waiter double-checking `wait_reasons` after publishing its `waiting_on_address`. -- The waker looping `atomic.wake` until the waiter increments its `wait_counter`. - -Even if the waker's first `atomic.wake` occurs after the waiter's double-check -but *before* the waiter actually enters the `atomic.wait` instruction, the waker -will continue to loop and call `atomic.wake` again. The subsequent call(s) will -successfully wake the waiter once it is actually sleeping. - -Multiple wakers can safely call this logic simultaneously; they will all exit -the loop as soon as the waiter acknowledges the wake by incrementing the -counter. - -### 5. Overlapping and Spurious Wakeups -The design must handle cases where "real" wakeups (triggered by the application) and "side-channel" wakeups (cancellation/mailbox) occur simultaneously. - -1. **Spurious Wakeups for Other Threads**: If multiple threads are waiting on the same address (e.g., a shared mutex), a side-channel `atomic_wake(addr, 1)` targeted at Thread A might be delivered by the kernel to Thread B. - - **Thread B's response**: It will wake up, increment its `wait_counter`, see that its `wait_reasons` are empty, and return `0` to its caller. - - **Thread C (the waker)**: It will see that Thread A's `wait_counter` has *not* changed and `waiting_on_address` is still `addr`. It will therefore continue its loop and call `atomic_wake` again until Thread A is finally woken. - - **Result**: Thread B experiences a "spurious" wakeup. This is acceptable and expected behavior for futex-based synchronization. -2. **Handling Side-Channel Success**: If Thread A is woken by the side-channel, it handles the event and returns `0`. The user's code will typically see that its own synchronization condition is not yet met and immediately call `emscripten_futex_wait` again. This effectively "resumes" the wait from the user's perspective while having allowed the side-channel event to be processed. -3. **No Lost "Real" Wakeups**: By returning to the caller whenever `atomic.wait` returns `OK`, we ensure that we never miss or swallow a real application-level `atomic.wake`. - -### 6. Counter Wrap-around -The `wait_counter` is a `uint32_t` and will wrap around to zero after $2^{32}$ wakeups. This is safe because: -1. **Impossibility of Racing**: For the waker to "miss" a wake-up due to wrap-around, the waiter would have to wake up and re-enter a sleep state exactly $2^{32}$ times in the very brief window between the waker's `atomic_wake` and its subsequent check of `wait_counter`. Even at extreme wakeup frequencies (e.g., 1 million per second), this would take over an hour. -2. **Address Change Check**: The waker loop also checks `target->waiting_on_address != target_addr`. If the waiter wakes up and either stops waiting or starts waiting on a *different* address, the waker will exit the loop regardless of the counter value. - -### 6. Benefits -- **Lower Power Consumption**: Threads can sleep indefinitely (or for the full duration of a user-requested timeout) without periodic wakeups. -- **Lower Latency**: Mailbox events and cancellation requests are processed immediately rather than waiting for the next 1ms or 100ms tick. -- **Simpler Loop**: The complex logic for calculating remaining timeout slices in `emscripten_futex_wait` is removed. +The protocol handles the "Lost Wakeup" race by having the waker loop until the +waiter clears its `wait_addr`. If the waker sets the `NOTIFY_BIT` just before +the waiter enters `atomic.wait`, the `atomic_wake` will be delivered once the +waiter is asleep. If the waiter wakes up for any reason (timeout, real wake, or +side-channel wake), its `atomic_exchange` will satisfy the waker's loop +condition. + +## Benefits + +- **Lower Power Consumption**: Threads can sleep indefinitely (or for the full duration of a user-requested timeout) without periodic wakeups. +- **Lower Latency**: Mailbox events and cancellation requests are processed immediately rather than waiting for the next 1ms or 100ms tick. +- **Simpler Loop**: The complex logic for calculating remaining timeout slices in `emscripten_futex_wait` is removed. ## Alternatives Considered -- **Signal-based wakeups**: Not currently feasible in Wasm as signals are not - implemented in a way that can interrupt `atomic.wait`. -- **A single global "wake-up" address per thread**: This would require the - waiter to wait on *two* addresses simultaneously (the user's futex and its - own wakeup address), which `atomic.wait` does not support. The proposed - design works around this by having the waker use the *user's* futex address. +- **Signal-based wakeups**: Not currently feasible in Wasm as signals are not + implemented in a way that can interrupt `atomic.wait`. +- **A single global "wake-up" address per thread**: This would require the + waiter to wait on *two* addresses simultaneously (the user's futex and its + own wakeup address), which `atomic.wait` does not support. The proposed + design works around this by having the waker use the *user's* futex address. ## Security/Safety Considerations -- The `waiting_on_address` must be managed carefully to ensure wakers don't - call `atomic.wake` on stale addresses. The `wait_counter` and clearing the - address upon wake mitigate this. -- The waker loop should have a reasonable fallback (like a yield) to prevent a - busy-wait deadlock if the waiter is somehow prevented from waking up (though - `atomic.wait` is generally guaranteed to wake if `atomic.wake` is called). +- **The `wait_addr` must be managed carefully** to ensure wakers don't + call `atomic.wake` on stale addresses. Clearing the address upon wake + mitigates this. +- **The waker loop should have a reasonable fallback** (like a yield) to prevent a + busy-wait deadlock if the waiter is somehow prevented from waking up (though + `atomic.wait` is generally guaranteed to wake if `atomic.wake` is called). diff --git a/docs/design/README.md b/docs/design/README.md index 89adac3a508ab..6e5b4bb388e37 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -3,8 +3,19 @@ This directory contains design documents for emscripten features and major changes/refactors. -We are experimenting with keeping these document here under source control with +We are experimenting with keeping these documents here under source control with the hope that this will increase understandability of the codebase. This has -some advantages over doing all planning in Google Docs or GitHub issues. For -example, it allows us to track the history of designs and it allows them to be -searchable using standard tools like `git grep`. +some advantages over doing all our planning in Google Docs or GitHub issues. +For example, it allows us to track the history of designs and it allows them to +be searchable using standard tools like `git grep`. + +## Document Format + +Each document in this directory should be a markdown file. At the top of each +document should be a `Status` which can be either `Draft`, `Accepted`, +`Completed`. + +When a document is marked as `Completed` it should also be updated such that +it is clear the work has been done, and is now in the past. For example, +phrases such as "The current behavior" should be replaced with "The previous +behavior". From 3ba68164fcd89d7e279a28b106f1b37fadfc6e87 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Tue, 7 Apr 2026 12:08:23 -0700 Subject: [PATCH 16/42] Simplify MIN_NODE_VERSION override from embind ts-gen. (#26643) Followup to #26604 since we no longer support node v15 or v16. I guess we still need to set `MIN_NODE_VERSION` in case the user specified `MIN_NODE_VERSION=-1` (UNSUPPORTED). --- tools/link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/link.py b/tools/link.py index cbb3013a3c117..d4cab9999c9f2 100644 --- a/tools/link.py +++ b/tools/link.py @@ -1959,6 +1959,7 @@ def run_embind_gen(options, wasm_target, js_syms, extra_settings): # Force node since that is where the tool runs. if 'node' not in settings.ENVIRONMENT: settings.ENVIRONMENT.append('node') + settings.MIN_NODE_VERSION = feature_matrix.OLDEST_SUPPORTED_NODE settings.MINIMAL_RUNTIME = 0 # Required function to trigger TS generation. settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$callRuntimeCallbacks', '$addRunDependency', '$removeRunDependency'] @@ -1979,7 +1980,6 @@ def run_embind_gen(options, wasm_target, js_syms, extra_settings): dirname, basename = os.path.split(lib) if basename == 'libembind.js': settings.JS_LIBRARIES[i] = os.path.join(dirname, 'libembind_gen.js') - settings.MIN_NODE_VERSION = 160000 if settings.MEMORY64 else 150000 # The final version of the memory64 proposal is not implemented until node # v24, so we need to lower it away in order to execute the binary at build # time. From a8e9415305f5895ed4613b415b2b73308c2663fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20P=C3=A9ron?= Date: Tue, 7 Apr 2026 21:09:28 +0200 Subject: [PATCH 17/42] Fix Pkgconfig for freetype2 (#26640) --- tools/ports/freetype.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/ports/freetype.py b/tools/ports/freetype.py index 844c28be3e854..d99a6c0059975 100644 --- a/tools/ports/freetype.py +++ b/tools/ports/freetype.py @@ -6,6 +6,8 @@ import os TAG = 'VER-2-13-3' +# See version_info in builds/unix/configure.raw or `freetype-config --version` +PKG_VERSION = '26.2.20' HASH = 'ce413487c24e689631d705f53b64725256f89fffe9aade7cf07bbd785a9cd49eb6b8d2297a55554f3fee0a50b17e8af78f505cdab565768afab833794f968c2f' variants = {'freetype-legacysjlj': {'SUPPORT_LONGJMP': 'wasm', 'WASM_LEGACY_EXCEPTIONS': 1}} @@ -96,7 +98,7 @@ def create(final): if settings.SUPPORT_LONGJMP == 'wasm': flags.append('-sSUPPORT_LONGJMP=wasm') - ports.make_pkg_config('freetype', TAG, '-sUSE_FREETYPE') + ports.make_pkg_config('freetype2', PKG_VERSION, '-sUSE_FREETYPE') ports.build_port(source_path, final, 'freetype', flags=flags, srcs=srcs) return [shared.cache.get_lib(get_lib_name(settings), create, what='port')] From 12dcf7561b1088767f61bb6f6e889c1dc9314285 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Tue, 7 Apr 2026 13:18:32 -0700 Subject: [PATCH 18/42] Remove final usages of node's `global`. NFC (#26642) We already just globalThis everywhere else in the codebase. --- src/shell.js | 2 +- src/shell_minimal.js | 2 +- test/codesize/test_codesize_minimal_pthreads.json | 8 ++++---- .../test_codesize_minimal_pthreads_memgrowth.json | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/shell.js b/src/shell.js index 41294749487a4..3968682f855ed 100644 --- a/src/shell.js +++ b/src/shell.js @@ -118,7 +118,7 @@ if (ENVIRONMENT_IS_NODE) { #if PTHREADS || WASM_WORKERS var worker_threads = require('node:worker_threads'); - global.Worker = worker_threads.Worker; + globalThis.Worker = worker_threads.Worker; ENVIRONMENT_IS_WORKER = !worker_threads.isMainThread; #if PTHREADS // Under node we set `workerData` to `em-pthread` to signal that the worker diff --git a/src/shell_minimal.js b/src/shell_minimal.js index 4a45eff85b4f3..0e71500ef2404 100644 --- a/src/shell_minimal.js +++ b/src/shell_minimal.js @@ -60,7 +60,7 @@ var ENVIRONMENT_IS_WORKER = !!globalThis.WorkerGlobalScope; #if ENVIRONMENT_MAY_BE_NODE && (PTHREADS || WASM_WORKERS) if (ENVIRONMENT_IS_NODE) { var worker_threads = require('node:worker_threads'); - global.Worker = worker_threads.Worker; + globalThis.Worker = worker_threads.Worker; ENVIRONMENT_IS_WORKER = !worker_threads.isMainThread; } #endif diff --git a/test/codesize/test_codesize_minimal_pthreads.json b/test/codesize/test_codesize_minimal_pthreads.json index b5da211f8cb60..050cb6cd65f24 100644 --- a/test/codesize/test_codesize_minimal_pthreads.json +++ b/test/codesize/test_codesize_minimal_pthreads.json @@ -1,10 +1,10 @@ { - "a.out.js": 7363, - "a.out.js.gz": 3604, + "a.out.js": 7367, + "a.out.js.gz": 3603, "a.out.nodebug.wasm": 19003, "a.out.nodebug.wasm.gz": 8786, - "total": 26366, - "total_gz": 12390, + "total": 26370, + "total_gz": 12389, "sent": [ "a (memory)", "b (exit)", diff --git a/test/codesize/test_codesize_minimal_pthreads_memgrowth.json b/test/codesize/test_codesize_minimal_pthreads_memgrowth.json index e83fe59e4f5dc..3076a1322f4a5 100644 --- a/test/codesize/test_codesize_minimal_pthreads_memgrowth.json +++ b/test/codesize/test_codesize_minimal_pthreads_memgrowth.json @@ -1,10 +1,10 @@ { - "a.out.js": 7765, - "a.out.js.gz": 3810, + "a.out.js": 7769, + "a.out.js.gz": 3809, "a.out.nodebug.wasm": 19004, "a.out.nodebug.wasm.gz": 8787, - "total": 26769, - "total_gz": 12597, + "total": 26773, + "total_gz": 12596, "sent": [ "a (memory)", "b (exit)", From d5ebf2646d454f71435523fe0ed8da31ced78336 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Tue, 7 Apr 2026 13:18:57 -0700 Subject: [PATCH 19/42] Simplify FS missing assertions. NFC (#26641) --- src/preamble.js | 25 +++++++++---------- test/codesize/test_codesize_hello_O0.json | 8 +++--- .../test_codesize_minimal_O0.expected.js | 25 +++++++++---------- test/codesize/test_codesize_minimal_O0.json | 8 +++--- test/codesize/test_unoptimized_code_size.json | 12 ++++----- 5 files changed, 38 insertions(+), 40 deletions(-) diff --git a/src/preamble.js b/src/preamble.js index ab3a7d78b8806..d92557f5757b2 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -301,20 +301,19 @@ function abort(what) { #if ASSERTIONS && !('$FS' in addedLibraryItems) // show errors on likely calls to FS when it was not included +function fsMissing() { + abort('Filesystem support (FS) was not included. The problem is that you are using files from JS, but files were not used from C/C++, so filesystem support was not auto-included. You can force-include filesystem support with -sFORCE_FILESYSTEM'); +} var FS = { - error() { - abort('Filesystem support (FS) was not included. The problem is that you are using files from JS, but files were not used from C/C++, so filesystem support was not auto-included. You can force-include filesystem support with -sFORCE_FILESYSTEM'); - }, - init() { FS.error() }, - createDataFile() { FS.error() }, - createPreloadedFile() { FS.error() }, - createLazyFile() { FS.error() }, - open() { FS.error() }, - mkdev() { FS.error() }, - registerDevice() { FS.error() }, - analyzePath() { FS.error() }, - - ErrnoError() { FS.error() }, + init: fsMissing, + createDataFile: fsMissing, + createPreloadedFile: fsMissing, + createLazyFile: fsMissing, + open: fsMissing, + mkdev: fsMissing, + registerDevice: fsMissing, + analyzePath: fsMissing, + ErrnoError: fsMissing, }; {{{ addAtModule(` diff --git a/test/codesize/test_codesize_hello_O0.json b/test/codesize/test_codesize_hello_O0.json index 7a5d20ab1398f..b0a1a0020f2a1 100644 --- a/test/codesize/test_codesize_hello_O0.json +++ b/test/codesize/test_codesize_hello_O0.json @@ -1,10 +1,10 @@ { - "a.out.js": 24261, - "a.out.js.gz": 8720, + "a.out.js": 24221, + "a.out.js.gz": 8713, "a.out.nodebug.wasm": 14850, "a.out.nodebug.wasm.gz": 7311, - "total": 39111, - "total_gz": 16031, + "total": 39071, + "total_gz": 16024, "sent": [ "fd_write" ], diff --git a/test/codesize/test_codesize_minimal_O0.expected.js b/test/codesize/test_codesize_minimal_O0.expected.js index 41117166dff11..5e1a96d0fa02f 100644 --- a/test/codesize/test_codesize_minimal_O0.expected.js +++ b/test/codesize/test_codesize_minimal_O0.expected.js @@ -544,20 +544,19 @@ function abort(what) { } // show errors on likely calls to FS when it was not included +function fsMissing() { + abort('Filesystem support (FS) was not included. The problem is that you are using files from JS, but files were not used from C/C++, so filesystem support was not auto-included. You can force-include filesystem support with -sFORCE_FILESYSTEM'); +} var FS = { - error() { - abort('Filesystem support (FS) was not included. The problem is that you are using files from JS, but files were not used from C/C++, so filesystem support was not auto-included. You can force-include filesystem support with -sFORCE_FILESYSTEM'); - }, - init() { FS.error() }, - createDataFile() { FS.error() }, - createPreloadedFile() { FS.error() }, - createLazyFile() { FS.error() }, - open() { FS.error() }, - mkdev() { FS.error() }, - registerDevice() { FS.error() }, - analyzePath() { FS.error() }, - - ErrnoError() { FS.error() }, + init: fsMissing, + createDataFile: fsMissing, + createPreloadedFile: fsMissing, + createLazyFile: fsMissing, + open: fsMissing, + mkdev: fsMissing, + registerDevice: fsMissing, + analyzePath: fsMissing, + ErrnoError: fsMissing, }; diff --git a/test/codesize/test_codesize_minimal_O0.json b/test/codesize/test_codesize_minimal_O0.json index ffaad48b0a942..cd89937e5b390 100644 --- a/test/codesize/test_codesize_minimal_O0.json +++ b/test/codesize/test_codesize_minimal_O0.json @@ -1,10 +1,10 @@ { - "a.out.js": 19452, - "a.out.js.gz": 7004, + "a.out.js": 19412, + "a.out.js.gz": 6999, "a.out.nodebug.wasm": 1015, "a.out.nodebug.wasm.gz": 602, - "total": 20467, - "total_gz": 7606, + "total": 20427, + "total_gz": 7601, "sent": [], "imports": [], "exports": [ diff --git a/test/codesize/test_unoptimized_code_size.json b/test/codesize/test_unoptimized_code_size.json index 60b458d2f280f..e65e5f2049dff 100644 --- a/test/codesize/test_unoptimized_code_size.json +++ b/test/codesize/test_unoptimized_code_size.json @@ -1,16 +1,16 @@ { - "hello_world.js": 57078, - "hello_world.js.gz": 17757, + "hello_world.js": 57030, + "hello_world.js.gz": 17755, "hello_world.wasm": 14850, "hello_world.wasm.gz": 7311, "no_asserts.js": 26654, "no_asserts.js.gz": 8901, "no_asserts.wasm": 12010, "no_asserts.wasm.gz": 5880, - "strict.js": 54896, - "strict.js.gz": 17065, + "strict.js": 54848, + "strict.js.gz": 17063, "strict.wasm": 14850, "strict.wasm.gz": 7311, - "total": 180338, - "total_gz": 64225 + "total": 180242, + "total_gz": 64221 } From eacafb3f06229923ee96114d65122b9cd387d3e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20P=C3=A9ron?= Date: Tue, 7 Apr 2026 23:27:28 +0200 Subject: [PATCH 20/42] Harfbuzz add Pkgconfig file (#26645) --- tools/ports/harfbuzz.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/ports/harfbuzz.py b/tools/ports/harfbuzz.py index 5efe4fec7f067..ec651328cd3f6 100644 --- a/tools/ports/harfbuzz.py +++ b/tools/ports/harfbuzz.py @@ -135,6 +135,7 @@ def create(final): cflags.append('-DHB_NO_PRAGMA_GCC_DIAGNOSTIC_ERROR') cflags.append('-DHB_NO_PRAGMA_GCC_DIAGNOSTIC_WARNING') + ports.make_pkg_config('harfbuzz', VERSION, '-sUSE_HARFBUZZ') ports.build_port(os.path.join(source_path, 'src'), final, 'harfbuzz', flags=cflags, srcs=srcs) return [shared.cache.get_lib(get_lib_name(settings), create, what='port')] From f2ad693901a1428f94b51f463d0c0bd86b8b1246 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Tue, 7 Apr 2026 14:51:18 -0700 Subject: [PATCH 21/42] Add design doc: Wasm Worker Pthread Compatibility (#26634) See #26631 --- docs/design/02-wasm-worker-pthread-compat.md | 75 ++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 docs/design/02-wasm-worker-pthread-compat.md diff --git a/docs/design/02-wasm-worker-pthread-compat.md b/docs/design/02-wasm-worker-pthread-compat.md new file mode 100644 index 0000000000000..5fecd315a3e56 --- /dev/null +++ b/docs/design/02-wasm-worker-pthread-compat.md @@ -0,0 +1,75 @@ +# Design Doc: Wasm Worker Pthread Compatibility + +- **Status**: Draft +- **Bug**: https://github.com/emscripten-core/emscripten/issues/26631 + +## Context + +Wasm Workers in Emscripten are a lightweight alternative to pthreads. They use +the same memory and can use the same synchronization primitives, but they do not +have a full `struct pthread` and thus many pthread-based APIs (like +`pthread_self()`) currently do not work when called from a Wasm Worker. + +This is not an issue in pure Wasm Workers programs but we also support hybrid +programs that run both pthreads and Wasm Workers. In this cases the pthread +API is available, but will fail in undefined ways if called from Wasm Workers. + +This document proposes a plan to improve the hybrid mode by adding the pthread +metadata (`struct pthread`) to each Wasm Worker, allowing the pthread API (or at +least some subset of it) APIs to used from Wasm Workers. + +## Proposed Changes + +### 1. Memory Layout + +Currently, Wasm Workers allocate space for TLS and stack: `[TLS data] [Stack]`. +We propose to change this to: `[struct pthread] [TLS data] [Stack]`. + +The `struct pthread` will be located at the very beginning of the allocated +memory block for each Wasm Worker. + +### 2. `struct pthread` Initialization + +The `struct pthread` will be initialized by the creator thread in `emscripten_create_wasm_worker` (or `emscripten_malloc_wasm_worker`). +This includes: +- Zero-initializing the structure. +- Setting the `self` pointer to the start of the `struct pthread`. +- Initializing essential fields like `tid`. + +On the worker thread side, `_emscripten_wasm_worker_initialize` will need to set +the thread-local pointer (returned by `__get_tp()`) to the `struct pthread` +location. + +### 3. `__get_tp` Support + +We will modify `system/lib/pthread/emscripten_thread_state.S` to provide a +`__get_tp` implementation for Wasm workers that returns the address of the +`struct pthread`. This will allow `__pthread_self()` and other related functions +to work correctly. + +### 4. Supported Pthread API Subset + +We intend to support a subset of the pthread API within Wasm workers: +- `pthread_self()`: Returns the worker's `struct pthread` pointer. +- `pthread_equal()`: Works normally. +- `pthread_getspecific()` / `pthread_setspecific()`: TSD (Thread Specific Data) should work if `tsd` field in `struct pthread` is initialized. +- `pthread_mutex_*`: Mutexes will work as they rely on `struct pthread` for owner tracking. +- `pthread_cond_*`: Condition variables will work as they rely on `struct pthread` for waiter tracking. +- Low-level synchronization primitives that use `struct pthread` (e.g., some internal locks). + +APIs that will NOT be supported (or will have limited support): +- `pthread_create()` / `pthread_join()` / `pthread_detach()`: Wasm workers have their own creation and lifecycle management. +- `pthread_cancel()`: Not supported in Wasm workers. +- `pthread_kill()`: Not supported in Wasm workers. + +## Implementation Plan + +1. Modify `emscripten_create_wasm_worker` in `system/lib/wasm_worker/library_wasm_worker.c` to account for `sizeof(struct pthread)` in memory allocation and initialize the structure. +2. Update `_emscripten_wasm_worker_initialize` in `system/lib/wasm_worker/wasm_worker_initialize.S` to set the thread pointer. +3. Modify `system/lib/pthread/emscripten_thread_state.S` to enable `__get_tp` for Wasm workers. +4. Review and test essential pthread functions (like TSD) in Wasm workers. +5. Document the supported and unsupported pthread APIs for Wasm workers. + +## Verification +- Add a new test that makes use of `pthread_self()` and low level synchronization APIs from a Wasm Worker. +- Verify that existing Wasm worker tests still pass. From 317c35d3de80217f23313041bf090ad9113bb113 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Tue, 7 Apr 2026 16:33:23 -0700 Subject: [PATCH 22/42] Add testing for compiler built-in `atomic_is_lock_free` API. NFC (#26646) --- test/pthread/is_lock_free.c | 35 +++++++++++++++++-- .../hardware_concurrency_is_lock_free.c | 24 +++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/test/pthread/is_lock_free.c b/test/pthread/is_lock_free.c index 537fe25f550b2..936240e5546d7 100644 --- a/test/pthread/is_lock_free.c +++ b/test/pthread/is_lock_free.c @@ -1,9 +1,15 @@ -#include -#include #include +#include +#include #include -// Test emscripten_atomics_is_lock_free() functions +#include + +// Test the various `is_lock_free` functions: +// +// - emscripten_atomics_is_lock_free +// - __atomic_always_lock_free +// - atomic_is_lock_free void test() { assert(emscripten_atomics_is_lock_free(1)); @@ -12,7 +18,30 @@ void test() { // Chrome is buggy, see // https://bugs.chromium.org/p/chromium/issues/detail?id=1167449 //assert(emscripten_atomics_is_lock_free(8)); + assert(!emscripten_atomics_is_lock_free(16)); assert(!emscripten_atomics_is_lock_free(31)); + + // Test compiler buildin __atomic_always_lock_free version + assert(__atomic_always_lock_free(1, 0)); + assert(__atomic_always_lock_free(2, 0)); + assert(__atomic_always_lock_free(4, 0)); + assert(__atomic_always_lock_free(8, 0)); + assert(!__atomic_always_lock_free(16, 0)); + assert(!__atomic_always_lock_free(31, 0)); + + // Test C11 atomic_is_lock_free + struct { char a[1]; } one; + struct { char a[2]; } two; + struct { char a[4]; } four; + struct { char a[8]; } eight; + struct { char a[16]; } sixteen; + struct { char a[31]; } thirty_one; + assert(atomic_is_lock_free(&one)); + assert(atomic_is_lock_free(&two)); + assert(atomic_is_lock_free(&four)); + assert(atomic_is_lock_free(&eight)); + assert(!atomic_is_lock_free(&sixteen)); + assert(!atomic_is_lock_free(&thirty_one)); } void* thread_main(void* arg) { diff --git a/test/wasm_worker/hardware_concurrency_is_lock_free.c b/test/wasm_worker/hardware_concurrency_is_lock_free.c index f02b0c6718b55..9d594c00db008 100644 --- a/test/wasm_worker/hardware_concurrency_is_lock_free.c +++ b/test/wasm_worker/hardware_concurrency_is_lock_free.c @@ -1,6 +1,7 @@ #include #include #include +#include // Test emscripten_navigator_hardware_concurrency() and emscripten_atomics_is_lock_free() functions @@ -13,7 +14,30 @@ void test() { // Chrome is buggy, see // https://bugs.chromium.org/p/chromium/issues/detail?id=1167449 //assert(emscripten_atomics_is_lock_free(8)); + assert(!emscripten_atomics_is_lock_free(16)); assert(!emscripten_atomics_is_lock_free(31)); + + // Test compiler buildin __atomic_always_lock_free version + assert(__atomic_always_lock_free(1, 0)); + assert(__atomic_always_lock_free(2, 0)); + assert(__atomic_always_lock_free(4, 0)); + assert(__atomic_always_lock_free(8, 0)); + assert(!__atomic_always_lock_free(16, 0)); + assert(!__atomic_always_lock_free(31, 0)); + + // Test C11 atomic_is_lock_free + struct { char a[1]; } one; + struct { char a[2]; } two; + struct { char a[4]; } four; + struct { char a[8]; } eight; + struct { char a[16]; } sixteen; + struct { char a[31]; } thirty_one; + assert(atomic_is_lock_free(&one)); + assert(atomic_is_lock_free(&two)); + assert(atomic_is_lock_free(&four)); + assert(atomic_is_lock_free(&eight)); + assert(!atomic_is_lock_free(&sixteen)); + assert(!atomic_is_lock_free(&thirty_one)); } void worker_main() { From d984d060ea4d89ea119a3070f09b1b29d9e3b9a4 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Tue, 7 Apr 2026 17:06:16 -0700 Subject: [PATCH 23/42] Fix/remove TODO in is_lock_free tests. NFC (#26649) Returning true here is apparently the correct behavior. See https://issues.chromium.org/issues/40742723 --- test/pthread/is_lock_free.c | 4 +--- test/wasm_worker/hardware_concurrency_is_lock_free.c | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/test/pthread/is_lock_free.c b/test/pthread/is_lock_free.c index 936240e5546d7..a6ea8996cd700 100644 --- a/test/pthread/is_lock_free.c +++ b/test/pthread/is_lock_free.c @@ -15,9 +15,7 @@ void test() { assert(emscripten_atomics_is_lock_free(1)); assert(emscripten_atomics_is_lock_free(2)); assert(emscripten_atomics_is_lock_free(4)); - // Chrome is buggy, see - // https://bugs.chromium.org/p/chromium/issues/detail?id=1167449 - //assert(emscripten_atomics_is_lock_free(8)); + assert(emscripten_atomics_is_lock_free(8)); assert(!emscripten_atomics_is_lock_free(16)); assert(!emscripten_atomics_is_lock_free(31)); diff --git a/test/wasm_worker/hardware_concurrency_is_lock_free.c b/test/wasm_worker/hardware_concurrency_is_lock_free.c index 9d594c00db008..d4ee78666e2ef 100644 --- a/test/wasm_worker/hardware_concurrency_is_lock_free.c +++ b/test/wasm_worker/hardware_concurrency_is_lock_free.c @@ -11,9 +11,7 @@ void test() { assert(emscripten_atomics_is_lock_free(1)); assert(emscripten_atomics_is_lock_free(2)); assert(emscripten_atomics_is_lock_free(4)); - // Chrome is buggy, see - // https://bugs.chromium.org/p/chromium/issues/detail?id=1167449 - //assert(emscripten_atomics_is_lock_free(8)); + assert(emscripten_atomics_is_lock_free(8)); assert(!emscripten_atomics_is_lock_free(16)); assert(!emscripten_atomics_is_lock_free(31)); From 339a66f5c7efa580a19cf3222cf8415c77726955 Mon Sep 17 00:00:00 2001 From: emscripten-bot <179889221+emscripten-bot@users.noreply.github.com> Date: Wed, 8 Apr 2026 09:19:57 -0700 Subject: [PATCH 24/42] Automatic rebaseline of codesize expectations. NFC (#26651) This is an automatic change generated by tools/maint/rebaseline_tests.py. The following (1) test expectation files were updated by running the tests with `--rebaseline`: ``` codesize/test_codesize_cxx_lto.json: 120519 => 120725 [+206 bytes / +0.17%] Average change: +0.17% (+0.17% - +0.17%) ``` Co-authored-by: emscripten-bot --- test/codesize/test_codesize_cxx_lto.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/codesize/test_codesize_cxx_lto.json b/test/codesize/test_codesize_cxx_lto.json index 7bea9337c40d2..979613f5c8d1f 100644 --- a/test/codesize/test_codesize_cxx_lto.json +++ b/test/codesize/test_codesize_cxx_lto.json @@ -1,10 +1,10 @@ { "a.out.js": 18563, "a.out.js.gz": 7666, - "a.out.nodebug.wasm": 101956, - "a.out.nodebug.wasm.gz": 39460, - "total": 120519, - "total_gz": 47126, + "a.out.nodebug.wasm": 102162, + "a.out.nodebug.wasm.gz": 39560, + "total": 120725, + "total_gz": 47226, "sent": [ "a (emscripten_resize_heap)", "b (_setitimer_js)", From 54ef4c628343e4d5e68bfc9dc8198463472d6503 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Wed, 8 Apr 2026 11:14:11 -0700 Subject: [PATCH 25/42] Add `@requires_wasm_workers` decorator. NFC (#26652) This avoids repeating the same set of decorators all over the place. --- test/test_core.py | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/test/test_core.py b/test/test_core.py index 60314248ca320..f0e0a92dcf010 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -161,6 +161,18 @@ def decorated(self, *args, **kwargs): return decorated +def requires_wasm_workers(func): + assert callable(func) + + @wraps(func) + @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with WASM_WORKERS') + @no_sanitize('sanitizers do not support WASM_WORKERS') + def decorated(self, *args, **kwargs): + return func(self, *args, **kwargs) + + return decorated + + def wasm_relaxed_simd(func): assert callable(func) @@ -287,13 +299,11 @@ def also_with_wasm_workers(func): @wraps(func) def metafunc(self, ww, *args, **kwargs): + f = func if ww: - if self.get_setting('WASM_ESM_INTEGRATION'): - self.skipTest('WASM_ESM_INTEGRATION is not compatible with WASM_WORKERS') - if is_sanitizing(self.cflags): - self.skipTest('sanitizers are not compatible with WASM_WORKERS') + f = requires_wasm_workers(f) self.cflags += ['-sWASM_WORKERS'] - return func(self, *args, **kwargs) + return f(self, *args, **kwargs) parameterize(metafunc, {'': (False,), 'ww': (True,)}) return metafunc @@ -991,8 +1001,7 @@ def test_longjmp_standalone(self): def test_longjmp(self): self.do_core_test('test_longjmp.c') - @no_sanitize('sanitizers do not support WASM_WORKERS') - @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with WASM_WORKERS') + @requires_wasm_workers def test_longjmp_wasm_workers(self): self.do_core_test('test_longjmp.c', cflags=['-sWASM_WORKERS']) @@ -7770,8 +7779,7 @@ def test_embind_no_rtti_followed_by_rtti(self): ''' self.do_run(src, '418\ndotest returned: 42\n', cflags=['-lembind', '-fno-rtti', '-frtti']) - @no_sanitize('sanitizers do not support WASM_WORKERS') - @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with WASM_WORKERS') + @requires_wasm_workers def test_embind_wasm_workers(self): self.do_run_in_out_file_test('embind/test_embind_wasm_workers.cpp', cflags=['-lembind', '-sWASM_WORKERS']) @@ -9259,8 +9267,7 @@ def test_emscripten_futex_api_basics(self): def test_stdio_locking(self): self.do_core_test('test_stdio_locking.c', cflags=['-sPTHREAD_POOL_SIZE=2']) - @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with WASM_WORKERS') - @no_sanitize('sanitizers do not support WASM_WORKERS') + @requires_wasm_workers def test_stdio_locking_ww(self): # Note: do not combine with test_stdio_locking above because we want to test standalone # wasm workers here and `@requires_pthreads` would prevent that. @@ -9697,33 +9704,28 @@ def test_emscripten_async_load_script(self): self.run_process([FILE_PACKAGER, 'test.data', '--preload', 'file1.txt', 'file2.txt', '--from-emcc', '--js-output=script2.js']) self.do_runf('test_emscripten_async_load_script.c', cflags=['-sFORCE_FILESYSTEM']) - @no_sanitize('sanitizers do not support WASM_WORKERS') @also_with_minimal_runtime @also_with_modularize - @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with WASM_WORKERS') + @requires_wasm_workers def test_wasm_worker_hello(self): if self.is_wasm2js() and '-sMODULARIZE' in self.cflags: self.skipTest('WASM2JS + MODULARIZE + WASM_WORKERS is not supported') self.maybe_closure() self.do_run_in_out_file_test('wasm_worker/hello_wasm_worker.c', cflags=['-sWASM_WORKERS']) - @no_sanitize('sanitizers do not support WASM_WORKERS') - @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with WASM_WORKERS') + @requires_wasm_workers def test_wasm_worker_malloc(self): self.do_run_in_out_file_test('wasm_worker/malloc_wasm_worker.c', cflags=['-sWASM_WORKERS']) - @no_sanitize('sanitizers do not support WASM_WORKERS') - @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with WASM_WORKERS') + @requires_wasm_workers def test_wasm_worker_runtime_debug(self): self.do_runf('wasm_worker/hello_wasm_worker.c', 'wasm worker starting ...', cflags=['-sWASM_WORKERS', '-sRUNTIME_DEBUG']) - @no_sanitize('sanitizers do not support WASM_WORKERS') - @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with WASM_WORKERS') + @requires_wasm_workers def test_wasm_worker_futex_wait(self): self.do_runf('wasm_worker/wasm_worker_futex_wait.c', cflags=['-sWASM_WORKERS']) - @no_sanitize('sanitizers do not support WASM_WORKERS') - @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with WASM_WORKERS') + @requires_wasm_workers def test_wasm_worker_wait_async(self): self.do_runf('atomic/test_wait_async.c', cflags=['-sWASM_WORKERS']) From 4cadbe344f6b9f471a609637588ea6d99a3d100c Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Wed, 8 Apr 2026 12:00:47 -0700 Subject: [PATCH 26/42] Fix for exceptions in Wasm Workers under node (#26650) Under node we install the `uncaughtException` handler in workers (both in pthread and wasm worker). This means that `uncaughtException` is sent via `postMessage` back to the main thread. See https://github.com/nodejs/node/issues/59617 for why we do this. However, these message was simply being ignored by the main thread in the case of Wasm Workers. With this change we honor the `uncaughtException` message and avoid completely loosing the uncaught exception. --- src/lib/libwasm_worker.js | 12 +++++++++++- test/test_core.py | 4 ++++ .../wasm_worker/test_wasm_worker_exceptions.c | 19 +++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 test/wasm_worker/test_wasm_worker_exceptions.c diff --git a/src/lib/libwasm_worker.js b/src/lib/libwasm_worker.js index e4eb8491389c1..1b7aed785d7d6 100644 --- a/src/lib/libwasm_worker.js +++ b/src/lib/libwasm_worker.js @@ -223,7 +223,17 @@ if (ENVIRONMENT_IS_WASM_WORKER #if ENVIRONMENT_MAY_BE_NODE if (ENVIRONMENT_IS_NODE) { /** @suppress {checkTypes} */ - worker.on('message', (msg) => worker.onmessage({ data: msg })); + worker.on('message', (msg) => { + if (msg['cmd'] == 'uncaughtException') { + // Message handler for Node.js specific out-of-order behavior: + // https://github.com/nodejs/node/issues/59617 + // A worker sent an uncaught exception event. Re-raise it on the main thread. + err(`worker sent an error! ${msg.error.message}`); + throw msg.error; + } else { + worker.onmessage({ data: msg }); + } + }); } #endif #if RUNTIME_DEBUG diff --git a/test/test_core.py b/test/test_core.py index f0e0a92dcf010..5dd5fd029a8f7 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -9713,6 +9713,10 @@ def test_wasm_worker_hello(self): self.maybe_closure() self.do_run_in_out_file_test('wasm_worker/hello_wasm_worker.c', cflags=['-sWASM_WORKERS']) + @requires_wasm_workers + def test_wasm_worker_exceptions(self): + self.do_runf('wasm_worker/test_wasm_worker_exceptions.c', 'worker sent an error! Aborted', assert_returncode=NON_ZERO, cflags=['-sWASM_WORKERS']) + @requires_wasm_workers def test_wasm_worker_malloc(self): self.do_run_in_out_file_test('wasm_worker/malloc_wasm_worker.c', cflags=['-sWASM_WORKERS']) diff --git a/test/wasm_worker/test_wasm_worker_exceptions.c b/test/wasm_worker/test_wasm_worker_exceptions.c new file mode 100644 index 0000000000000..976a2b3de5ec7 --- /dev/null +++ b/test/wasm_worker/test_wasm_worker_exceptions.c @@ -0,0 +1,19 @@ +#include +#include + +#include +#include +#include +#include + +void do_abort() { + emscripten_out("Hello from wasm worker!"); + abort(); +} + +int main() { + emscripten_out("in main"); + emscripten_wasm_worker_post_function_v(emscripten_malloc_wasm_worker(1024), do_abort); + emscripten_exit_with_live_runtime(); + assert(false && "should never get here"); +} From 547fb26f957c0129e09ca95b06b711da0c247f5f Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Wed, 8 Apr 2026 14:08:48 -0700 Subject: [PATCH 27/42] Mark `-sDETERMINISIC` as deprecated. (#26653) This does not remove anything, just generates a warning if the setting is used. See #26647 and #26648 --- ChangeLog.md | 1 + site/source/docs/tools_reference/settings_reference.rst | 3 +++ src/settings.js | 1 + test/test_other.py | 2 +- tools/settings.py | 1 + 5 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 15700a09b91e7..a43b714166d62 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -22,6 +22,7 @@ See docs/process.md for more on how version tagging works. ---------------------- - The minimum version of node supported by the generated code was bumped from v12.22.0 to v18.3.0. (#26604) +- The DETERMINISIC settings was marked as deprecated () 5.0.5 - 04/03/26 ---------------- diff --git a/site/source/docs/tools_reference/settings_reference.rst b/site/source/docs/tools_reference/settings_reference.rst index a8de5a33b2acd..b9962d866639c 100644 --- a/site/source/docs/tools_reference/settings_reference.rst +++ b/site/source/docs/tools_reference/settings_reference.rst @@ -1787,6 +1787,8 @@ browser's language setting (which would mean you can get different results in different browsers, or in the browser and in node). Good for comparing builds for debugging purposes (and nothing else). +.. note:: This setting is deprecated + Default value: false .. _modularize: @@ -3421,6 +3423,7 @@ these settings please open a bug (or reply to one of the existing bugs). - ``ASYNCIFY_EXPORTS``: please use JSPI_EXPORTS instead - ``LINKABLE``: under consideration for removal (https://github.com/emscripten-core/emscripten/issues/25262) - ``EXPORT_EXCEPTION_HANDLING_HELPERS``: getExceptionMessage is exported anyway when ASSERTIONS or EXCEPTION_STACK_TRACES is set, which are set by default at -O0. At -O1 or above, you can export it separately by -sEXPORTED_RUNTIME_METHODS=getExceptionMessage,decrementExceptionRefcount. + - ``DETERMINISTIC``: under consideration for removal (https://github.com/emscripten-core/emscripten/issues/26647) .. _legacy-settings: diff --git a/src/settings.js b/src/settings.js index fc50d8e0983f5..fdb3a4ad1864e 100644 --- a/src/settings.js +++ b/src/settings.js @@ -1213,6 +1213,7 @@ var SMALL_XHR_CHUNKS = false; // in different browsers, or in the browser and in node). // Good for comparing builds for debugging purposes (and nothing else). // [link] +// [deprecated] var DETERMINISTIC = false; // By default we emit all code in a straightforward way into the output diff --git a/test/test_other.py b/test/test_other.py index 3ac05d902c1ee..e948678f1b4fe 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -12059,7 +12059,7 @@ def test_deterministic(self): printf("JS random: %d\n", EM_ASM_INT({ return Math.random() })); } ''') - self.run_process([EMCC, 'src.c', '-sDETERMINISTIC'] + self.get_cflags()) + self.run_process([EMCC, 'src.c', '-sDETERMINISTIC', '-Wno-deprecated'] + self.get_cflags()) one = self.run_js('a.out.js') # ensure even if the time resolution is 1 second, that if we see the real # time we'll see a difference diff --git a/tools/settings.py b/tools/settings.py index ce511e75eb1d5..b82206bcc9d44 100644 --- a/tools/settings.py +++ b/tools/settings.py @@ -115,6 +115,7 @@ 'ASYNCIFY_EXPORTS': 'please use JSPI_EXPORTS instead', 'LINKABLE': 'under consideration for removal (https://github.com/emscripten-core/emscripten/issues/25262)', 'EXPORT_EXCEPTION_HANDLING_HELPERS': 'getExceptionMessage is exported anyway when ASSERTIONS or EXCEPTION_STACK_TRACES is set, which are set by default at -O0. At -O1 or above, you can export it separately by -sEXPORTED_RUNTIME_METHODS=getExceptionMessage,decrementExceptionRefcount.', + 'DETERMINISTIC': 'under consideration for removal (https://github.com/emscripten-core/emscripten/issues/26647)', } # Settings that don't need to be externalized when serializing to json because they From 9a755c02d8c3a834e21ea55f1eb4c964a3c9877a Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Wed, 8 Apr 2026 14:50:26 -0700 Subject: [PATCH 28/42] Remove redundant min version checks (#26654) We dropped support for firefox older than 58 a long time back. We dropped support node older than 18.1 in #26604. --- tools/minimal_runtime_shell.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/minimal_runtime_shell.py b/tools/minimal_runtime_shell.py index c7f50fb1450b1..2b5a77d4b0786 100644 --- a/tools/minimal_runtime_shell.py +++ b/tools/minimal_runtime_shell.py @@ -26,7 +26,7 @@ def generate_minimal_runtime_load_statement(target_basename): # Expand {{{ DOWNLOAD_WASM }}} block from here (if we added #define support, this could be done in # the template directly) if settings.MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION: - if settings.MIN_SAFARI_VERSION < 150000 or settings.MIN_NODE_VERSION < 180100 or settings.MIN_FIREFOX_VERSION < 58: + if settings.MIN_SAFARI_VERSION < 150000: # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/compileStreaming download_wasm = f"WebAssembly.compileStreaming ? WebAssembly.compileStreaming(fetch('{target_basename}.wasm')) : binary('{target_basename}.wasm')" else: @@ -35,7 +35,7 @@ def generate_minimal_runtime_load_statement(target_basename): elif settings.MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION: # Same compatibility story as above for # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiateStreaming - if settings.MIN_SAFARI_VERSION < 150000 or settings.MIN_NODE_VERSION < 180100 or settings.MIN_FIREFOX_VERSION < 58: + if settings.MIN_SAFARI_VERSION < 150000: download_wasm = f"!WebAssembly.instantiateStreaming && binary('{target_basename}.wasm')" else: # WebAssembly.instantiateStreaming() is unconditionally supported, so we do not download wasm From 1738682e3d60c99fa04abc1d99d59fe47bdc2875 Mon Sep 17 00:00:00 2001 From: emscripten-bot <179889221+emscripten-bot@users.noreply.github.com> Date: Wed, 8 Apr 2026 23:04:04 -0700 Subject: [PATCH 29/42] Automatic rebaseline of codesize expectations. NFC (#26656) This is an automatic change generated by tools/maint/rebaseline_tests.py. The following (4) test expectation files were updated by running the tests with `--rebaseline`: ``` codesize/test_minimal_runtime_code_size_hello_webgl2_wasm.json: 13200 => 13200 [+0 bytes / +0.00%] codesize/test_minimal_runtime_code_size_hello_webgl2_wasm2js.json: 18534 => 18537 [+3 bytes / +0.02%] codesize/test_minimal_runtime_code_size_hello_webgl_wasm.json: 12738 => 12738 [+0 bytes / +0.00%] codesize/test_minimal_runtime_code_size_hello_webgl_wasm2js.json: 18060 => 18063 [+3 bytes / +0.02%] Average change: +0.01% (+0.00% - +0.02%) ``` Co-authored-by: emscripten-bot --- .../test_minimal_runtime_code_size_hello_webgl2_wasm.json | 4 ++-- ...st_minimal_runtime_code_size_hello_webgl2_wasm2js.json | 8 ++++---- .../test_minimal_runtime_code_size_hello_webgl_wasm.json | 4 ++-- ...est_minimal_runtime_code_size_hello_webgl_wasm2js.json | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/codesize/test_minimal_runtime_code_size_hello_webgl2_wasm.json b/test/codesize/test_minimal_runtime_code_size_hello_webgl2_wasm.json index f74de1ab6a6f7..65d134eece5d2 100644 --- a/test/codesize/test_minimal_runtime_code_size_hello_webgl2_wasm.json +++ b/test/codesize/test_minimal_runtime_code_size_hello_webgl2_wasm.json @@ -4,7 +4,7 @@ "a.js": 4437, "a.js.gz": 2281, "a.wasm": 8313, - "a.wasm.gz": 5648, + "a.wasm.gz": 5646, "total": 13200, - "total_gz": 8247 + "total_gz": 8245 } diff --git a/test/codesize/test_minimal_runtime_code_size_hello_webgl2_wasm2js.json b/test/codesize/test_minimal_runtime_code_size_hello_webgl2_wasm2js.json index 09d11b0682f79..93c8647732266 100644 --- a/test/codesize/test_minimal_runtime_code_size_hello_webgl2_wasm2js.json +++ b/test/codesize/test_minimal_runtime_code_size_hello_webgl2_wasm2js.json @@ -1,8 +1,8 @@ { "a.html": 342, "a.html.gz": 252, - "a.js": 18192, - "a.js.gz": 9826, - "total": 18534, - "total_gz": 10078 + "a.js": 18195, + "a.js.gz": 9829, + "total": 18537, + "total_gz": 10081 } diff --git a/test/codesize/test_minimal_runtime_code_size_hello_webgl_wasm.json b/test/codesize/test_minimal_runtime_code_size_hello_webgl_wasm.json index 61816037cdae4..75607a4d6a1d8 100644 --- a/test/codesize/test_minimal_runtime_code_size_hello_webgl_wasm.json +++ b/test/codesize/test_minimal_runtime_code_size_hello_webgl_wasm.json @@ -4,7 +4,7 @@ "a.js": 3975, "a.js.gz": 2123, "a.wasm": 8313, - "a.wasm.gz": 5648, + "a.wasm.gz": 5646, "total": 12738, - "total_gz": 8089 + "total_gz": 8087 } diff --git a/test/codesize/test_minimal_runtime_code_size_hello_webgl_wasm2js.json b/test/codesize/test_minimal_runtime_code_size_hello_webgl_wasm2js.json index 3ea609d71d2e8..c4a9691ca6749 100644 --- a/test/codesize/test_minimal_runtime_code_size_hello_webgl_wasm2js.json +++ b/test/codesize/test_minimal_runtime_code_size_hello_webgl_wasm2js.json @@ -1,8 +1,8 @@ { "a.html": 342, "a.html.gz": 252, - "a.js": 17718, - "a.js.gz": 9662, - "total": 18060, - "total_gz": 9914 + "a.js": 17721, + "a.js.gz": 9665, + "total": 18063, + "total_gz": 9917 } From 03c9ee7a885b163d7332a43c3bd294de645488a2 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Thu, 9 Apr 2026 07:54:05 -0700 Subject: [PATCH 30/42] Minor cleanup of setitimer. NFC (#26655) These minor cleanups were split out from my larger work on precise wakeups. --- system/lib/libc/musl/src/signal/setitimer.c | 13 ++++++++++--- system/lib/pthread/emscripten_futex_wait.c | 3 ++- system/lib/pthread/emscripten_yield.c | 4 +--- test/codesize/test_codesize_hello_dylink_all.json | 4 ++-- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/system/lib/libc/musl/src/signal/setitimer.c b/system/lib/libc/musl/src/signal/setitimer.c index b15986a0c0870..5e9b4683ee067 100644 --- a/system/lib/libc/musl/src/signal/setitimer.c +++ b/system/lib/libc/musl/src/signal/setitimer.c @@ -6,6 +6,8 @@ #ifdef __EMSCRIPTEN__ #include +#include +#include #include #include #include @@ -63,6 +65,9 @@ void _emscripten_timeout(int which, double now) void _emscripten_check_timers(double now) { + // Timers always run on the main runtime thread. They are registered with + // _setitimer_js which is proxied to the main runtime thread. + assert(emscripten_is_main_runtime_thread()); for (int which = 0; which < 3; which++) { if (current_timeout_ms[which]) { // Only call out to JS to get the current time if it was not passed in @@ -84,14 +89,16 @@ int setitimer(int which, const struct itimerval *restrict new, struct itimerval if (old) { __getitimer(which, old, now); } + double timeout_ms = new->it_value.tv_sec * 1000 + new->it_value.tv_usec / 1000.0; + double interval_ms = new->it_interval.tv_sec * 1000 + new->it_interval.tv_usec / 1000.0; if (new->it_value.tv_sec || new->it_value.tv_usec) { - current_timeout_ms[which] = now + new->it_value.tv_sec * 1000 + new->it_value.tv_usec / 1000; - current_intervals_ms[which] = new->it_interval.tv_sec * 1000 + new->it_interval.tv_usec / 1000; + current_timeout_ms[which] = now + timeout_ms; + current_intervals_ms[which] = interval_ms; } else { current_timeout_ms[which] = 0; current_intervals_ms[which] = 0; } - return _setitimer_js(which, new->it_value.tv_sec * 1000 + new->it_value.tv_usec / 1000); + return _setitimer_js(which, timeout_ms); #else if (sizeof(time_t) > sizeof(long)) { time_t is = new->it_interval.tv_sec, vs = new->it_value.tv_sec; diff --git a/system/lib/pthread/emscripten_futex_wait.c b/system/lib/pthread/emscripten_futex_wait.c index 869bb7f8f83c4..0584edccbd57e 100644 --- a/system/lib/pthread/emscripten_futex_wait.c +++ b/system/lib/pthread/emscripten_futex_wait.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -111,7 +112,7 @@ static int futex_wait_main_browser_thread(volatile void* addr, // here and return, it's the same is if what happened on the main thread // was the same as calling _emscripten_yield() // a few times before calling emscripten_futex_wait(). - if (__c11_atomic_load((_Atomic uint32_t*)addr, __ATOMIC_SEQ_CST) != val) { + if (atomic_load((_Atomic uint32_t*)addr) != val) { return -EWOULDBLOCK; } diff --git a/system/lib/pthread/emscripten_yield.c b/system/lib/pthread/emscripten_yield.c index 70c20fec55fd9..fa3e0398b70a2 100644 --- a/system/lib/pthread/emscripten_yield.c +++ b/system/lib/pthread/emscripten_yield.c @@ -20,14 +20,12 @@ static void dummy(double now) weak_alias(dummy, _emscripten_check_timers); void _emscripten_yield(double now) { - int is_runtime_thread = emscripten_is_main_runtime_thread(); - // When a secondary thread crashes, we need to be able to interrupt the main // thread even if it's in a blocking/looping on a mutex. We want to avoid // using the normal proxying mechanism to send this message since it can // allocate (or otherwise itself crash) so use a low level atomic primitive // for this signal. - if (is_runtime_thread) { + if (emscripten_is_main_runtime_thread()) { if (crashed_thread_id) { // Mark the crashed thread as strongly referenced so that Node.js doesn't // exit while the pthread is propagating the uncaught exception back to diff --git a/test/codesize/test_codesize_hello_dylink_all.json b/test/codesize/test_codesize_hello_dylink_all.json index 8eaf79b3ee12a..0c55f1f87dc11 100644 --- a/test/codesize/test_codesize_hello_dylink_all.json +++ b/test/codesize/test_codesize_hello_dylink_all.json @@ -1,7 +1,7 @@ { "a.out.js": 244343, - "a.out.nodebug.wasm": 577447, - "total": 821790, + "a.out.nodebug.wasm": 577432, + "total": 821775, "sent": [ "IMG_Init", "IMG_Load", From 7481de081c4cd2fad4afabe8985721f91e9a3187 Mon Sep 17 00:00:00 2001 From: stephenduong1004 Date: Thu, 9 Apr 2026 14:26:42 -0400 Subject: [PATCH 31/42] Change start WW id value in hybrid threading mode (#26660) Linux TID/PID values have a max limit of 4,194,304. We should mimic that and start WW id at 4,194,304/2. --- src/lib/libwasm_worker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/libwasm_worker.js b/src/lib/libwasm_worker.js index 1b7aed785d7d6..af8b8930104d5 100644 --- a/src/lib/libwasm_worker.js +++ b/src/lib/libwasm_worker.js @@ -65,10 +65,10 @@ addToLibrary({ #if PTHREADS // When the build contains both pthreads and Wasm Workers, offset the // Wasm Worker ID space to avoid collisions with pthread TIDs (which start - // at 42). We use `1 << 30` since it's ~1/2 way through `pid_t` space, + // at 42). We use `1 << 21` since it's ~1/2 way through `pid_t` space, // essentially giving pthreads the first 1/2 of the range and wasm workers the // second half. - $_wasmWorkersID: {{{ 1 << 30 }}}, + $_wasmWorkersID: {{{ 1 << 21 }}}, #else $_wasmWorkersID: 1, #endif From c22a176c400f51aea33f5bf9f8ae8d5a089aa505 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Thu, 9 Apr 2026 13:01:49 -0700 Subject: [PATCH 32/42] [ci] Use newly official github action to install emsdk (#26662) See https://github.com/emscripten-core/setup-emsdk --- .github/workflows/ci.yml | 20 +++++++------------- .github/workflows/rebaseline-tests.yml | 21 +++++++-------------- 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 619fad1570663..be9dfcd59af6f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,13 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'pull_request' steps: + - name: Install emsdk + uses: emscripten-core/setup-emsdk@v16 + with: + version: tot + - name: Set EM_CONFIG + run: + echo "EM_CONFIG=$EMSDK/.emscripten" >> $GITHUB_ENV - name: Checkout repo uses: actions/checkout@v4 with: @@ -42,19 +49,6 @@ jobs: which python3 python3 --version python3 -m pip install -r requirements-dev.txt - - name: Install emsdk - run: | - EM_CONFIG=$HOME/emsdk/.emscripten - echo "emscripten config file: $EM_CONFIG" - echo "EM_CONFIG=$EM_CONFIG" >> $GITHUB_ENV - curl -# -L -o ~/emsdk-main.tar.gz https://github.com/emscripten-core/emsdk/archive/main.tar.gz - tar -C ~ -xf ~/emsdk-main.tar.gz - mv ~/emsdk-main ~/emsdk - cd ~/emsdk - ./emsdk install tot - ./emsdk activate tot - echo "emscripten config file ($EM_CONFIG) contents:" - cat $EM_CONFIG - name: Check test expectations on target branch # Skip this step for rebaseline PRs, since the target branch is expected # to be out of date in this case. diff --git a/.github/workflows/rebaseline-tests.yml b/.github/workflows/rebaseline-tests.yml index 82f87e9a5a82d..48092dd999e8d 100644 --- a/.github/workflows/rebaseline-tests.yml +++ b/.github/workflows/rebaseline-tests.yml @@ -17,6 +17,13 @@ jobs: env: GH_TOKEN: ${{ secrets.EMSCRIPTEN_BOT_TOKEN }} steps: + - name: Install emsdk + uses: emscripten-core/setup-emsdk@v16 + with: + version: tot + - name: Set EM_CONFIG + run: + echo "EM_CONFIG=$EMSDK/.emscripten" >> $GITHUB_ENV - name: Checkout repo uses: actions/checkout@v4 with: @@ -27,20 +34,6 @@ jobs: which python3 python3 --version python3 -m pip install -r requirements-dev.txt - - name: Install emsdk - run: | - EM_CONFIG=$HOME/emsdk/.emscripten - echo $EM_CONFIG - curl -# -L -o ~/emsdk-main.tar.gz https://github.com/emscripten-core/emsdk/archive/main.tar.gz - tar -C ~ -xf ~/emsdk-main.tar.gz - mv ~/emsdk-main ~/emsdk - cd ~/emsdk - ./emsdk install tot - ./emsdk activate tot - echo "JS_ENGINES = [NODE_JS]" >> $EM_CONFIG - echo "final config:" - cat $EM_CONFIG - echo "EM_CONFIG=$EM_CONFIG" >> $GITHUB_ENV - name: Rebaseline tests run: | git config user.name emscripten-bot From fdbd73c8f4f83f03636e05ca47139d75a6d7150f Mon Sep 17 00:00:00 2001 From: stephenduong1004 Date: Thu, 9 Apr 2026 18:41:12 -0400 Subject: [PATCH 33/42] Fix multiple jscompiler errors (#26632) Fix the following errors after https://github.com/emscripten-core/emscripten/pull/26424: ``` - ERROR - [JSC_BAD_JSDOC_ANNOTATION] Parse error. illegal use of unknown JSDoc tag "noreturn"; ignoring it. Place another character before the @ to stop JSCompiler from parsing it as an annotation. - ERROR - [JSC_USED_GLOBAL_THIS] dangerous use of the global this object. ``` --- src/lib/libembind.js | 4 ++-- src/lib/libwasi.js | 1 - src/preamble.js | 1 - .../test_codesize_file_preload.expected.js | 3 +-- .../test_codesize_minimal_O0.expected.js | 1 - test/codesize/test_unoptimized_code_size.json | 16 ++++++++-------- 6 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/lib/libembind.js b/src/lib/libembind.js index defd2498dcff9..19080626392f3 100644 --- a/src/lib/libembind.js +++ b/src/lib/libembind.js @@ -375,7 +375,7 @@ var LibraryEmbind = { } #if ASSERTIONS else if (typeof value != "bigint") { - throw new TypeError(`Cannot convert "${embindRepr(value)}" to ${this.name}`); + throw new TypeError(`Cannot convert "${embindRepr(value)}" to ${name}`); } assertIntegerRange(name, value, minRange, maxRange); #endif @@ -404,7 +404,7 @@ var LibraryEmbind = { toWireType: (destructors, value) => { #if ASSERTIONS if (typeof value != "number" && typeof value != "boolean") { - throw new TypeError(`Cannot convert ${embindRepr(value)} to ${this.name}`); + throw new TypeError(`Cannot convert ${embindRepr(value)} to ${name}`); } #endif // The VM will perform JS to Wasm value conversion, according to the spec: diff --git a/src/lib/libwasi.js b/src/lib/libwasi.js index e83ba664d6b4b..ca3a218553d99 100644 --- a/src/lib/libwasi.js +++ b/src/lib/libwasi.js @@ -17,7 +17,6 @@ var WasiLibrary = { #endif proc_exit__nothrow: true, - proc_exit__docs: '/** @noreturn */', proc_exit: (code) => { #if MINIMAL_RUNTIME throw `exit(${code})`; diff --git a/src/preamble.js b/src/preamble.js index d92557f5757b2..bb7d09e98ce46 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -237,7 +237,6 @@ function postRun() { /** * @param {string|number=} what - * @noreturn */ function abort(what) { #if expectToReceiveOnModule('onAbort') diff --git a/test/codesize/test_codesize_file_preload.expected.js b/test/codesize/test_codesize_file_preload.expected.js index 785751e68113b..1ab70743544e1 100644 --- a/test/codesize/test_codesize_file_preload.expected.js +++ b/test/codesize/test_codesize_file_preload.expected.js @@ -363,7 +363,6 @@ function postRun() {} /** * @param {string|number=} what - * @noreturn */ function abort(what) { what = `Aborted(${what})`; // TODO(sbc): Should we remove printing and leave it up to whoever @@ -3082,7 +3081,7 @@ function _fd_write(fd, iov, iovcnt, pnum) { var keepRuntimeAlive = () => true; -/** @noreturn */ var _proc_exit = code => { +var _proc_exit = code => { EXITSTATUS = code; if (!keepRuntimeAlive()) { ABORT = true; diff --git a/test/codesize/test_codesize_minimal_O0.expected.js b/test/codesize/test_codesize_minimal_O0.expected.js index 5e1a96d0fa02f..90ccede8fa797 100644 --- a/test/codesize/test_codesize_minimal_O0.expected.js +++ b/test/codesize/test_codesize_minimal_O0.expected.js @@ -510,7 +510,6 @@ function postRun() { /** * @param {string|number=} what - * @noreturn */ function abort(what) { diff --git a/test/codesize/test_unoptimized_code_size.json b/test/codesize/test_unoptimized_code_size.json index e65e5f2049dff..f9e256d7fbca2 100644 --- a/test/codesize/test_unoptimized_code_size.json +++ b/test/codesize/test_unoptimized_code_size.json @@ -1,16 +1,16 @@ { - "hello_world.js": 57030, - "hello_world.js.gz": 17755, + "hello_world.js": 56998, + "hello_world.js.gz": 17743, "hello_world.wasm": 14850, "hello_world.wasm.gz": 7311, - "no_asserts.js": 26654, - "no_asserts.js.gz": 8901, + "no_asserts.js": 26622, + "no_asserts.js.gz": 8888, "no_asserts.wasm": 12010, "no_asserts.wasm.gz": 5880, - "strict.js": 54848, - "strict.js.gz": 17063, + "strict.js": 54816, + "strict.js.gz": 17052, "strict.wasm": 14850, "strict.wasm.gz": 7311, - "total": 180242, - "total_gz": 64221 + "total": 180146, + "total_gz": 64185 } From 7b7fd47a48652b5babcc7730141141af9c93fc2d Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Thu, 9 Apr 2026 15:45:33 -0700 Subject: [PATCH 34/42] Temporarily disabled a bunch of circleci testing (#26664) We are nearing the end of our budget, so for a few weeks we need to cut back. --- .circleci/config.yml | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 713713b1006cf..8e762fe57fd27 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1367,15 +1367,15 @@ workflows: - test-core0: requires: - build-linux - - test-core2: - requires: - - build-linux + #- test-core2: + # requires: + # - build-linux - test-core3: requires: - build-linux - - test-wasm64-misc: - requires: - - build-linux + #- test-wasm64-misc: + # requires: + # - build-linux - test-wasm64-4gb: requires: - build-linux @@ -1388,9 +1388,9 @@ workflows: - test-modularize-instance: requires: - build-linux - - test-esm-integration: - requires: - - build-linux + #- test-esm-integration: + # requires: + # - build-linux - test-stress: requires: - build-linux @@ -1411,14 +1411,14 @@ workflows: - test-sockets-chrome: requires: - build-linux - - test-bun - - test-deno - - test-jsc - - test-spidermonkey - - test-node-compat - - test-windows - - test-windows-browser-firefox + #- test-bun + #- test-deno + #- test-jsc + #- test-spidermonkey + #- test-node-compat + #- test-windows + #- test-windows-browser-firefox - build-windows-launcher - - test-mac-arm64: - requires: - - build-linux + #- test-mac-arm64: + # requires: + # - build-linux From dc413f8481d49af201d4b3451f6581d0cb1bad81 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Thu, 9 Apr 2026 15:51:30 -0700 Subject: [PATCH 35/42] Avoid shipping internal musl headers as part of the sysroot (#26658) It turns out that the files in `musl/arch/generic/` and `musl/arch/emscripten/` are supposed to me internal headers even though the `bits/` subdirectories are public. This change means that `syscall_arch.h` is not longer visible outside of the musl build (e.g. when building wasmfs) so I moved the public function declarations to `emscripten/syscalls.h`. --- ChangeLog.md | 5 +- system/include/emscripten/syscalls.h | 120 ++++++++++++++++++ system/lib/libc/emscripten_syscall_stubs.c | 2 +- .../libc/musl/arch/emscripten/syscall_arch.h | 115 +---------------- system/lib/wasmfs/js_api.cpp | 2 +- system/lib/wasmfs/syscalls.cpp | 2 +- tools/gen_struct_info.py | 2 + tools/system_libs.py | 7 +- 8 files changed, 137 insertions(+), 118 deletions(-) create mode 100644 system/include/emscripten/syscalls.h diff --git a/ChangeLog.md b/ChangeLog.md index a43b714166d62..2e1d037f5970f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -22,7 +22,10 @@ See docs/process.md for more on how version tagging works. ---------------------- - The minimum version of node supported by the generated code was bumped from v12.22.0 to v18.3.0. (#26604) -- The DETERMINISIC settings was marked as deprecated () +- The DETERMINISIC settings was marked as deprecated (#26653) +- Some musl-internal headers are no longer installed into the sysroot include + directory. In particular, `syscall_arch.h` no longer exists, but can be + replaced with `emscripten/syscalls.h`. (#26658) 5.0.5 - 04/03/26 ---------------- diff --git a/system/include/emscripten/syscalls.h b/system/include/emscripten/syscalls.h new file mode 100644 index 0000000000000..218a8bad4281e --- /dev/null +++ b/system/include/emscripten/syscalls.h @@ -0,0 +1,120 @@ +/* + * Copyright 2026 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int __syscall_chdir(intptr_t path); +int __syscall_mknod(intptr_t path, int mode, int dev); +int __syscall_chmod(intptr_t path, int mode); +int __syscall_getpid(void); +int __syscall_pause(void); +int __syscall_access(intptr_t path, int amode); +int __syscall_sync(void); +int __syscall_rmdir(intptr_t path); +int __syscall_dup(int fd); +int __syscall_acct(intptr_t filename); +int __syscall_ioctl(int fd, int request, ...); +int __syscall_setpgid(int pid, int gpid); +int __syscall_umask(int mask); +int __syscall_getppid(void); +int __syscall_getpgrp(void); +int __syscall_setsid(void); +int __syscall_getrusage(int who, intptr_t usage); +int __syscall_munmap(intptr_t addr, size_t len); +int __syscall_fchmod(int fd, int mode); +int __syscall_getpriority(int which, int who); +int __syscall_setpriority(int which, int who, int prio); +int __syscall_socketcall(int call, intptr_t args); +int __syscall_wait4(int pid, intptr_t wstatus, int options, int rusage); +int __syscall_setdomainname(intptr_t name, size_t size); +int __syscall_uname(intptr_t buf); +int __syscall_mprotect(size_t addr, size_t len, int prot); +int __syscall_getpgid(int pid); +int __syscall_fchdir(int fd); +int __syscall_msync(intptr_t addr, size_t len, int flags); +int __syscall_getsid(int pid); +int __syscall_fdatasync(int fd); +int __syscall_mlock(intptr_t addr, size_t len); +int __syscall_munlock(intptr_t addr, size_t len); +int __syscall_mlockall(int flags); +int __syscall_munlockall(void); +int __syscall_mremap(intptr_t old_addr, size_t old_size, size_t new_size, int flags, intptr_t new_addr); +int __syscall_poll(intptr_t fds, int nfds, int timeout); +int __syscall_getcwd(intptr_t buf, size_t size); +intptr_t __syscall_mmap2(intptr_t addr, size_t len, int prot, int flags, int fd, off_t offset); +int __syscall_truncate64(intptr_t path, off_t length); +int __syscall_ftruncate64(int fd, off_t length); +int __syscall_stat64(intptr_t path, intptr_t buf); +int __syscall_lstat64(intptr_t path, intptr_t buf); +int __syscall_fstat64(int fd, intptr_t buf); +int __syscall_getuid32(void); +int __syscall_getgid32(void); +int __syscall_geteuid32(void); +int __syscall_getegid32(void); +int __syscall_setreuid32(int ruid, int euid); +int __syscall_setregid32(int rgid, int egid); +int __syscall_getgroups32(int size, intptr_t list); +int __syscall_fchown32(int fd, int owner, int group); +int __syscall_setresuid32(int ruid, int euid, int suid); +int __syscall_getresuid32(intptr_t ruid, intptr_t euid, intptr_t suid); +int __syscall_setresgid32(int rgid, int egid, int sgid); +int __syscall_getresgid32(intptr_t rgid, intptr_t egid, intptr_t sgid); +int __syscall_setuid32(int uid); +int __syscall_setgid32(int uid); +int __syscall_mincore(intptr_t addr, size_t length, intptr_t vec); +int __syscall_madvise(intptr_t addr, size_t length, int advice); +int __syscall_getdents64(int fd, intptr_t dirp, size_t count); +int __syscall_fcntl64(int fd, int cmd, ...); +int __syscall_statfs64(intptr_t path, size_t size, intptr_t buf); +int __syscall_fstatfs64(int fd, size_t size, intptr_t buf); +int __syscall_fadvise64(int fd, off_t offset, off_t length, int advice); +int __syscall_openat(int dirfd, intptr_t path, int flags, ...); // mode is optional +int __syscall_mkdirat(int dirfd, intptr_t path, int mode); +int __syscall_mknodat(int dirfd, intptr_t path, int mode, int dev); +int __syscall_fchownat(int dirfd, intptr_t path, int owner, int group, int flags); +int __syscall_newfstatat(int dirfd, intptr_t path, intptr_t buf, int flags); +int __syscall_unlinkat(int dirfd, intptr_t path, int flags); +int __syscall_renameat(int olddirfd, intptr_t oldpath, int newdirfd, intptr_t newpath); +int __syscall_linkat(int olddirfd, intptr_t oldpath, int newdirfd, intptr_t newpath, int flags); +int __syscall_symlinkat(intptr_t target, int newdirfd, intptr_t linkpath); +int __syscall_readlinkat(int dirfd, intptr_t path, intptr_t buf, size_t bufsize); +int __syscall_fchmodat2(int dirfd, intptr_t path, int mode, int flags); +int __syscall_faccessat(int dirfd, intptr_t path, int amode, int flags); +int __syscall_utimensat(int dirfd, intptr_t path, intptr_t times, int flags); +int __syscall_fallocate(int fd, int mode, off_t offset, off_t len); +int __syscall_dup3(int fd, int suggestfd, int flags); +int __syscall_pipe2(intptr_t fds, int flags); +int __syscall_recvmmsg(int sockfd, intptr_t msgvec, size_t vlen, int flags, ...); +int __syscall_prlimit64(int pid, int resource, intptr_t new_limit, intptr_t old_limit); +int __syscall_sendmmsg(int sockfd, intptr_t msgvec, size_t vlen, int flags, ...); +int __syscall_socket(int domain, int type, int protocol, int dummy1, int dummy2, int dummy3); +int __syscall_socketpair(int domain, int type, int protocol, intptr_t fds, int dummy, int dummy2); +int __syscall_bind(int sockfd, intptr_t addr, size_t alen, int dummy, int dummy2, int dummy3); +int __syscall_connect(int sockfd, intptr_t addr, size_t len, int dummy, int dummy2, int dummy3); +int __syscall_listen(int sockfd, int backlog, int dummy1, int dummy2, int dummy3, int dummy4); +int __syscall_accept4(int sockfd, intptr_t addr, intptr_t addrlen, int flags, int dummy1, int dummy2); +int __syscall_getsockopt(int sockfd, int level, int optname, intptr_t optval, intptr_t optlen, int dummy); +int __syscall_setsockopt(int sockfd, int level, int optname, intptr_t optval, size_t optlen, int dummy); +int __syscall_getsockname(int sockfd, intptr_t addr, intptr_t len, int dummy, int dummy2, int dummy3); +int __syscall_getpeername(int sockfd, intptr_t addr, intptr_t len, int dummy, int dummy2, int dummy3); +int __syscall_sendto(int sockfd, intptr_t msg, size_t len, int flags, intptr_t addr, size_t alen); +int __syscall_sendmsg(int sockfd, intptr_t msg , int flags, intptr_t addr, size_t alen, int dummy); +int __syscall_recvfrom(int sockfd, intptr_t msg, size_t len, int flags, intptr_t addr, intptr_t alen); +int __syscall_recvmsg(int sockfd, intptr_t msg, int flags, int dummy, int dummy2, int dummy3); +int __syscall_shutdown(int sockfd, int how, int dummy, int dummy2, int dummy3, int dummy4); + +#ifdef __cplusplus +} +#endif diff --git a/system/lib/libc/emscripten_syscall_stubs.c b/system/lib/libc/emscripten_syscall_stubs.c index 9ba7e411733e1..8aed824b8c572 100644 --- a/system/lib/libc/emscripten_syscall_stubs.c +++ b/system/lib/libc/emscripten_syscall_stubs.c @@ -15,12 +15,12 @@ #include #include #include -#include #include #include #include #include #include +#include #include #include #include diff --git a/system/lib/libc/musl/arch/emscripten/syscall_arch.h b/system/lib/libc/musl/arch/emscripten/syscall_arch.h index e2dd384acd78b..cf9ad1048dc64 100644 --- a/system/lib/libc/musl/arch/emscripten/syscall_arch.h +++ b/system/lib/libc/musl/arch/emscripten/syscall_arch.h @@ -1,117 +1,8 @@ -#include #include #include +#include -// Compile as if we can pass uint64 values directly to the -// host. Binaryen will take care of splitting any i64 params -// into a pair of i32 values if needed. +// Compile as if we can pass uint64 values directly to the host. Binaryen will +// take care of splitting any i64 params into a pair of i32 values if needed. #define __SYSCALL_LL_E(x) (x) #define __SYSCALL_LL_O(x) (x) - -#ifdef __cplusplus -extern "C" { -#endif - -int __syscall_chdir(intptr_t path); -int __syscall_mknod(intptr_t path, int mode, int dev); -int __syscall_chmod(intptr_t path, int mode); -int __syscall_getpid(void); -int __syscall_pause(void); -int __syscall_access(intptr_t path, int amode); -int __syscall_sync(void); -int __syscall_rmdir(intptr_t path); -int __syscall_dup(int fd); -int __syscall_acct(intptr_t filename); -int __syscall_ioctl(int fd, int request, ...); -int __syscall_setpgid(int pid, int gpid); -int __syscall_umask(int mask); -int __syscall_getppid(void); -int __syscall_getpgrp(void); -int __syscall_setsid(void); -int __syscall_getrusage(int who, intptr_t usage); -int __syscall_munmap(intptr_t addr, size_t len); -int __syscall_fchmod(int fd, int mode); -int __syscall_getpriority(int which, int who); -int __syscall_setpriority(int which, int who, int prio); -int __syscall_socketcall(int call, intptr_t args); -int __syscall_wait4(int pid, intptr_t wstatus, int options, int rusage); -int __syscall_setdomainname(intptr_t name, size_t size); -int __syscall_uname(intptr_t buf); -int __syscall_mprotect(size_t addr, size_t len, int prot); -int __syscall_getpgid(int pid); -int __syscall_fchdir(int fd); -int __syscall_msync(intptr_t addr, size_t len, int flags); -int __syscall_getsid(int pid); -int __syscall_fdatasync(int fd); -int __syscall_mlock(intptr_t addr, size_t len); -int __syscall_munlock(intptr_t addr, size_t len); -int __syscall_mlockall(int flags); -int __syscall_munlockall(void); -int __syscall_mremap(intptr_t old_addr, size_t old_size, size_t new_size, int flags, intptr_t new_addr); -int __syscall_poll(intptr_t fds, int nfds, int timeout); -int __syscall_getcwd(intptr_t buf, size_t size); -intptr_t __syscall_mmap2(intptr_t addr, size_t len, int prot, int flags, int fd, off_t offset); -int __syscall_truncate64(intptr_t path, off_t length); -int __syscall_ftruncate64(int fd, off_t length); -int __syscall_stat64(intptr_t path, intptr_t buf); -int __syscall_lstat64(intptr_t path, intptr_t buf); -int __syscall_fstat64(int fd, intptr_t buf); -int __syscall_getuid32(void); -int __syscall_getgid32(void); -int __syscall_geteuid32(void); -int __syscall_getegid32(void); -int __syscall_setreuid32(int ruid, int euid); -int __syscall_setregid32(int rgid, int egid); -int __syscall_getgroups32(int size, intptr_t list); -int __syscall_fchown32(int fd, int owner, int group); -int __syscall_setresuid32(int ruid, int euid, int suid); -int __syscall_getresuid32(intptr_t ruid, intptr_t euid, intptr_t suid); -int __syscall_setresgid32(int rgid, int egid, int sgid); -int __syscall_getresgid32(intptr_t rgid, intptr_t egid, intptr_t sgid); -int __syscall_setuid32(int uid); -int __syscall_setgid32(int uid); -int __syscall_mincore(intptr_t addr, size_t length, intptr_t vec); -int __syscall_madvise(intptr_t addr, size_t length, int advice); -int __syscall_getdents64(int fd, intptr_t dirp, size_t count); -int __syscall_fcntl64(int fd, int cmd, ...); -int __syscall_statfs64(intptr_t path, size_t size, intptr_t buf); -int __syscall_fstatfs64(int fd, size_t size, intptr_t buf); -int __syscall_fadvise64(int fd, off_t offset, off_t length, int advice); -int __syscall_openat(int dirfd, intptr_t path, int flags, ...); // mode is optional -int __syscall_mkdirat(int dirfd, intptr_t path, int mode); -int __syscall_mknodat(int dirfd, intptr_t path, int mode, int dev); -int __syscall_fchownat(int dirfd, intptr_t path, int owner, int group, int flags); -int __syscall_newfstatat(int dirfd, intptr_t path, intptr_t buf, int flags); -int __syscall_unlinkat(int dirfd, intptr_t path, int flags); -int __syscall_renameat(int olddirfd, intptr_t oldpath, int newdirfd, intptr_t newpath); -int __syscall_linkat(int olddirfd, intptr_t oldpath, int newdirfd, intptr_t newpath, int flags); -int __syscall_symlinkat(intptr_t target, int newdirfd, intptr_t linkpath); -int __syscall_readlinkat(int dirfd, intptr_t path, intptr_t buf, size_t bufsize); -int __syscall_fchmodat2(int dirfd, intptr_t path, int mode, int flags); -int __syscall_faccessat(int dirfd, intptr_t path, int amode, int flags); -int __syscall_utimensat(int dirfd, intptr_t path, intptr_t times, int flags); -int __syscall_fallocate(int fd, int mode, off_t offset, off_t len); -int __syscall_dup3(int fd, int suggestfd, int flags); -int __syscall_pipe2(intptr_t fds, int flags); -int __syscall_recvmmsg(int sockfd, intptr_t msgvec, size_t vlen, int flags, ...); -int __syscall_prlimit64(int pid, int resource, intptr_t new_limit, intptr_t old_limit); -int __syscall_sendmmsg(int sockfd, intptr_t msgvec, size_t vlen, int flags, ...); -int __syscall_socket(int domain, int type, int protocol, int dummy1, int dummy2, int dummy3); -int __syscall_socketpair(int domain, int type, int protocol, intptr_t fds, int dummy, int dummy2); -int __syscall_bind(int sockfd, intptr_t addr, size_t alen, int dummy, int dummy2, int dummy3); -int __syscall_connect(int sockfd, intptr_t addr, size_t len, int dummy, int dummy2, int dummy3); -int __syscall_listen(int sockfd, int backlog, int dummy1, int dummy2, int dummy3, int dummy4); -int __syscall_accept4(int sockfd, intptr_t addr, intptr_t addrlen, int flags, int dummy1, int dummy2); -int __syscall_getsockopt(int sockfd, int level, int optname, intptr_t optval, intptr_t optlen, int dummy); -int __syscall_setsockopt(int sockfd, int level, int optname, intptr_t optval, size_t optlen, int dummy); -int __syscall_getsockname(int sockfd, intptr_t addr, intptr_t len, int dummy, int dummy2, int dummy3); -int __syscall_getpeername(int sockfd, intptr_t addr, intptr_t len, int dummy, int dummy2, int dummy3); -int __syscall_sendto(int sockfd, intptr_t msg, size_t len, int flags, intptr_t addr, size_t alen); -int __syscall_sendmsg(int sockfd, intptr_t msg , int flags, intptr_t addr, size_t alen, int dummy); -int __syscall_recvfrom(int sockfd, intptr_t msg, size_t len, int flags, intptr_t addr, intptr_t alen); -int __syscall_recvmsg(int sockfd, intptr_t msg, int flags, int dummy, int dummy2, int dummy3); -int __syscall_shutdown(int sockfd, int how, int dummy, int dummy2, int dummy3, int dummy4); - -#ifdef __cplusplus -} -#endif diff --git a/system/lib/wasmfs/js_api.cpp b/system/lib/wasmfs/js_api.cpp index 48a241768242e..5330b66b4074a 100644 --- a/system/lib/wasmfs/js_api.cpp +++ b/system/lib/wasmfs/js_api.cpp @@ -4,9 +4,9 @@ // found in the LICENSE file. #include -#include #include #include +#include #include "backend.h" #include "file.h" diff --git a/system/lib/wasmfs/syscalls.cpp b/system/lib/wasmfs/syscalls.cpp index 53edc1fa95a8e..0b6acf681b16b 100644 --- a/system/lib/wasmfs/syscalls.cpp +++ b/system/lib/wasmfs/syscalls.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -20,7 +21,6 @@ #include #include #include -#include #include #include #include diff --git a/tools/gen_struct_info.py b/tools/gen_struct_info.py index 3f99e1a555302..35af3a10eb31b 100755 --- a/tools/gen_struct_info.py +++ b/tools/gen_struct_info.py @@ -78,6 +78,8 @@ ] INTERNAL_CFLAGS = [ + '-I' + utils.path_from_root('system/lib/libc/musl/arch/emscripten'), + '-I' + utils.path_from_root('system/lib/libc/musl/arch/generic'), '-I' + utils.path_from_root('system/lib/libc/musl/src/internal'), '-I' + utils.path_from_root('system/lib/libc/musl/src/include'), '-I' + utils.path_from_root('system/lib/pthread/'), diff --git a/tools/system_libs.py b/tools/system_libs.py index 50a22ecc62a26..f2debcd9db20e 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -905,6 +905,8 @@ def get_default_variation(cls, **kwargs): class MuslInternalLibrary(Library): includes = [ + 'system/lib/libc/musl/arch/emscripten', + 'system/lib/libc/musl/arch/generic', 'system/lib/libc/musl/src/internal', 'system/lib/libc/musl/src/include', 'system/lib/libc/musl/include', @@ -918,6 +920,7 @@ class MuslInternalLibrary(Library): '-Wno-unused-result', # system call results are often ignored in musl, and in wasi that warns '-Wno-bitwise-op-parentheses', '-Wno-shift-op-parentheses', + '-Wno-constant-conversion', ] @@ -2500,10 +2503,10 @@ def install_system_headers(stamp): 'system/lib/compiler-rt/include': '', 'system/lib/libunwind/include': '', # Copy the generic arch files first then - 'system/lib/libc/musl/arch/generic': '', + 'system/lib/libc/musl/arch/generic/bits': 'bits', # Then overlay the emscripten directory on top. # This mimics how musl itself installs its headers. - 'system/lib/libc/musl/arch/emscripten': '', + 'system/lib/libc/musl/arch/emscripten/bits': 'bits', 'system/lib/libc/musl/include': '', 'system/lib/libcxx/include': 'c++/v1', 'system/lib/libcxxabi/include': 'c++/v1', From 2cf3138c4be47f269f96ab549028cc3c1447d8d3 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Thu, 9 Apr 2026 17:53:18 -0700 Subject: [PATCH 36/42] Add more testing for pthread_kill. (#26663) - Test using `pthread_kill` on yourself. - Test using `pthread_kill` from a background thread to the main thread. For some reason this was disallowed before. - Add a stub version of `pthread_kill` along with a test. --- system/lib/libc/raise.c | 8 ++-- system/lib/pthread/library_pthread_stub.c | 8 ++++ system/lib/pthread/pthread_kill.c | 20 +++++----- test/codesize/test_codesize_cxx_ctors1.json | 8 ++-- test/codesize/test_codesize_cxx_ctors2.json | 8 ++-- test/codesize/test_codesize_cxx_except.json | 8 ++-- .../test_codesize_cxx_except_wasm.json | 8 ++-- .../test_codesize_cxx_except_wasm_legacy.json | 8 ++-- test/codesize/test_codesize_cxx_lto.json | 8 ++-- test/codesize/test_codesize_cxx_mangle.json | 4 +- test/codesize/test_codesize_cxx_noexcept.json | 8 ++-- test/codesize/test_codesize_cxx_wasmfs.json | 8 ++-- test/codesize/test_codesize_file_preload.json | 4 +- test/codesize/test_codesize_hello_O0.json | 6 +-- test/codesize/test_codesize_hello_O1.json | 4 +- test/codesize/test_codesize_hello_O2.json | 4 +- test/codesize/test_codesize_hello_O3.json | 4 +- test/codesize/test_codesize_hello_Os.json | 4 +- test/codesize/test_codesize_hello_Oz.json | 4 +- test/codesize/test_codesize_hello_dylink.json | 8 ++-- .../test_codesize_hello_dylink_all.json | 6 ++- .../test_codesize_hello_single_file.json | 2 +- test/codesize/test_codesize_hello_wasmfs.json | 4 +- ...inimal_runtime_code_size_hello_embind.json | 4 +- test/codesize/test_unoptimized_code_size.json | 8 ++-- test/pthread/test_pthread_kill.c | 40 ++++++++++++++----- test/pthread/test_pthread_kill.out | 7 +++- test/pthread/test_pthread_kill_self.c | 11 +++++ test/test_other.py | 7 ++++ 29 files changed, 142 insertions(+), 89 deletions(-) create mode 100644 test/pthread/test_pthread_kill_self.c diff --git a/system/lib/libc/raise.c b/system/lib/libc/raise.c index 5e56d5cb44221..b653bf72c09d3 100644 --- a/system/lib/libc/raise.c +++ b/system/lib/libc/raise.c @@ -71,10 +71,10 @@ int raise(int sig) { handler(sig); } } else if (handler != SIG_IGN) { - // Avoid a direct call to the handler, and instead call via JS so we can - // avoid strict signature checking. - // https://github.com/emscripten-core/posixtestsuite/issues/6 - __call_sighandler(handler, sig); + // Avoid a direct call to the handler, and instead call via JS so we can + // avoid strict signature checking. + // https://github.com/emscripten-core/posixtestsuite/issues/6 + __call_sighandler(handler, sig); } } return 0; diff --git a/system/lib/pthread/library_pthread_stub.c b/system/lib/pthread/library_pthread_stub.c index 4c17e3c50bdc0..e4e0416915337 100644 --- a/system/lib/pthread/library_pthread_stub.c +++ b/system/lib/pthread/library_pthread_stub.c @@ -260,6 +260,14 @@ int pthread_equal(pthread_t t1, pthread_t t2) { return t1 == t2; } +int pthread_kill(pthread_t thread, int sig) { + if (thread != pthread_self()) { + return EINVAL; + } + raise(sig); + return 0; +} + int pthread_mutexattr_init(pthread_mutexattr_t *attr) { return 0; } diff --git a/system/lib/pthread/pthread_kill.c b/system/lib/pthread/pthread_kill.c index 86e486cea9ae4..dcaaa002d6f89 100644 --- a/system/lib/pthread/pthread_kill.c +++ b/system/lib/pthread/pthread_kill.c @@ -11,10 +11,8 @@ #include #include "pthread_impl.h" -#include "lock.h" -void do_raise(void* arg) { - int sig = (intptr_t)arg; +static void do_raise(int sig) { if (sig == SIGCANCEL) { // For `SIGCANCEL` there is no need to actually call raise to run the // handler function. The calling thread (the one calling `pthread_cancel`) @@ -31,17 +29,17 @@ void do_raise(void* arg) { _emscripten_runtime_keepalive_clear(); return; } - raise((intptr_t)sig); + raise(sig); +} + +static void proxied_do_raise(void* arg) { + do_raise((intptr_t)arg); } int pthread_kill(pthread_t t, int sig) { if (sig < 0 || sig >= _NSIG) { return EINVAL; } - if (t == emscripten_main_runtime_thread_id()) { - if (sig == 0) return 0; // signal == 0 is a no-op. - return ESRCH; - } if (!t || !_emscripten_thread_is_valid(t)) { return ESRCH; } @@ -49,6 +47,10 @@ int pthread_kill(pthread_t t, int sig) { // The job of pthread_kill is basically to run the (process-wide) signal // handler on the target thread. - emscripten_proxy_async(emscripten_proxy_get_system_queue(), t, do_raise, (void*)(intptr_t)sig); + if (pthread_equal(pthread_self(), t)) { + do_raise(sig); + } else { + emscripten_proxy_async(emscripten_proxy_get_system_queue(), t, proxied_do_raise, (void*)(intptr_t)sig); + } return 0; } diff --git a/test/codesize/test_codesize_cxx_ctors1.json b/test/codesize/test_codesize_cxx_ctors1.json index dff56a4063fb0..0b8bea69b50e7 100644 --- a/test/codesize/test_codesize_cxx_ctors1.json +++ b/test/codesize/test_codesize_cxx_ctors1.json @@ -1,10 +1,10 @@ { "a.out.js": 19194, "a.out.js.gz": 7969, - "a.out.nodebug.wasm": 132638, - "a.out.nodebug.wasm.gz": 49927, - "total": 151832, - "total_gz": 57896, + "a.out.nodebug.wasm": 132635, + "a.out.nodebug.wasm.gz": 49922, + "total": 151829, + "total_gz": 57891, "sent": [ "__cxa_throw", "_abort_js", diff --git a/test/codesize/test_codesize_cxx_ctors2.json b/test/codesize/test_codesize_cxx_ctors2.json index fb2e77ec4ae92..4603f919e7c55 100644 --- a/test/codesize/test_codesize_cxx_ctors2.json +++ b/test/codesize/test_codesize_cxx_ctors2.json @@ -1,10 +1,10 @@ { "a.out.js": 19171, "a.out.js.gz": 7957, - "a.out.nodebug.wasm": 132064, - "a.out.nodebug.wasm.gz": 49586, - "total": 151235, - "total_gz": 57543, + "a.out.nodebug.wasm": 132061, + "a.out.nodebug.wasm.gz": 49579, + "total": 151232, + "total_gz": 57536, "sent": [ "__cxa_throw", "_abort_js", diff --git a/test/codesize/test_codesize_cxx_except.json b/test/codesize/test_codesize_cxx_except.json index c29dbd2b26b46..d70ea38a08ea4 100644 --- a/test/codesize/test_codesize_cxx_except.json +++ b/test/codesize/test_codesize_cxx_except.json @@ -1,10 +1,10 @@ { "a.out.js": 23174, "a.out.js.gz": 8960, - "a.out.nodebug.wasm": 172516, - "a.out.nodebug.wasm.gz": 57438, - "total": 195690, - "total_gz": 66398, + "a.out.nodebug.wasm": 172526, + "a.out.nodebug.wasm.gz": 57447, + "total": 195700, + "total_gz": 66407, "sent": [ "__cxa_begin_catch", "__cxa_end_catch", diff --git a/test/codesize/test_codesize_cxx_except_wasm.json b/test/codesize/test_codesize_cxx_except_wasm.json index cb737cf73678b..66e3ff01b1367 100644 --- a/test/codesize/test_codesize_cxx_except_wasm.json +++ b/test/codesize/test_codesize_cxx_except_wasm.json @@ -1,10 +1,10 @@ { "a.out.js": 19026, "a.out.js.gz": 7904, - "a.out.nodebug.wasm": 147922, - "a.out.nodebug.wasm.gz": 55312, - "total": 166948, - "total_gz": 63216, + "a.out.nodebug.wasm": 147926, + "a.out.nodebug.wasm.gz": 55308, + "total": 166952, + "total_gz": 63212, "sent": [ "_abort_js", "_tzset_js", diff --git a/test/codesize/test_codesize_cxx_except_wasm_legacy.json b/test/codesize/test_codesize_cxx_except_wasm_legacy.json index 71fbc6b06eb6d..bbc6a807b6b7c 100644 --- a/test/codesize/test_codesize_cxx_except_wasm_legacy.json +++ b/test/codesize/test_codesize_cxx_except_wasm_legacy.json @@ -1,10 +1,10 @@ { "a.out.js": 19100, "a.out.js.gz": 7929, - "a.out.nodebug.wasm": 145729, - "a.out.nodebug.wasm.gz": 54945, - "total": 164829, - "total_gz": 62874, + "a.out.nodebug.wasm": 145732, + "a.out.nodebug.wasm.gz": 54936, + "total": 164832, + "total_gz": 62865, "sent": [ "_abort_js", "_tzset_js", diff --git a/test/codesize/test_codesize_cxx_lto.json b/test/codesize/test_codesize_cxx_lto.json index 979613f5c8d1f..6c07b8cbdbf3f 100644 --- a/test/codesize/test_codesize_cxx_lto.json +++ b/test/codesize/test_codesize_cxx_lto.json @@ -1,10 +1,10 @@ { "a.out.js": 18563, "a.out.js.gz": 7666, - "a.out.nodebug.wasm": 102162, - "a.out.nodebug.wasm.gz": 39560, - "total": 120725, - "total_gz": 47226, + "a.out.nodebug.wasm": 102164, + "a.out.nodebug.wasm.gz": 39553, + "total": 120727, + "total_gz": 47219, "sent": [ "a (emscripten_resize_heap)", "b (_setitimer_js)", diff --git a/test/codesize/test_codesize_cxx_mangle.json b/test/codesize/test_codesize_cxx_mangle.json index cd1eeff3bb99d..b12d268816995 100644 --- a/test/codesize/test_codesize_cxx_mangle.json +++ b/test/codesize/test_codesize_cxx_mangle.json @@ -2,9 +2,9 @@ "a.out.js": 23224, "a.out.js.gz": 8983, "a.out.nodebug.wasm": 238957, - "a.out.nodebug.wasm.gz": 79842, + "a.out.nodebug.wasm.gz": 79820, "total": 262181, - "total_gz": 88825, + "total_gz": 88803, "sent": [ "__cxa_begin_catch", "__cxa_end_catch", diff --git a/test/codesize/test_codesize_cxx_noexcept.json b/test/codesize/test_codesize_cxx_noexcept.json index 4881c4a93cac7..59ac98658db69 100644 --- a/test/codesize/test_codesize_cxx_noexcept.json +++ b/test/codesize/test_codesize_cxx_noexcept.json @@ -1,10 +1,10 @@ { "a.out.js": 19194, "a.out.js.gz": 7969, - "a.out.nodebug.wasm": 134661, - "a.out.nodebug.wasm.gz": 50777, - "total": 153855, - "total_gz": 58746, + "a.out.nodebug.wasm": 134657, + "a.out.nodebug.wasm.gz": 50774, + "total": 153851, + "total_gz": 58743, "sent": [ "__cxa_throw", "_abort_js", diff --git a/test/codesize/test_codesize_cxx_wasmfs.json b/test/codesize/test_codesize_cxx_wasmfs.json index 9d83f3e7f89b1..577ddcf1fd9b6 100644 --- a/test/codesize/test_codesize_cxx_wasmfs.json +++ b/test/codesize/test_codesize_cxx_wasmfs.json @@ -1,10 +1,10 @@ { "a.out.js": 7023, "a.out.js.gz": 3310, - "a.out.nodebug.wasm": 172714, - "a.out.nodebug.wasm.gz": 63316, - "total": 179737, - "total_gz": 66626, + "a.out.nodebug.wasm": 172710, + "a.out.nodebug.wasm.gz": 63317, + "total": 179733, + "total_gz": 66627, "sent": [ "__cxa_throw", "_abort_js", diff --git a/test/codesize/test_codesize_file_preload.json b/test/codesize/test_codesize_file_preload.json index a84ee7c65dad3..3cf2d2dc5c016 100644 --- a/test/codesize/test_codesize_file_preload.json +++ b/test/codesize/test_codesize_file_preload.json @@ -2,9 +2,9 @@ "a.out.js": 22141, "a.out.js.gz": 9184, "a.out.nodebug.wasm": 1648, - "a.out.nodebug.wasm.gz": 939, + "a.out.nodebug.wasm.gz": 938, "total": 23789, - "total_gz": 10123, + "total_gz": 10122, "sent": [ "a (fd_write)" ], diff --git a/test/codesize/test_codesize_hello_O0.json b/test/codesize/test_codesize_hello_O0.json index b0a1a0020f2a1..618458a4dcf0d 100644 --- a/test/codesize/test_codesize_hello_O0.json +++ b/test/codesize/test_codesize_hello_O0.json @@ -1,10 +1,10 @@ { "a.out.js": 24221, - "a.out.js.gz": 8713, + "a.out.js.gz": 8717, "a.out.nodebug.wasm": 14850, - "a.out.nodebug.wasm.gz": 7311, + "a.out.nodebug.wasm.gz": 7314, "total": 39071, - "total_gz": 16024, + "total_gz": 16031, "sent": [ "fd_write" ], diff --git a/test/codesize/test_codesize_hello_O1.json b/test/codesize/test_codesize_hello_O1.json index b078c77c56f44..1cee141c63fce 100644 --- a/test/codesize/test_codesize_hello_O1.json +++ b/test/codesize/test_codesize_hello_O1.json @@ -2,9 +2,9 @@ "a.out.js": 6345, "a.out.js.gz": 2456, "a.out.nodebug.wasm": 2530, - "a.out.nodebug.wasm.gz": 1423, + "a.out.nodebug.wasm.gz": 1421, "total": 8875, - "total_gz": 3879, + "total_gz": 3877, "sent": [ "fd_write" ], diff --git a/test/codesize/test_codesize_hello_O2.json b/test/codesize/test_codesize_hello_O2.json index f3986cb629424..383ae20cdc135 100644 --- a/test/codesize/test_codesize_hello_O2.json +++ b/test/codesize/test_codesize_hello_O2.json @@ -2,9 +2,9 @@ "a.out.js": 4323, "a.out.js.gz": 2130, "a.out.nodebug.wasm": 1898, - "a.out.nodebug.wasm.gz": 1122, + "a.out.nodebug.wasm.gz": 1120, "total": 6221, - "total_gz": 3252, + "total_gz": 3250, "sent": [ "fd_write" ], diff --git a/test/codesize/test_codesize_hello_O3.json b/test/codesize/test_codesize_hello_O3.json index 4c25347b54b72..c6cc2db3c5d91 100644 --- a/test/codesize/test_codesize_hello_O3.json +++ b/test/codesize/test_codesize_hello_O3.json @@ -2,9 +2,9 @@ "a.out.js": 4265, "a.out.js.gz": 2089, "a.out.nodebug.wasm": 1648, - "a.out.nodebug.wasm.gz": 939, + "a.out.nodebug.wasm.gz": 938, "total": 5913, - "total_gz": 3028, + "total_gz": 3027, "sent": [ "a (fd_write)" ], diff --git a/test/codesize/test_codesize_hello_Os.json b/test/codesize/test_codesize_hello_Os.json index 2e784fcceb96b..433d6af14da02 100644 --- a/test/codesize/test_codesize_hello_Os.json +++ b/test/codesize/test_codesize_hello_Os.json @@ -2,9 +2,9 @@ "a.out.js": 4265, "a.out.js.gz": 2089, "a.out.nodebug.wasm": 1638, - "a.out.nodebug.wasm.gz": 942, + "a.out.nodebug.wasm.gz": 941, "total": 5903, - "total_gz": 3031, + "total_gz": 3030, "sent": [ "a (fd_write)" ], diff --git a/test/codesize/test_codesize_hello_Oz.json b/test/codesize/test_codesize_hello_Oz.json index 6b94087d7d37d..a25237d4dd55b 100644 --- a/test/codesize/test_codesize_hello_Oz.json +++ b/test/codesize/test_codesize_hello_Oz.json @@ -2,9 +2,9 @@ "a.out.js": 3900, "a.out.js.gz": 1896, "a.out.nodebug.wasm": 1172, - "a.out.nodebug.wasm.gz": 724, + "a.out.nodebug.wasm.gz": 723, "total": 5072, - "total_gz": 2620, + "total_gz": 2619, "sent": [ "a (fd_write)" ], diff --git a/test/codesize/test_codesize_hello_dylink.json b/test/codesize/test_codesize_hello_dylink.json index efb0f7d1c501f..9c3b1473570d7 100644 --- a/test/codesize/test_codesize_hello_dylink.json +++ b/test/codesize/test_codesize_hello_dylink.json @@ -1,10 +1,10 @@ { "a.out.js": 26185, "a.out.js.gz": 11171, - "a.out.nodebug.wasm": 17668, - "a.out.nodebug.wasm.gz": 8921, - "total": 43853, - "total_gz": 20092, + "a.out.nodebug.wasm": 17671, + "a.out.nodebug.wasm.gz": 8923, + "total": 43856, + "total_gz": 20094, "sent": [ "__syscall_stat64", "emscripten_resize_heap", diff --git a/test/codesize/test_codesize_hello_dylink_all.json b/test/codesize/test_codesize_hello_dylink_all.json index 0c55f1f87dc11..105e4113624d7 100644 --- a/test/codesize/test_codesize_hello_dylink_all.json +++ b/test/codesize/test_codesize_hello_dylink_all.json @@ -1,7 +1,7 @@ { "a.out.js": 244343, - "a.out.nodebug.wasm": 577432, - "total": 821775, + "a.out.nodebug.wasm": 577473, + "total": 821816, "sent": [ "IMG_Init", "IMG_Load", @@ -2879,6 +2879,7 @@ "pthread_join", "pthread_key_create", "pthread_key_delete", + "pthread_kill", "pthread_mutex_consistent", "pthread_mutex_destroy", "pthread_mutex_init", @@ -4621,6 +4622,7 @@ "$pthread_getattr_np", "$pthread_getcpuclockid", "$pthread_getspecific", + "$pthread_kill", "$pthread_mutexattr_getprotocol", "$pthread_mutexattr_getpshared", "$pthread_mutexattr_getrobust", diff --git a/test/codesize/test_codesize_hello_single_file.json b/test/codesize/test_codesize_hello_single_file.json index 221cb519bb48d..efdca993df465 100644 --- a/test/codesize/test_codesize_hello_single_file.json +++ b/test/codesize/test_codesize_hello_single_file.json @@ -1,6 +1,6 @@ { "a.out.js": 5223, - "a.out.js.gz": 2886, + "a.out.js.gz": 2887, "sent": [ "a (fd_write)" ] diff --git a/test/codesize/test_codesize_hello_wasmfs.json b/test/codesize/test_codesize_hello_wasmfs.json index 4c25347b54b72..c6cc2db3c5d91 100644 --- a/test/codesize/test_codesize_hello_wasmfs.json +++ b/test/codesize/test_codesize_hello_wasmfs.json @@ -2,9 +2,9 @@ "a.out.js": 4265, "a.out.js.gz": 2089, "a.out.nodebug.wasm": 1648, - "a.out.nodebug.wasm.gz": 939, + "a.out.nodebug.wasm.gz": 938, "total": 5913, - "total_gz": 3028, + "total_gz": 3027, "sent": [ "a (fd_write)" ], diff --git a/test/codesize/test_minimal_runtime_code_size_hello_embind.json b/test/codesize/test_minimal_runtime_code_size_hello_embind.json index 6f8026cfbadb0..ad16c7f5b35e4 100644 --- a/test/codesize/test_minimal_runtime_code_size_hello_embind.json +++ b/test/codesize/test_minimal_runtime_code_size_hello_embind.json @@ -4,7 +4,7 @@ "a.js": 7262, "a.js.gz": 3324, "a.wasm": 7099, - "a.wasm.gz": 3257, + "a.wasm.gz": 3246, "total": 14909, - "total_gz": 6952 + "total_gz": 6941 } diff --git a/test/codesize/test_unoptimized_code_size.json b/test/codesize/test_unoptimized_code_size.json index f9e256d7fbca2..be274ef0a737c 100644 --- a/test/codesize/test_unoptimized_code_size.json +++ b/test/codesize/test_unoptimized_code_size.json @@ -1,8 +1,8 @@ { "hello_world.js": 56998, - "hello_world.js.gz": 17743, + "hello_world.js.gz": 17746, "hello_world.wasm": 14850, - "hello_world.wasm.gz": 7311, + "hello_world.wasm.gz": 7314, "no_asserts.js": 26622, "no_asserts.js.gz": 8888, "no_asserts.wasm": 12010, @@ -10,7 +10,7 @@ "strict.js": 54816, "strict.js.gz": 17052, "strict.wasm": 14850, - "strict.wasm.gz": 7311, + "strict.wasm.gz": 7310, "total": 180146, - "total_gz": 64185 + "total_gz": 64190 } diff --git a/test/pthread/test_pthread_kill.c b/test/pthread/test_pthread_kill.c index 2bc8095b05c41..cde94049f8994 100644 --- a/test/pthread/test_pthread_kill.c +++ b/test/pthread/test_pthread_kill.c @@ -17,14 +17,18 @@ pthread_cond_t started_cond = PTHREAD_COND_INITIALIZER; pthread_mutex_t started_lock = PTHREAD_MUTEX_INITIALIZER; -_Atomic bool got_term_signal = false; +_Atomic bool got_sigterm = false; +_Atomic bool got_sigusr1 = false; -pthread_t thr; +pthread_t main_thread; +pthread_t child_thread; void signal_handler(int sig, siginfo_t * info, void * arg) { - printf("signal: %d onthread=%d\n", sig, pthread_self() == thr); + printf("signal: %d onthread=%d\n", sig, pthread_self() == child_thread); if (sig == SIGTERM) { - got_term_signal = true; + got_sigterm = true; + } else if (sig == SIGUSR1) { + got_sigusr1 = true; } } @@ -34,6 +38,7 @@ void setup_handler() { act.sa_flags = SA_SIGINFO; act.sa_sigaction = signal_handler; sigaction(SIGTERM, &act, NULL); + sigaction(SIGUSR1, &act, NULL); } @@ -46,17 +51,26 @@ void *thread_start(void *arg) { pthread_cond_signal(&started_cond); pthread_mutex_unlock(&started_lock); // As long as this thread is running, keep the shared variable latched to nonzero value. - while (!got_term_signal) { + while (!got_sigterm) { sleepms(1); } - printf("got term signal, shutting down thread\n"); - pthread_exit(0); + printf("got term signal, sending signal back to main thread\n"); + pthread_kill(main_thread, SIGUSR1); + return NULL; } int main() { + main_thread = pthread_self(); setup_handler(); - int s = pthread_create(&thr, NULL, thread_start, 0); + printf("tesing pthread_kill with pthread_self\n"); + assert(!got_sigterm); + int s = pthread_kill(pthread_self(), SIGTERM); + assert(got_sigterm); + got_sigterm = false; + assert(s == 0); + + s = pthread_create(&child_thread, NULL, thread_start, 0); assert(s == 0); // Wait until thread kicks in and sets the shared variable. @@ -65,11 +79,15 @@ int main() { pthread_mutex_unlock(&started_lock); printf("thread has started, sending SIGTERM\n"); - s = pthread_kill(thr, SIGTERM); + s = pthread_kill(child_thread, SIGTERM); assert(s == 0); printf("SIGTERM sent\n"); - - pthread_join(thr, NULL); + pthread_join(child_thread, NULL); + printf("joined child_thread\n"); + while (!got_sigusr1) { + sleepms(1); + } + printf("got SIGUSR1. all done.\n"); return 0; } diff --git a/test/pthread/test_pthread_kill.out b/test/pthread/test_pthread_kill.out index 63ee2d876273c..9c0d30aee8994 100644 --- a/test/pthread/test_pthread_kill.out +++ b/test/pthread/test_pthread_kill.out @@ -1,4 +1,9 @@ +tesing pthread_kill with pthread_self +signal: 15 onthread=0 thread has started, sending SIGTERM SIGTERM sent signal: 15 onthread=1 -got term signal, shutting down thread +got term signal, sending signal back to main thread +joined child_thread +signal: 10 onthread=0 +got SIGUSR1. all done. diff --git a/test/pthread/test_pthread_kill_self.c b/test/pthread/test_pthread_kill_self.c new file mode 100644 index 0000000000000..5528ee4cd5f74 --- /dev/null +++ b/test/pthread/test_pthread_kill_self.c @@ -0,0 +1,11 @@ +#include +#include +#include +#include +#include + +int main() { + printf("main\n"); + pthread_kill(pthread_self(), SIGTERM); + assert(false && "should not get here"); +} diff --git a/test/test_other.py b/test/test_other.py index e948678f1b4fe..de22e2d3594a9 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -13038,6 +13038,13 @@ def test_pthread_trap(self): def test_pthread_kill(self): self.do_run_in_out_file_test('pthread/test_pthread_kill.c') + @parameterized({ + '': (['-pthread'],), + 'stub': ([],), + }) + def test_pthread_kill_self(self, args): + self.do_runf('pthread/test_pthread_kill_self.c', 'main\n', assert_returncode=NON_ZERO, cflags=args) + # Tests memory growth in pthreads mode, but still on the main thread. @requires_pthreads @parameterized({ From 4c4c5d401727f5c331f103b2d0fcb1ecaa8899b9 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Fri, 10 Apr 2026 12:42:25 -0700 Subject: [PATCH 37/42] Emsymbolizer: Remove VMA adjustment from llvm-symbolizer call (#26665) After https://github.com/llvm/llvm-project/pull/191068 it is no longer necessary (or correct). --- test/test_other.py | 2 ++ tools/emsymbolizer.py | 15 +++++---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/test/test_other.py b/test/test_other.py index de22e2d3594a9..780c164773f3a 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -9684,6 +9684,8 @@ def get_instr_addr(self, text, filename): def test_emsymbolizer_srcloc(self): 'Test emsymbolizer use cases that provide src location granularity info' + self.skipTest('TODO: Re-enable when https://github.com/llvm/llvm-project/pull/191329 rolls') + def check_dwarf_loc_info(address, funcs, locs): out = self.run_process( [emsymbolizer, '-s', 'dwarf', 'test_dwarf.wasm', address], diff --git a/tools/emsymbolizer.py b/tools/emsymbolizer.py index 5ba7d641f7025..1d505614b4d8f 100755 --- a/tools/emsymbolizer.py +++ b/tools/emsymbolizer.py @@ -67,13 +67,8 @@ def has_linking_section(module): return module.get_custom_section('linking') is not None -def symbolize_address_symbolizer(module, address, is_dwarf): - if is_dwarf: - vma_adjust = get_codesec_offset(module) - else: - vma_adjust = 0 - cmd = [LLVM_SYMBOLIZER, '-e', module.filename, f'--adjust-vma={vma_adjust}', - str(address)] +def symbolize_address_symbolizer(module, address): + cmd = [LLVM_SYMBOLIZER, '-e', module.filename, str(address)] if shared.DEBUG: print(f'Running {" ".join(cmd)}') out = utils.run_process(cmd, stdout=subprocess.PIPE).stdout.strip() @@ -280,16 +275,16 @@ def print_loc(loc): if ((has_debug_line_section(module) and not args.source) or 'dwarf' in args.source): - print_loc(symbolize_address_symbolizer(module, address, is_dwarf=True)) + print_loc(symbolize_address_symbolizer(module, address)) elif ((get_sourceMappingURL_section(module) and not args.source) or 'sourcemap' in args.source): print_loc(symbolize_address_sourcemap(module, address, args.file)) elif ((has_name_section(module) and not args.source) or 'names' in args.source): - print_loc(symbolize_address_symbolizer(module, address, is_dwarf=False)) + print_loc(symbolize_address_symbolizer(module, address)) elif ((has_linking_section(module) and not args.source) or 'symtab' in args.source): - print_loc(symbolize_address_symbolizer(module, address, is_dwarf=False)) + print_loc(symbolize_address_symbolizer(module, address)) elif (args.source == 'symbolmap'): print_loc(symbolize_address_symbolmap(module, address, args.file)) else: From 670b4c7d653e3493d1d6c6ea3c8110c34050ca1c Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Fri, 10 Apr 2026 17:21:23 -0700 Subject: [PATCH 38/42] [test] Add EMTEST_TIMEOUT environment variable (#26669) This is useful when debugging tests that are timing out without having to wait the full default of 5 minutes to get the failure. For example I've been debugging a timeout recently using: ``` EMTEST_TIMEOUT=5 ./test/runner posixtest.test_pthread_join_4_1 ``` This waits for 5 seconds rather than 5 minutes. --- test/jsrun.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jsrun.py b/test/jsrun.py index 9cffe16da6df9..88015b40088d2 100644 --- a/test/jsrun.py +++ b/test/jsrun.py @@ -15,7 +15,7 @@ from tools import utils WORKING_ENGINES = {} # Holds all configured engines and whether they work: maps path -> True/False -DEFAULT_TIMEOUT = 5 * 60 +DEFAULT_TIMEOUT = int(os.environ.get('EMTEST_TIMEOUT', str(5 * 60))) def make_command(filename, engine, args=None): From b3632b87e7274c3d2b8549e8b20648d1fe9a32ab Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Fri, 10 Apr 2026 18:59:33 -0700 Subject: [PATCH 39/42] Remove special case from pthread_kill. (#26668) This special handling for `SIGCANCEL` should not be needed. If we need to do something like `_emscripten_runtime_keepalive_clear` during pthread cancelation it would be best done when the cancellation in processed in `__testcancel`/`__cancel`. The comment & code I'm removing here was added back in #22467 along with testing in the form of `test/pthread/test_pthread_kill.c` and the pthread_kill tests in posixtest. I recently expanded the testing in #26663, and this change doesn't break any of those tests. --- system/lib/pthread/pthread_kill.c | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/system/lib/pthread/pthread_kill.c b/system/lib/pthread/pthread_kill.c index dcaaa002d6f89..b1306fe5b9ea6 100644 --- a/system/lib/pthread/pthread_kill.c +++ b/system/lib/pthread/pthread_kill.c @@ -12,28 +12,8 @@ #include "pthread_impl.h" -static void do_raise(int sig) { - if (sig == SIGCANCEL) { - // For `SIGCANCEL` there is no need to actually call raise to run the - // handler function. The calling thread (the one calling `pthread_cancel`) - // will already have marked us as being cancelled. All we need to do is - // ensure that `pthread_testcancel` is eventually called and that will cause - // this thread to exit. We can't call `pthread_testcancel` here (since we - // are being called from the proxy queue process and we don't want to leave - // that in a bad state by unwinding). Instead, we rely on - // `pthread_testcancel` at the end of `_emscripten_check_mailbox`. Before - // we return, we do want to make sure we clear the keepalive state so that - // the thread will exit even if it has a reason to stay alive. TODO(sbc): - // Is this the correct behaviour, should `pthread_cancel` instead wait for - // threads to be done with outstanding work/event loops? - _emscripten_runtime_keepalive_clear(); - return; - } - raise(sig); -} - -static void proxied_do_raise(void* arg) { - do_raise((intptr_t)arg); +static void proxied_raise(void* arg) { + raise((intptr_t)arg); } int pthread_kill(pthread_t t, int sig) { @@ -48,9 +28,9 @@ int pthread_kill(pthread_t t, int sig) { // The job of pthread_kill is basically to run the (process-wide) signal // handler on the target thread. if (pthread_equal(pthread_self(), t)) { - do_raise(sig); + raise(sig); } else { - emscripten_proxy_async(emscripten_proxy_get_system_queue(), t, proxied_do_raise, (void*)(intptr_t)sig); + emscripten_proxy_async(emscripten_proxy_get_system_queue(), t, proxied_raise, (void*)(intptr_t)sig); } return 0; } From 1177b42cf2e5bea6a141030982392fc186b353c0 Mon Sep 17 00:00:00 2001 From: Subh aush singh Date: Mon, 13 Apr 2026 05:31:47 +0530 Subject: [PATCH 40/42] cmdline: Allow overriding -jsD directives (#26639) Fixes: #26576 --- test/jslib/test_jslib_custom_settings.js | 7 ++----- test/test_jslib.py | 11 ++++++++--- tools/cmdline.py | 7 ++++--- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/test/jslib/test_jslib_custom_settings.js b/test/jslib/test_jslib_custom_settings.js index 2423a0574fc2d..8c13d90f29faa 100644 --- a/test/jslib/test_jslib_custom_settings.js +++ b/test/jslib/test_jslib_custom_settings.js @@ -1,9 +1,6 @@ addToLibrary({ - js_function: function() { #if CUSTOM_JS_OPTION - return 1; -#else - return 0; + js_function: () => {{{ CUSTOM_JS_OPTION }}}, #endif - } }); + diff --git a/test/test_jslib.py b/test/test_jslib.py index 0c46710abc217..211a679386bd4 100644 --- a/test/test_jslib.py +++ b/test/test_jslib.py @@ -353,10 +353,15 @@ def test_jslib_legacy(self): # the -jsDfoo=val syntax: # See https://github.com/emscripten-core/emscripten/issues/10580. def test_jslib_custom_settings(self): - self.cflags += ['--js-library', test_file('jslib/test_jslib_custom_settings.js'), '-jsDCUSTOM_JS_OPTION=1'] - self.do_run_in_out_file_test('jslib/test_jslib_custom_settings.c') + test_file_path = test_file('jslib/test_jslib_custom_settings.c') + js_lib = test_file('jslib/test_jslib_custom_settings.js') - self.assert_fail([EMCC, '-jsDWASM=0'], 'cannot change built-in settings values with a -jsD directive') + self.do_runf(test_file_path, '1\n', cflags=['--js-library', js_lib, '-jsDCUSTOM_JS_OPTION=1']) + + # verify that the settings can be specified more than once, and that the last one wins. + self.do_runf(test_file_path, '2\n', cflags=['--js-library', js_lib, '-jsDCUSTOM_JS_OPTION=1', '-jsDCUSTOM_JS_OPTION=2']) + + self.assert_fail([EMCC, '-jsDWASM=1'], 'cannot change built-in settings values with a -jsD directive') def test_jslib_native_deps(self): # Verify that memset (which lives in compiled code), can be specified as a JS library diff --git a/tools/cmdline.py b/tools/cmdline.py index d708a09f5c5a7..6584ea02e25a2 100644 --- a/tools/cmdline.py +++ b/tools/cmdline.py @@ -216,6 +216,7 @@ def parse_args(newargs): # noqa: C901, PLR0912, PLR0915 """ should_exit = False skip = False + builtin_settings = set(settings.keys()) LEGACY_ARGS = {'--js-opts', '--llvm-opts', '--llvm-lto', '--memory-init-file'} LEGACY_FLAGS = {'--separate-asm', '--jcache', '--proxy-to-worker', '--default-obj-ext', '--embind-emit-tsd', '--remove-duplicates', '--no-heap-copy'} @@ -572,12 +573,12 @@ def consume_arg_file(): elif arg.startswith('-jsD'): key = arg.removeprefix('-jsD') if '=' in key: - key, value = key.split('=') + key, value = key.split('=', 1) else: value = '1' - if key in settings.keys(): + if key in builtin_settings: exit_with_error(f'{arg}: cannot change built-in settings values with a -jsD directive. Pass -s{key}={value} instead!') - # Apply user -jsD settings + # Allow overrides/duplicates for user-defined -jsD flags settings[key] = value newargs[i] = '' elif check_flag('-shared'): From 74209a704998e23f8f8933ee9ea31b57c5b2aa72 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Mon, 13 Apr 2026 08:00:58 -0700 Subject: [PATCH 41/42] Add testing for libtty.js & make fsync a no-op if absent Make ttyops.fsync optional, if not present `fsync()` is a no-op. Before this PR, there was no coverage for `TTY.register()` other than the features used in default_tty_ops`. The new tests creates and registers new TTY devices using TTY.register() and then excercises them via the native C file APIs. The TTY API is undocumented and slightly ill-considered but people are using it so it's good to have some test coverage. --- src/lib/libtty.js | 4 +- test/other/libtty.c | 209 ++++++++++++++++++++++++++++++++++++++++++ test/other/libtty.out | 54 +++++++++++ test/test_other.py | 3 + 4 files changed, 268 insertions(+), 2 deletions(-) create mode 100644 test/other/libtty.c create mode 100644 test/other/libtty.out diff --git a/src/lib/libtty.js b/src/lib/libtty.js index ba392b12c32ff..69addab8fab80 100644 --- a/src/lib/libtty.js +++ b/src/lib/libtty.js @@ -54,10 +54,10 @@ addToLibrary({ }, close(stream) { // flush any pending line data - stream.tty.ops.fsync(stream.tty); + stream.tty.ops.fsync?.(stream.tty); }, fsync(stream) { - stream.tty.ops.fsync(stream.tty); + stream.tty.ops.fsync?.(stream.tty); }, read(stream, buffer, offset, length, pos /* ignored */) { if (!stream.tty || !stream.tty.ops.get_char) { diff --git a/test/other/libtty.c b/test/other/libtty.c new file mode 100644 index 0000000000000..8146dbef65176 --- /dev/null +++ b/test/other/libtty.c @@ -0,0 +1,209 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EM_JS_DEPS(main, "$TTY"); + +// clang-format off +EM_JS(void, init, (void), { + var major = 100; + var tty_ops = { + get_char: function (tty) { + if (tty.input.length > 0) { + return tty.input.shift(); + } + return undefined; + }, + put_char: function (tty, val) { + if (val !== 0 && val !== 10) { + tty.output.push(val); + } + }, + fsync: function (tty) { + console.log('fsync called'); + tty.output = []; + }, + ioctl_tcgets: function (tty) { + return { + c_iflag: 0, + c_oflag: 0, + c_cflag: 0, + c_lflag: 0, + c_cc: new Array(32).fill(0), + }; + }, + ioctl_tcsets: function (tty, optional_actions, data) { + return 0; + }, + ioctl_tiocgwinsz: function (tty) { + return [25, 80]; + }, + }; + var device = FS.makedev(major, 0); + TTY.register(device, tty_ops); + FS.mkdev('/custom_tty', device); + // Populate the TTY input buffer with test data "ABCD" + TTY.ttys[device].input = [65, 66, 67, 68]; + + // TTY without get_char - should cause ENXIO on read + var tty_no_getchar = {put_char: function (tty, val) {}}; + var device_no_getchar = FS.makedev(major + 1, 0); + TTY.register(device_no_getchar, tty_no_getchar); + FS.mkdev('/tty_no_getchar', device_no_getchar); + + // TTY without put_char - should cause ENXIO on write + var tty_no_putchar = { + get_char: function (tty) { + return 0; + }, + }; + var device_no_putchar = FS.makedev(major + 2, 0); + TTY.register(device_no_putchar, tty_no_putchar); + FS.mkdev('/tty_no_putchar', device_no_putchar); + + // TTY with throwing get_char - should cause EIO on read + var tty_throw_getchar = { + get_char: function (tty) { + throw new Error('get_char error'); + }, + put_char: function (tty, val) {}, + }; + var device_throw_getchar = FS.makedev(major + 3, 0); + TTY.register(device_throw_getchar, tty_throw_getchar); + FS.mkdev('/tty_throw_getchar', device_throw_getchar); + + // TTY with throwing put_char - should cause EIO on write + var tty_throw_putchar = { + get_char: function (tty) { + return 0; + }, + put_char: function (tty, val) { + throw new Error('put_char error'); + }, + }; + var device_throw_putchar = FS.makedev(major + 4, 0); + TTY.register(device_throw_putchar, tty_throw_putchar); + FS.mkdev('/tty_throw_putchar', device_throw_putchar); + + // TTY with empty input (returns undefined immediately) - should cause EAGAIN on + // read + var tty_empty = { + get_char: function (tty) { + return undefined; + }, + put_char: function (tty, val) {}, + }; + var device_empty = FS.makedev(major + 5, 0); + TTY.register(device_empty, tty_empty); + FS.mkdev('/tty_empty', device_empty); +}); +// clang-format on + +int main() { + init(); + char readBuffer[256] = {0}; + char writeBuffer[] = "Test"; + struct winsize ws; + struct termios term; + + printf("\nTest 1: open custom TTY device and check isatty\n"); + int fd = open("/custom_tty", O_RDWR); + assert(fd >= 0); + printf("isatty: %d\n", isatty(fd)); + printf("errno after open: %s\n", strerror(errno)); + errno = 0; + + printf("\nTest 2: read from TTY with data\n"); + ssize_t bytesRead = read(fd, readBuffer, sizeof(readBuffer)); + printf("read bytes: %zd\n", bytesRead); + printf("read data: %s\n", bytesRead > 0 ? readBuffer : "(none)"); + printf("errno after read: %s\n", strerror(errno)); + errno = 0; + + printf("\nTest 3: write to TTY\n"); + ssize_t bytesWritten = write(fd, writeBuffer, strlen(writeBuffer)); + printf("write bytes: %zd\n", bytesWritten); + printf("errno after write: %s\n", strerror(errno)); + errno = 0; + + printf("\nTest 4: ioctl TIOCGWINSZ\n"); + int result = ioctl(fd, TIOCGWINSZ, &ws); + printf("ioctl TIOCGWINSZ: %d\n", result); + printf("ws_row: %d ws_col: %d\n", ws.ws_row, ws.ws_col); + printf("errno after ioctl: %s\n", strerror(errno)); + errno = 0; + + printf("\nTest 5: ioctl TCGETS\n"); + result = ioctl(fd, TCGETS, &term); + printf("ioctl TCGETS: %d\n", result); + printf("errno after TCGETS: %s\n", strerror(errno)); + errno = 0; + + printf("\nTest 6: ioctl TCSETS\n"); + result = ioctl(fd, TCSETS, &term); + printf("ioctl TCSETS: %d\n", result); + printf("errno after TCSETS: %s\n", strerror(errno)); + errno = 0; + + printf("\nTest 7: fsync\n"); + result = fsync(fd); + printf("fsync: %d\n", result); + printf("errno after fsync: %s\n", strerror(errno)); + errno = 0; + + close(fd); + + printf("\nTest 8: no put_char\n"); + fd = open("/tty_no_putchar", O_WRONLY); + assert(fd >= 0); + bytesWritten = write(fd, writeBuffer, strlen(writeBuffer)); + printf("write: %zd\n", bytesWritten); + printf("errno: %s\n", strerror(errno)); + errno = 0; + close(fd); + + printf("\nTest 9: no get_char\n"); + fd = open("/tty_no_getchar", O_RDONLY); + assert(fd >= 0); + bytesRead = read(fd, readBuffer, sizeof(readBuffer)); + printf("read: %zd\n", bytesRead); + printf("errno: %s\n", strerror(errno)); + errno = 0; + close(fd); + + printf("\nTest 10: put_char throws\n"); + fd = open("/tty_throw_putchar", O_WRONLY); + assert(fd >= 0); + bytesWritten = write(fd, writeBuffer, strlen(writeBuffer)); + printf("write: %zd\n", bytesWritten); + printf("errno: %s\n", strerror(errno)); + errno = 0; + close(fd); + + printf("\nTest 11: get_char throws\n"); + fd = open("/tty_throw_getchar", O_RDONLY); + assert(fd >= 0); + bytesRead = read(fd, readBuffer, sizeof(readBuffer)); + printf("read: %zd\n", bytesRead); + printf("errno: %s\n", strerror(errno)); + errno = 0; + close(fd); + + printf("\nTest 12: get_char returns undefined\n"); + fd = open("/tty_empty", O_RDONLY); + assert(fd >= 0); + bytesRead = read(fd, readBuffer, sizeof(readBuffer)); + printf("read: %zd\n", bytesRead); + printf("errno: %s\n", strerror(errno)); + errno = 0; + close(fd); + + printf("\ndone\n"); + return 0; +} diff --git a/test/other/libtty.out b/test/other/libtty.out new file mode 100644 index 0000000000000..0cd5c04e16eb2 --- /dev/null +++ b/test/other/libtty.out @@ -0,0 +1,54 @@ + +Test 1: open custom TTY device and check isatty +isatty: 1 +errno after open: Success + +Test 2: read from TTY with data +read bytes: 4 +read data: ABCD +errno after read: Success + +Test 3: write to TTY +write bytes: 4 +errno after write: Success + +Test 4: ioctl TIOCGWINSZ +ioctl TIOCGWINSZ: 0 +ws_row: 25 ws_col: 80 +errno after ioctl: Success + +Test 5: ioctl TCGETS +ioctl TCGETS: 0 +errno after TCGETS: Success + +Test 6: ioctl TCSETS +ioctl TCSETS: 0 +errno after TCSETS: Success + +Test 7: fsync +fsync called +fsync: 0 +errno after fsync: Success +fsync called + +Test 8: no put_char +write: -1 +errno: No such device or address + +Test 9: no get_char +read: -1 +errno: No such device or address + +Test 10: put_char throws +write: -1 +errno: I/O error + +Test 11: get_char throws +read: -1 +errno: I/O error + +Test 12: get_char returns undefined +read: -1 +errno: Resource temporarily unavailable + +done diff --git a/test/test_other.py b/test/test_other.py index 780c164773f3a..167279b7e57c3 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -13185,6 +13185,9 @@ def test_unistd_isatty(self): self.skipTest('depends on /dev filesystem') self.do_runf('unistd/isatty.c', 'success') + def test_libtty(self): + self.do_other_test('libtty.c') + def test_unistd_login(self): self.do_run_in_out_file_test('unistd/login.c') From f54e4f6b70fc98418bc2cadbe029e2caff5cb974 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Mon, 13 Apr 2026 08:54:41 -0700 Subject: [PATCH 42/42] Automatic rebaseline of codesize expectations. NFC This is an automatic change generated by tools/maint/rebaseline_tests.py. The following (17) test expectation files were updated by running the tests with `--rebaseline`: ``` codesize/test_codesize_cxx_ctors1.json: 151829 => 151833 [+4 bytes / +0.00%] codesize/test_codesize_cxx_ctors2.json: 151232 => 151236 [+4 bytes / +0.00%] codesize/test_codesize_cxx_except.json: 195700 => 195704 [+4 bytes / +0.00%] codesize/test_codesize_cxx_except_wasm.json: 166952 => 166956 [+4 bytes / +0.00%] codesize/test_codesize_cxx_except_wasm_legacy.json: 164832 => 164836 [+4 bytes / +0.00%] codesize/test_codesize_cxx_lto.json: 120727 => 120525 [-202 bytes / -0.17%] codesize/test_codesize_cxx_mangle.json: 262181 => 262185 [+4 bytes / +0.00%] codesize/test_codesize_cxx_noexcept.json: 153851 => 153855 [+4 bytes / +0.00%] test/codesize/test_codesize_file_preload.expected.js updated codesize/test_codesize_file_preload.json: 23789 => 23793 [+4 bytes / +0.02%] codesize/test_codesize_files_js_fs.json: 18215 => 18219 [+4 bytes / +0.02%] codesize/test_codesize_hello_dylink.json: 43856 => 43860 [+4 bytes / +0.01%] codesize/test_codesize_hello_dylink_all.json: 821816 => 821817 [+1 bytes / +0.00%] codesize/test_minimal_runtime_code_size_hello_webgl2_wasm.json: 13200 => 13200 [+0 bytes / +0.00%] codesize/test_minimal_runtime_code_size_hello_webgl2_wasm2js.json: 18537 => 18534 [-3 bytes / -0.02%] codesize/test_minimal_runtime_code_size_hello_webgl_wasm.json: 12738 => 12738 [+0 bytes / +0.00%] codesize/test_minimal_runtime_code_size_hello_webgl_wasm2js.json: 18063 => 18060 [-3 bytes / -0.02%] Average change: -0.01% (-0.17% - +0.02%) ``` --- test/codesize/test_codesize_cxx_ctors1.json | 8 ++++---- test/codesize/test_codesize_cxx_ctors2.json | 8 ++++---- test/codesize/test_codesize_cxx_except.json | 8 ++++---- test/codesize/test_codesize_cxx_except_wasm.json | 8 ++++---- .../test_codesize_cxx_except_wasm_legacy.json | 4 ++-- test/codesize/test_codesize_cxx_lto.json | 12 ++++++------ test/codesize/test_codesize_cxx_mangle.json | 10 +++++----- test/codesize/test_codesize_cxx_noexcept.json | 8 ++++---- test/codesize/test_codesize_file_preload.expected.js | 4 ++-- test/codesize/test_codesize_file_preload.json | 8 ++++---- test/codesize/test_codesize_files_js_fs.json | 8 ++++---- test/codesize/test_codesize_hello_dylink.json | 8 ++++---- test/codesize/test_codesize_hello_dylink_all.json | 6 +++--- ..._minimal_runtime_code_size_hello_webgl2_wasm.json | 4 ++-- ...nimal_runtime_code_size_hello_webgl2_wasm2js.json | 8 ++++---- ...t_minimal_runtime_code_size_hello_webgl_wasm.json | 4 ++-- ...inimal_runtime_code_size_hello_webgl_wasm2js.json | 8 ++++---- 17 files changed, 62 insertions(+), 62 deletions(-) diff --git a/test/codesize/test_codesize_cxx_ctors1.json b/test/codesize/test_codesize_cxx_ctors1.json index 0b8bea69b50e7..b5b36b2a28e95 100644 --- a/test/codesize/test_codesize_cxx_ctors1.json +++ b/test/codesize/test_codesize_cxx_ctors1.json @@ -1,10 +1,10 @@ { - "a.out.js": 19194, - "a.out.js.gz": 7969, + "a.out.js": 19198, + "a.out.js.gz": 7971, "a.out.nodebug.wasm": 132635, "a.out.nodebug.wasm.gz": 49922, - "total": 151829, - "total_gz": 57891, + "total": 151833, + "total_gz": 57893, "sent": [ "__cxa_throw", "_abort_js", diff --git a/test/codesize/test_codesize_cxx_ctors2.json b/test/codesize/test_codesize_cxx_ctors2.json index 4603f919e7c55..0cf6d3c4421a3 100644 --- a/test/codesize/test_codesize_cxx_ctors2.json +++ b/test/codesize/test_codesize_cxx_ctors2.json @@ -1,10 +1,10 @@ { - "a.out.js": 19171, - "a.out.js.gz": 7957, + "a.out.js": 19175, + "a.out.js.gz": 7958, "a.out.nodebug.wasm": 132061, "a.out.nodebug.wasm.gz": 49579, - "total": 151232, - "total_gz": 57536, + "total": 151236, + "total_gz": 57537, "sent": [ "__cxa_throw", "_abort_js", diff --git a/test/codesize/test_codesize_cxx_except.json b/test/codesize/test_codesize_cxx_except.json index d70ea38a08ea4..66f79b791113c 100644 --- a/test/codesize/test_codesize_cxx_except.json +++ b/test/codesize/test_codesize_cxx_except.json @@ -1,10 +1,10 @@ { - "a.out.js": 23174, - "a.out.js.gz": 8960, + "a.out.js": 23178, + "a.out.js.gz": 8962, "a.out.nodebug.wasm": 172526, "a.out.nodebug.wasm.gz": 57447, - "total": 195700, - "total_gz": 66407, + "total": 195704, + "total_gz": 66409, "sent": [ "__cxa_begin_catch", "__cxa_end_catch", diff --git a/test/codesize/test_codesize_cxx_except_wasm.json b/test/codesize/test_codesize_cxx_except_wasm.json index 66e3ff01b1367..a303dc09a7cbc 100644 --- a/test/codesize/test_codesize_cxx_except_wasm.json +++ b/test/codesize/test_codesize_cxx_except_wasm.json @@ -1,10 +1,10 @@ { - "a.out.js": 19026, - "a.out.js.gz": 7904, + "a.out.js": 19030, + "a.out.js.gz": 7905, "a.out.nodebug.wasm": 147926, "a.out.nodebug.wasm.gz": 55308, - "total": 166952, - "total_gz": 63212, + "total": 166956, + "total_gz": 63213, "sent": [ "_abort_js", "_tzset_js", diff --git a/test/codesize/test_codesize_cxx_except_wasm_legacy.json b/test/codesize/test_codesize_cxx_except_wasm_legacy.json index bbc6a807b6b7c..1d6efbc53c2d2 100644 --- a/test/codesize/test_codesize_cxx_except_wasm_legacy.json +++ b/test/codesize/test_codesize_cxx_except_wasm_legacy.json @@ -1,9 +1,9 @@ { - "a.out.js": 19100, + "a.out.js": 19104, "a.out.js.gz": 7929, "a.out.nodebug.wasm": 145732, "a.out.nodebug.wasm.gz": 54936, - "total": 164832, + "total": 164836, "total_gz": 62865, "sent": [ "_abort_js", diff --git a/test/codesize/test_codesize_cxx_lto.json b/test/codesize/test_codesize_cxx_lto.json index 6c07b8cbdbf3f..06f5f0850212b 100644 --- a/test/codesize/test_codesize_cxx_lto.json +++ b/test/codesize/test_codesize_cxx_lto.json @@ -1,10 +1,10 @@ { - "a.out.js": 18563, - "a.out.js.gz": 7666, - "a.out.nodebug.wasm": 102164, - "a.out.nodebug.wasm.gz": 39553, - "total": 120727, - "total_gz": 47219, + "a.out.js": 18567, + "a.out.js.gz": 7667, + "a.out.nodebug.wasm": 101958, + "a.out.nodebug.wasm.gz": 39463, + "total": 120525, + "total_gz": 47130, "sent": [ "a (emscripten_resize_heap)", "b (_setitimer_js)", diff --git a/test/codesize/test_codesize_cxx_mangle.json b/test/codesize/test_codesize_cxx_mangle.json index b12d268816995..fc3ed8a20eaee 100644 --- a/test/codesize/test_codesize_cxx_mangle.json +++ b/test/codesize/test_codesize_cxx_mangle.json @@ -1,10 +1,10 @@ { - "a.out.js": 23224, - "a.out.js.gz": 8983, + "a.out.js": 23228, + "a.out.js.gz": 8984, "a.out.nodebug.wasm": 238957, - "a.out.nodebug.wasm.gz": 79820, - "total": 262181, - "total_gz": 88803, + "a.out.nodebug.wasm.gz": 79825, + "total": 262185, + "total_gz": 88809, "sent": [ "__cxa_begin_catch", "__cxa_end_catch", diff --git a/test/codesize/test_codesize_cxx_noexcept.json b/test/codesize/test_codesize_cxx_noexcept.json index 59ac98658db69..3664373ceb36d 100644 --- a/test/codesize/test_codesize_cxx_noexcept.json +++ b/test/codesize/test_codesize_cxx_noexcept.json @@ -1,10 +1,10 @@ { - "a.out.js": 19194, - "a.out.js.gz": 7969, + "a.out.js": 19198, + "a.out.js.gz": 7971, "a.out.nodebug.wasm": 134657, "a.out.nodebug.wasm.gz": 50774, - "total": 153851, - "total_gz": 58743, + "total": 153855, + "total_gz": 58745, "sent": [ "__cxa_throw", "_abort_js", diff --git a/test/codesize/test_codesize_file_preload.expected.js b/test/codesize/test_codesize_file_preload.expected.js index 1ab70743544e1..5940d4e03b11b 100644 --- a/test/codesize/test_codesize_file_preload.expected.js +++ b/test/codesize/test_codesize_file_preload.expected.js @@ -883,10 +883,10 @@ var TTY = { }, close(stream) { // flush any pending line data - stream.tty.ops.fsync(stream.tty); + stream.tty.ops.fsync?.(stream.tty); }, fsync(stream) { - stream.tty.ops.fsync(stream.tty); + stream.tty.ops.fsync?.(stream.tty); }, read(stream, buffer, offset, length, pos) { if (!stream.tty || !stream.tty.ops.get_char) { diff --git a/test/codesize/test_codesize_file_preload.json b/test/codesize/test_codesize_file_preload.json index 3cf2d2dc5c016..1106e4ecd4075 100644 --- a/test/codesize/test_codesize_file_preload.json +++ b/test/codesize/test_codesize_file_preload.json @@ -1,10 +1,10 @@ { - "a.out.js": 22141, - "a.out.js.gz": 9184, + "a.out.js": 22145, + "a.out.js.gz": 9186, "a.out.nodebug.wasm": 1648, "a.out.nodebug.wasm.gz": 938, - "total": 23789, - "total_gz": 10122, + "total": 23793, + "total_gz": 10124, "sent": [ "a (fd_write)" ], diff --git a/test/codesize/test_codesize_files_js_fs.json b/test/codesize/test_codesize_files_js_fs.json index 78a3cf7f21ebf..9e6c725bd7c86 100644 --- a/test/codesize/test_codesize_files_js_fs.json +++ b/test/codesize/test_codesize_files_js_fs.json @@ -1,10 +1,10 @@ { - "a.out.js": 17834, - "a.out.js.gz": 7308, + "a.out.js": 17838, + "a.out.js.gz": 7309, "a.out.nodebug.wasm": 381, "a.out.nodebug.wasm.gz": 260, - "total": 18215, - "total_gz": 7568, + "total": 18219, + "total_gz": 7569, "sent": [ "a (fd_write)", "b (fd_read)", diff --git a/test/codesize/test_codesize_hello_dylink.json b/test/codesize/test_codesize_hello_dylink.json index 9c3b1473570d7..baccc7fe64842 100644 --- a/test/codesize/test_codesize_hello_dylink.json +++ b/test/codesize/test_codesize_hello_dylink.json @@ -1,10 +1,10 @@ { - "a.out.js": 26185, - "a.out.js.gz": 11171, + "a.out.js": 26189, + "a.out.js.gz": 11174, "a.out.nodebug.wasm": 17671, "a.out.nodebug.wasm.gz": 8923, - "total": 43856, - "total_gz": 20094, + "total": 43860, + "total_gz": 20097, "sent": [ "__syscall_stat64", "emscripten_resize_heap", diff --git a/test/codesize/test_codesize_hello_dylink_all.json b/test/codesize/test_codesize_hello_dylink_all.json index 105e4113624d7..4dd3a7904ca1c 100644 --- a/test/codesize/test_codesize_hello_dylink_all.json +++ b/test/codesize/test_codesize_hello_dylink_all.json @@ -1,7 +1,7 @@ { - "a.out.js": 244343, - "a.out.nodebug.wasm": 577473, - "total": 821816, + "a.out.js": 244347, + "a.out.nodebug.wasm": 577470, + "total": 821817, "sent": [ "IMG_Init", "IMG_Load", diff --git a/test/codesize/test_minimal_runtime_code_size_hello_webgl2_wasm.json b/test/codesize/test_minimal_runtime_code_size_hello_webgl2_wasm.json index 65d134eece5d2..f74de1ab6a6f7 100644 --- a/test/codesize/test_minimal_runtime_code_size_hello_webgl2_wasm.json +++ b/test/codesize/test_minimal_runtime_code_size_hello_webgl2_wasm.json @@ -4,7 +4,7 @@ "a.js": 4437, "a.js.gz": 2281, "a.wasm": 8313, - "a.wasm.gz": 5646, + "a.wasm.gz": 5648, "total": 13200, - "total_gz": 8245 + "total_gz": 8247 } diff --git a/test/codesize/test_minimal_runtime_code_size_hello_webgl2_wasm2js.json b/test/codesize/test_minimal_runtime_code_size_hello_webgl2_wasm2js.json index 93c8647732266..09d11b0682f79 100644 --- a/test/codesize/test_minimal_runtime_code_size_hello_webgl2_wasm2js.json +++ b/test/codesize/test_minimal_runtime_code_size_hello_webgl2_wasm2js.json @@ -1,8 +1,8 @@ { "a.html": 342, "a.html.gz": 252, - "a.js": 18195, - "a.js.gz": 9829, - "total": 18537, - "total_gz": 10081 + "a.js": 18192, + "a.js.gz": 9826, + "total": 18534, + "total_gz": 10078 } diff --git a/test/codesize/test_minimal_runtime_code_size_hello_webgl_wasm.json b/test/codesize/test_minimal_runtime_code_size_hello_webgl_wasm.json index 75607a4d6a1d8..61816037cdae4 100644 --- a/test/codesize/test_minimal_runtime_code_size_hello_webgl_wasm.json +++ b/test/codesize/test_minimal_runtime_code_size_hello_webgl_wasm.json @@ -4,7 +4,7 @@ "a.js": 3975, "a.js.gz": 2123, "a.wasm": 8313, - "a.wasm.gz": 5646, + "a.wasm.gz": 5648, "total": 12738, - "total_gz": 8087 + "total_gz": 8089 } diff --git a/test/codesize/test_minimal_runtime_code_size_hello_webgl_wasm2js.json b/test/codesize/test_minimal_runtime_code_size_hello_webgl_wasm2js.json index c4a9691ca6749..3ea609d71d2e8 100644 --- a/test/codesize/test_minimal_runtime_code_size_hello_webgl_wasm2js.json +++ b/test/codesize/test_minimal_runtime_code_size_hello_webgl_wasm2js.json @@ -1,8 +1,8 @@ { "a.html": 342, "a.html.gz": 252, - "a.js": 17721, - "a.js.gz": 9665, - "total": 18063, - "total_gz": 9917 + "a.js": 17718, + "a.js.gz": 9662, + "total": 18060, + "total_gz": 9914 }