Skip to content

Box JSException storage in a class to fit the direct typed-error convention#13

Draft
krodak wants to merge 2 commits into
kr/closures-throws-asyncfrom
kr/box-jsexception-storage
Draft

Box JSException storage in a class to fit the direct typed-error convention#13
krodak wants to merge 2 commits into
kr/closures-throws-asyncfrom
kr/box-jsexception-storage

Conversation

@krodak

@krodak krodak commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Overview

An intermediate solution for the async closure reject path broken by swiftlang/swift#89320, until the IRGen fix in swiftlang/swift#89715 ships.

JSException (~36 bytes) exceeds the direct typed-error convention on wasm32, so an async throws(JSException) closure value takes the indirect-error path that swiftlang/swift#89320 miscompiles: when the closure is captureless, the thrown error is corrupted across the async unwind and the rejected Promise receives garbage instead of the JSException. Boxing JSException's storage into a single class reference (~4 bytes) moves it into the direct convention, so the broken path is never taken.

The function-export variant of the same bug was fixed separately at the codegen level in swiftwasm#760 (already on main); this PR addresses the closure side, which codegen cannot reach (the closure value is constructed by user code). With the minimum supported Swift version now 6.3 (swiftwasm#762), the boxing is unconditional: the boxed layout is validated on 6.3, and the 6.1 wasm IRGen crash it would have triggered is no longer in support scope.

1. Boxing. thrownValue, description, and stack move into a private final class; the struct holds one stored reference. The public surface is unchanged (computed accessors, same initializers, equivalent Equatable); the cost is one heap allocation per thrown exception, on the error path only.

2. Reject tests un-gated. The Swift-to-JS async throws(JSException) closure reject assertions from the base PR run unconditionally and pass; the gate is removed.

3. Base-PR mitigations removed. Since BridgeJS closures only permit throws(JSException), boxing covers every reachable closure case, so the build-time warning and the doc callouts from the base PR are dropped.

4. JSClosure.async reject path. The same bug shape existed in JSClosure.async and was untested; this PR adds the missing reject-path test, which passes with boxing.

Notes

@krodak krodak force-pushed the kr/closures-throws-async branch from 30a9c43 to aa02b44 Compare June 10, 2026 18:10
@krodak krodak force-pushed the kr/box-jsexception-storage branch from 1744fd4 to 583c12b Compare June 10, 2026 18:10
@krodak krodak force-pushed the kr/closures-throws-async branch from aa02b44 to 982e9fb Compare June 10, 2026 18:30
@krodak krodak force-pushed the kr/box-jsexception-storage branch 2 times, most recently from da5ff9c to 89de6cd Compare June 10, 2026 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant