Skip to content

[v22.x] deps: V8: backport Wasm exception handling correctness fixes#62783

Open
guybedford wants to merge 17 commits intonodejs:v22.x-stagingfrom
guybedford:backport-exn-patches
Open

[v22.x] deps: V8: backport Wasm exception handling correctness fixes#62783
guybedford wants to merge 17 commits intonodejs:v22.x-stagingfrom
guybedford:backport-exn-patches

Conversation

@guybedford
Copy link
Copy Markdown
Contributor

@guybedford guybedford commented Apr 17, 2026

This is a set of V8 backports that land correctness fixes for the modern WebAssembly exception handling (exnref / try_table / throw_ref) implementation in Node.js 22's V8 12.4.254.21.

Node.js 22 force-enables --experimental-wasm-exnref at startup via src/node.cc, so the modern Wasm EH code paths are reachable by any Wasm module shipped by Node 22 users — including the default try_table / throw_ref sequences emitted by current toolchains (LLVM/Emscripten with -fwasm-exceptions, wasm-bindgen, AssemblyScript exception mode, etc.).

V8 12.4's exnref implementation predates spec finalization (April 2025) and has known correctness bugs that manifest as runtime traps (RuntimeError: unreachable inside catch wrappers), validation errors on spec-conforming modules, wrong representation of null exnref, and mis-canonicalization of exception tags across instances.

This PR brings in the strict-correctness subset of upstream V8's exnref fixes from 12.4.254.21..main, ordered chronologically to respect dependency order. Every commit is a focused correctness fix; refactors, DCHECK-only changes, Turboshaft-specific fixes (Node 22 uses Turbofan for Wasm), JSPI-specific fixes, and fuzzer-only hardening have been deliberately excluded.

# SHA Subject
1 5f1342c20b59 [wasm][exnref] Fix broken abstract casts
2 b2f3aea23a01 [wasm][exnref] Do not allow exnref at wasm/JS boundary
3 c734674e03f9 [wasm][exnref] Fix null value for constant expressions
4 692f3d526a38 [wasm][exnref] Special behavior of WA.JSTag in try_table
5 cf03d55db2a0 [wasm][exnref] Fix default value for null exnref
6 b8f91e510e0f [wasm][exnref] Update WA.JSTag semantics
7 910cb91733dc [wasm][liftoff][arm64] Fix DropExceptionValueAtOffset
8 89dc6eab605c [wasm] Add missing type canonicalization for exceptions JS API
9 323942700cfe [wasm][exnref] Accept exnref subtypes for throw_ref
10 63b8849d73ae [wasm][exnref] Reject non-nullable exnref in JS import/export
11 8e214ec3ec8c [wasm][exnref] Fix catchless try_table
12 e7ccf0af1bdd [wasm] Fix default externref/exnref reference
13 7cb6188cf913 [wasm][exnref] Accept non-nullable exn catch type
14 b96e40d5ac85 [wasm][js-api] Fix exception handling in WebAssembly.Exception ctor
15 9997fc013952 [wasm][exnref] Use wasm_null for exnref (null representation fix)
16 1b27e4674f11 [wasm] Disallow v128 in exception handling JS API
17 85b390089e51 [wasm][eh] Fix getArg() when exception has an S128

Notable

Commit #4 (692f3d526a38) in particular fixes a runtime RuntimeError: unreachable thrown from within the try_table JSTag catch wrapper on v22.x — the most user-visible symptom that motivated this triage.

Commit #15 (9997fc013952) changes the internal representation of a null exnref to use V8's wasm_null sentinel so that throw_ref of a caught JS null vs. a null exnref can behave per the spec (one rethrowable, one not). The upstream patch adds a use_wasm_null() helper; in this backport the equivalent representation change has been applied directly to the three compilers (Liftoff / Turbofan / graph-builder-interface) and the constant-expression interface, since the helper doesn't exist in V8 12.4. The new WasmThrowRef builtin from the upstream patch is included verbatim.

Commits #8 (89dc6eab605c) and #15 required manual porting because of V8 API drift; all others applied with git node v8 backport either cleanly or with only line-offset fuzz.

