diff --git a/.circleci/config.yml b/.circleci/config.yml index cd96285b981b5..8e762fe57fd27 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: " @@ -1368,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 @@ -1389,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 @@ -1412,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 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 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/ChangeLog.md b/ChangeLog.md index 86c661d6dae88..2e1d037f5970f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -18,8 +18,17 @@ 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) ---------------------- +- 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 (#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 +---------------- - 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/docs/design/01-precise-futex-wakeups.md b/docs/design/01-precise-futex-wakeups.md new file mode 100644 index 0000000000000..70fbf22d6a2a4 --- /dev/null +++ b/docs/design/01-precise-futex-wakeups.md @@ -0,0 +1,138 @@ +# Design Doc: Precise Futex Wakeups + +- **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 Workers do not have a `pthread` structure, so 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 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. +// +// 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: + +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: + +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 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. + +## Security/Safety Considerations +- **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/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. diff --git a/docs/design/README.md b/docs/design/README.md new file mode 100644 index 0000000000000..6e5b4bb388e37 --- /dev/null +++ b/docs/design/README.md @@ -0,0 +1,21 @@ +# Emscripten Design Documents + +This directory contains design documents for emscripten features and major +changes/refactors. + +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 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". 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 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); 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`` diff --git a/site/source/docs/tools_reference/settings_reference.rst b/site/source/docs/tools_reference/settings_reference.rst index b7777880f414b..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: @@ -2919,10 +2921,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: @@ -3420,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/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/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/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/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/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/lib/libwasm_worker.js b/src/lib/libwasm_worker.js index e4eb8491389c1..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 @@ -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/src/lib/libwasmfs_node.js b/src/lib/libwasmfs_node.js index 02295c10a164f..8922899e31d22 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: "!!globalThis.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; @@ -157,16 +157,16 @@ addToLibrary({ }, _wasmfs_node_truncate__i53abi: true, - _wasmfs_node_truncate__deps : ['$wasmfsTry'], - _wasmfs_node_truncate : (path_p, len) => { - if (isNaN(len)) return -{{{ cDefs.EOVERFLOW }}}; + _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) => { - if (isNaN(len)) return -{{{ cDefs.EOVERFLOW }}}; + _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. @@ -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); diff --git a/src/preamble.js b/src/preamble.js index ab3a7d78b8806..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') @@ -301,20 +300,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/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..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 @@ -1920,9 +1921,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/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/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/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/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/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/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..b1306fe5b9ea6 100644 --- a/system/lib/pthread/pthread_kill.c +++ b/system/lib/pthread/pthread_kill.c @@ -11,37 +11,15 @@ #include #include "pthread_impl.h" -#include "lock.h" -void do_raise(void* arg) { - int sig = (intptr_t)arg; - 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((intptr_t)sig); +static void proxied_raise(void* arg) { + 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 +27,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)) { + raise(sig); + } else { + emscripten_proxy_async(emscripten_proxy_get_system_queue(), t, proxied_raise, (void*)(intptr_t)sig); + } return 0; } 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); } diff --git a/system/lib/wasmfs/js_api.cpp b/system/lib/wasmfs/js_api.cpp index dba5fdb6dd93d..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" @@ -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; } 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/test/codesize/test_codesize_cxx_ctors1.json b/test/codesize/test_codesize_cxx_ctors1.json index dff56a4063fb0..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.nodebug.wasm": 132638, - "a.out.nodebug.wasm.gz": 49927, - "total": 151832, - "total_gz": 57896, + "a.out.js": 19198, + "a.out.js.gz": 7971, + "a.out.nodebug.wasm": 132635, + "a.out.nodebug.wasm.gz": 49922, + "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 fb2e77ec4ae92..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.nodebug.wasm": 132064, - "a.out.nodebug.wasm.gz": 49586, - "total": 151235, - "total_gz": 57543, + "a.out.js": 19175, + "a.out.js.gz": 7958, + "a.out.nodebug.wasm": 132061, + "a.out.nodebug.wasm.gz": 49579, + "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 c29dbd2b26b46..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.nodebug.wasm": 172516, - "a.out.nodebug.wasm.gz": 57438, - "total": 195690, - "total_gz": 66398, + "a.out.js": 23178, + "a.out.js.gz": 8962, + "a.out.nodebug.wasm": 172526, + "a.out.nodebug.wasm.gz": 57447, + "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 cb737cf73678b..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.nodebug.wasm": 147922, - "a.out.nodebug.wasm.gz": 55312, - "total": 166948, - "total_gz": 63216, + "a.out.js": 19030, + "a.out.js.gz": 7905, + "a.out.nodebug.wasm": 147926, + "a.out.nodebug.wasm.gz": 55308, + "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 71fbc6b06eb6d..1d6efbc53c2d2 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": 19104, "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": 164836, + "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 7bea9337c40d2..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": 101956, - "a.out.nodebug.wasm.gz": 39460, - "total": 120519, - "total_gz": 47126, + "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 cd1eeff3bb99d..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": 79842, - "total": 262181, - "total_gz": 88825, + "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 4881c4a93cac7..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.nodebug.wasm": 134661, - "a.out.nodebug.wasm.gz": 50777, + "a.out.js": 19198, + "a.out.js.gz": 7971, + "a.out.nodebug.wasm": 134657, + "a.out.nodebug.wasm.gz": 50774, "total": 153855, - "total_gz": 58746, + "total_gz": 58745, "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.expected.js b/test/codesize/test_codesize_file_preload.expected.js index 785751e68113b..5940d4e03b11b 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 @@ -884,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) { @@ -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_file_preload.json b/test/codesize/test_codesize_file_preload.json index a84ee7c65dad3..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": 939, - "total": 23789, - "total_gz": 10123, + "a.out.nodebug.wasm.gz": 938, + "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_O0.json b/test/codesize/test_codesize_hello_O0.json index ed7a13ce715c6..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": 24261, - "a.out.js.gz": 8714, + "a.out.js": 24221, + "a.out.js.gz": 8717, "a.out.nodebug.wasm": 14850, - "a.out.nodebug.wasm.gz": 7311, - "total": 39111, - "total_gz": 16025, + "a.out.nodebug.wasm.gz": 7314, + "total": 39071, + "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..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.nodebug.wasm": 17668, - "a.out.nodebug.wasm.gz": 8921, - "total": 43853, - "total_gz": 20092, + "a.out.js": 26189, + "a.out.js.gz": 11174, + "a.out.nodebug.wasm": 17671, + "a.out.nodebug.wasm.gz": 8923, + "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 8eaf79b3ee12a..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": 577447, - "total": 821790, + "a.out.js": 244347, + "a.out.nodebug.wasm": 577470, + "total": 821817, "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_codesize_minimal_O0.expected.js b/test/codesize/test_codesize_minimal_O0.expected.js index ad72f50b73f66..90ccede8fa797 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; @@ -510,7 +510,6 @@ function postRun() { /** * @param {string|number=} what - * @noreturn */ function abort(what) { @@ -544,20 +543,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 8d4cc47adfaba..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": 6998, + "a.out.js": 19412, + "a.out.js.gz": 6999, "a.out.nodebug.wasm": 1015, "a.out.nodebug.wasm.gz": 602, - "total": 20467, - "total_gz": 7600, + "total": 20427, + "total_gz": 7601, "sent": [], "imports": [], "exports": [ 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)", 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 3fd577e8caa13..be274ef0a737c 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": 17753, + "hello_world.js": 56998, + "hello_world.js.gz": 17746, "hello_world.wasm": 14850, - "hello_world.wasm.gz": 7311, - "no_asserts.js": 26654, - "no_asserts.js.gz": 8901, + "hello_world.wasm.gz": 7314, + "no_asserts.js": 26622, + "no_asserts.js.gz": 8888, "no_asserts.wasm": 12010, "no_asserts.wasm.gz": 5880, - "strict.js": 54896, - "strict.js.gz": 17061, + "strict.js": 54816, + "strict.js.gz": 17052, "strict.wasm": 14850, - "strict.wasm.gz": 7311, - "total": 180338, - "total_gz": 64217 + "strict.wasm.gz": 7310, + "total": 180146, + "total_gz": 64190 } 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/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/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): 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/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/is_lock_free.c b/test/pthread/is_lock_free.c index 537fe25f550b2..a6ea8996cd700 100644 --- a/test/pthread/is_lock_free.c +++ b/test/pthread/is_lock_free.c @@ -1,18 +1,45 @@ -#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)); 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)); + + // 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/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/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/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""" diff --git a/test/test_browser.py b/test/test_browser.py index 7f43824793515..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): @@ -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(): diff --git a/test/test_core.py b/test/test_core.py index c3f4e7fa1ed17..5dd5fd029a8f7 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: @@ -162,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) @@ -288,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 @@ -992,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']) @@ -7771,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']) @@ -9260,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. @@ -9698,33 +9704,32 @@ 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_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']) - @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']) 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/test/test_other.py b/test/test_other.py index 81d892921162c..167279b7e57c3 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], @@ -12059,7 +12061,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 @@ -13038,6 +13040,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({ @@ -13176,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') @@ -13414,15 +13426,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/test/wasm_worker/hardware_concurrency_is_lock_free.c b/test/wasm_worker/hardware_concurrency_is_lock_free.c index f02b0c6718b55..d4ee78666e2ef 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 @@ -10,10 +11,31 @@ 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)); + + // 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() { 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"); +} 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/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'): 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: 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/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/link.py b/tools/link.py index eb850b597b87e..d4cab9999c9f2 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'] @@ -1381,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'] @@ -1961,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'] @@ -1981,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. 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')) 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 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')] 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')] 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 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',