BridgeJS: Support throws and async for closures#11
Draft
krodak wants to merge 1 commit into
Draft
Conversation
746c781 to
09bb61c
Compare
09bb61c to
30a9c43
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Adds
throws(JSException)andasync(andasync throws(JSException)) support to BridgeJS-bridged closures, in both directions: closures Swift receives from JavaScript, and closures Swift hands to JavaScript.Async settlement reuses the
_bjs_makePromise+ per-typePromise_resolve_<mangled>/Promise_rejectmachinery introduced in swiftwasm#758, so every bridged return type (@JS struct, enums,Optional/Array/Dictionary) is inherited for free; no parallel mechanism is introduced.1. Effect-aware closure mangling. Closure ABI symbol names encode effects with the Swift ABI operators (
Yafor async,Kfor throws), so signatures that differ only by effects no longer collide.2. Throwing closures (both directions). A JavaScript callback's thrown
JSExceptionpropagates into Swift; a Swift closure's thrownJSExceptionis routed back to JavaScript. Onlythrows(JSException)is supported; plainthrowsis diagnosed.3. Async closures (both directions). A JS-to-Swift callback is awaited via
_bjs_awaitPromise; a Swift-to-JS closure returns aPromisesettled via_bjs_makePromise. TypeScript surfaces these as(args) => Promise<R>. Unsupported async return types (associated-value enums, protocols, namespace enums, including nested inOptional/Array/Dictionary) are diagnosed rather than miscompiled.4. Lifetime. The closure box survives across suspension without an explicit pin: the extracted closure value, captured by the settling
Task, keeps it alive even if JavaScript releases the closure mid-flight. Covered by release-race and concurrent-invocation tests.Known limitation
A Swift-to-JS
async throws(JSException)closure resolves correctly, but a thrown error does not currently propagate to JavaScript on the reject path. This is a Swift compiler bug on wasm32, swiftlang/swift#89320, with a fix in progress in swiftlang/swift#89715: when the typed error is too large for the direct error convention (asJSExceptionis) and the closure value is captureless (or a function reference), IRGen and the wasm calling-convention padding disagree on parameter order at the thick call site, and the thrown error is lost across the async unwind.The resolve path is unaffected, capturing closures are unaffected (a practical workaround), and JS-to-Swift async-throwing callbacks are unaffected. BridgeJS now emits a build-time warning when it sees the at-risk signature (a Swift-to-JS
async throws(JSException)closure), pointing at swiftlang/swift#89320, so users are aware before they hit it at runtime. The reject assertions are gated with a pointer to swiftlang/swift#89320 and the limitation is documented in the closure articles. A stacked follow-up PR provides a standalone reproducer demonstrating the bug in isolation.Tests
Codegen snapshots, the cross-swift-syntax-version matrix, and end-to-end runtime round-trips (both directions, throwing and async, resolve and reject,
Void,@JS structreturns, the release-race, and concurrent calls) pass. The generatederror.descriptionlowering is consistent with swiftwasm#759.