Explicitly NOT included

Per the Node.js V8 maintenance policy ("Only bug fixes are accepted for backporting") and scoping to observable user-facing correctness, the following upstream commits were excluded:

  • Turboshaft-only fixes (Turboshaft for Wasm is not the default optimizer in V8 12.4)
  • JSPI-specific fixes (--experimental-wasm-jspi is not forced on in Node 22)
  • DCHECK-only fixes (no release-build impact)
  • Fuzzer-only hardening of %GetWasmExceptionTag* intrinsics (require --allow-natives-syntax)
  • The 737-line WasmJSApiScope refactor (7d4ba5062ac) and its follow-ups — error-message consistency, not correctness
  • wasm_null helper follow-ups (47fb21842c1) — superseded by the direct application in commit domain: add arguments for the function in domain.run() #15
  • [wasm][eh] mixed old/new EH disabling (af887e07511 / bf0970ea9e5) — spec conformance rather than a crash/miscompile
  • 5e3f70eb5b9 (Liftoff slot count for EH) — a legacy-EH bug, being tracked as a separate backport concern
  • 2d499b85959 (top type) — compiler-hardening, not user-facing correctness

A final omission worth calling out: 657d8de2742 [maglev] Fix throwing node inside eager inlining — the single most impactful remaining wasm-EH correctness fix upstream. This one applies equally to Node 22 and Node 24 and will be submitted as a separate PR so it can be released in parallel with this series.

Verification

  • make -j$(nproc) completes cleanly against v22.x-staging.
  • Binary reports v22.22.3-pre / V8 12.4.254.21-node.40.
  • WebAssembly EH JS APIs (WebAssembly.Tag, WebAssembly.Exception) present and functional.
  • Wasm Bindgen panic=unwind test suite passes with this change
  • The V8 CI run (node-test-commit-v8-linux) should be requested in addition to the regular CI per the Node.js V8 maintenance doc.

/cc @nodejs/v8 @nodejs/lts @nodejs/releasers

Original commit message:

    [wasm][exnref] Fix broken abstract casts

    ref.test, ref.cast, br_on_cast, br_on_cast_fail allow arbitrary heap
    types, so they also allow exnref and noexnref.

    This CL also fixes the missing type checks in the js to wasm wrapper.

    Bug: v8:14398
    Change-Id: Ieefb9a8e99d3d7a4b175db60f55b7fa9a96c5203
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5372489
    Reviewed-by: Thibaud Michaud <thibaudm@chromium.org>
    Commit-Queue: Matthias Liedtke <mliedtke@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#92867}

Refs: v8/v8@5f1342c
@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/gyp
  • @nodejs/security-wg
  • @nodejs/v8-update

@nodejs-github-bot nodejs-github-bot added build Issues and PRs related to build files or the CI. needs-ci PRs that need a full CI run. v22.x Issues that can be reproduced on v22.x or PRs targeting the v22.x-staging branch. v8 engine Issues and PRs related to the V8 dependency. labels Apr 17, 2026
@meixg meixg added the request-ci Add this label to start a Jenkins CI on a PR. label Apr 17, 2026
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Apr 17, 2026
@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

@guybedford guybedford force-pushed the backport-exn-patches branch from 3e2a6d5 to d9bc587 Compare April 17, 2026 15:58
Comment thread common.gypi Outdated
@guybedford guybedford force-pushed the backport-exn-patches branch from d9bc587 to 902731a Compare April 17, 2026 16:26
thibaudmichaud and others added 13 commits April 17, 2026 09:29
Original commit message:

    [wasm][exnref] Do not allow exnref at the wasm/JS boundary

    R=mliedtke@chromium.org

    Bug: v8:14398
    Change-Id: I5bb75a83e9de9f838d8e530c77c89aa031f473f9
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5381603
    Reviewed-by: Matthias Liedtke <mliedtke@chromium.org>
    Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#92944}

Refs: v8/v8@b2f3aea
Original commit message:

    [wasm][exnref] Fix null value for constant expressions

    Bug: v8:14398
    Change-Id: Ia00d2de97a897d608d6c043b6e267c7d6313a18b
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5402583
    Auto-Submit: Manos Koukoutos <manoskouk@chromium.org>
    Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
    Reviewed-by: Thibaud Michaud <thibaudm@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#93107}

Refs: v8/v8@c734674
Original commit message:

    [wasm][exnref] Implement special behavior of WA.JSTag in try_table.

    This commit ports the changes from 4e79015dc28659a8a031f06580e902720b35674c
    to `CatchCase`, i.e., the `try_table` instruction. In addition, it
    implements the same changes in Turboshaft, for which the implementation
    of exceptions initially came from 2c1c14d30c61fa4fa19184369e587cb663bd8580
    and already contained the `JSTag` handling for `CatchException`.

    This commit addresses part of https://issues.chromium.org/issues/333067164
    but not all. Only catching with `try_table` is fixed. `throw` remains
    unchanged.

    Change-Id: I7bad031e28eaf609fb12e7706d0b6a7cc63fa09d
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5435077
    Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
    Reviewed-by: Thibaud Michaud <thibaudm@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#93303}

Refs: v8/v8@692f3d5
Original commit message:

    [wasm][exnref] Fix default value for null exnref

    R=manoskouk@chromium.org

    Bug: 332081797
    Change-Id: Ied777935946c880a78e2011040a4d9ab19a4ddd2
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5444544
    Reviewed-by: Manos Koukoutos <manoskouk@chromium.org>
    Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#93310}

Refs: v8/v8@cf03d55
Original commit message:

    [wasm][exnref] Update WA.JSTag semantics

    According to the last spec updates:
    - Passing WebAssembly.JSTag to the WebAssembly.Exception constructor is
      not allowed in JS,
    - Throwing an exception with the JSTag in wasm is allowed, but the
      exception should conceptually be "unwrapped" when it exits wasm, and
      JS should observe the raw externref. Instead, we simply throw the
      externref in this case, which has the same observable behavior and is
      consistent with how JSTag has been implemented so far.

    R=ahaas@chromium.org

    Bug: 333067164
    Change-Id: I6f43df8d254dd7450137d99ff7cc9cbffb697663
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5454404
    Reviewed-by: Andreas Haas <ahaas@chromium.org>
    Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#93398}

Refs: v8/v8@b8f91e5
Original commit message:

    [wasm][liftoff][arm64] Fix DropExceptionValueAtOffset

    We cannot exit the iteration early, we must update all entries
    in the cache state.

    Fixed: 343748812
    Change-Id: I8353acb7bd0edc4b979db92e44d24cb9028fd92b
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5596273
    Reviewed-by: Clemens Backes <clemensb@chromium.org>
    Commit-Queue: Clemens Backes <clemensb@chromium.org>
    Auto-Submit: Jakob Kummerow <jkummerow@chromium.org>
    Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#94244}

Refs: v8/v8@910cb91733dc
Original commit message:

    [wasm] Add missing type canonicalization for exceptions JS API

    When we encode a JS value in a wasm exception, canonicalize the type
    stored in the tag's signature first. Canonicalize it using the tag's
    original module by storing the instance on the tag object.

    R=jkummerow@chromium.org

    Bug: 346197738
    Change-Id: I7575fd79c792d98e4a11c00b466700f0ab82d164
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5613375
    Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
    Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#94335}

Refs: v8/v8@89dc6eab605c
Original commit message:

    [wasm][exnref] Accept exnref subtypes for throw_ref

    Only accepting a strict exnref type was incorrect in two cases:
    - the stack-polymorphic case, with the bottom type
    - a noexn param

    R=jkummerow@chromium.org

    Fixed: 332931390
    Change-Id: I80c96a7026f2469e6a3ce54344c2d5e617b78be7
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5904414
    Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
    Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#96387}

Refs: v8/v8@323942700cfe
Original commit message:

    [wasm][exnref] Reject non-nullable exnref in JS import/export

    R=jkummerow@chromium.org

    Change-Id: I0ba2deb1a9671d87e76fea7bfe76df9eee56442a
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5920338
    Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
    Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#96489}

Refs: v8/v8@63b8849d73ae
Original commit message:

    [wasm][exnref] Fix catchless try_table

    If the try_table does not have a catch handler, we don't update the
    {current_catch_} index or set the {previous_catch} field of the block
    when we enter it. Therefore also skip the reverse operation when the
    block ends.

    R=clemensb@chromium.org

    Fixed: 372261626
    Change-Id: Ib3a23e32f9d0ec153d6a00733d96b52cf040e8bd
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5920086
    Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
    Reviewed-by: Clemens Backes <clemensb@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#96481}

Refs: v8/v8@8e214ec
Original commit message:

    [wasm] Fix default externref/exnref reference

    - The default nullexternref should be null instead of undefined
    - The default exnref/nullexnref should be null instead of wasm_null

    R=mliedtke@chromium.org

    Fixed: 372285204,372269618
    Change-Id: Id5addce2b196f7ba81aac3c2dd9447a91ed2ce2b
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5922878
    Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
    Reviewed-by: Matthias Liedtke <mliedtke@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#96531}

Refs: v8/v8@e7ccf0a
Original commit message:

    [wasm][exnref] Accept non-nullable exn catch type

    R=jkummerow@chromium.org

    Fixed: 373681572
    Change-Id: Iecfc86d2ce6592a6f442bc3504ddde58ff236f64
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5938956
    Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
    Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#96637}

Refs: v8/v8@7cb6188
Original commit message:

    [wasm][js-api] Fix exception handling in Exception

    Calling `Get` on the third argument can throw. If so, return
    immediately.

    R=thibaudm@chromium.org

    Fixed: 372993873
    Change-Id: I70ec0d0421833a60151f6bcdc9c386f6ad864256
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5937803
    Reviewed-by: Thibaud Michaud <thibaudm@chromium.org>
    Commit-Queue: Clemens Backes <clemensb@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#96627}

Refs: v8/v8@b96e40d
Original commit message:

    [wasm][exnref] Use wasm_null for exnref

    A JS null caught in wasm as an exnref with catch_(all_)ref should be
    observably different from a null exnref: a JS null should behave like a
    regular JS exception with null as the externref package, while a null
    exnref is the actual null value for this type. In particular, a JS
    null exception can be rethrown while a null exnref cannot.
    Represent null exnrefs with wasm_null instead of JS null to avoid the
    confusion.

    R=jkummerow@chromium.org

    Fixed: 374790906
    Change-Id: If9f16a24407ee7d1399613255c3f14e0a6ebef9e
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5953226
    Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
    Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#96782}

Refs: v8/v8@9997fc013952
Original commit message:

    [wasm] Disallow v128 in exception handling JS API

    R=jkummerow@chromium.org

    Fixed: 395214627
    Change-Id: Ief84b0fd79a87e539dfbfed31d475926f0f0a288
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6249317
    Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
    Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#98608}

Refs: v8/v8@1b27e4674f11
Original commit message:

    [wasm][eh] Fix getArg() when exception has an S128

    We cannot read an S128 exception value from JS, but we can read a value
    at a higher index in the exception. So accept S128 values when we
    compute the encoded index.

    R=mliedtke@chromium.org

    Fixed: 403675482
    Change-Id: I7cc0238310863b6d579fcbe0a216ddce6f760c8b
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6367014
    Reviewed-by: Matthias Liedtke <mliedtke@chromium.org>
    Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#99305}

Refs: v8/v8@85b390089e51
@guybedford guybedford force-pushed the backport-exn-patches branch from 902731a to 37e2c4c Compare April 17, 2026 16:30
@guybedford
Copy link
Copy Markdown
Contributor Author

I ran it locally, and V8 CI should be passing now on this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build Issues and PRs related to build files or the CI. needs-ci PRs that need a full CI run. v8 engine Issues and PRs related to the V8 dependency. v22.x Issues that can be reproduced on v22.x or PRs targeting the v22.x-staging branch.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants