Skip to content

Commit 7864617

Browse files
committed
Clean up BridgeJSLink
1 parent 2b8bcaf commit 7864617

File tree

7 files changed

+460
-358
lines changed

7 files changed

+460
-358
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ClosureCodegen.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,28 @@ public struct ClosureCodegen {
188188
let collector = ClosureSignatureCollectorVisitor()
189189
var walker = BridgeTypeWalker(visitor: collector)
190190
walker.walk(skeleton)
191-
let closureSignatures = walker.visitor.signatures
191+
var closureSignatures = walker.visitor.signatures
192+
193+
// When any async import exists, inject a (JSValue) -> Void closure signature
194+
// so the closure infrastructure generates the make/invoke exports needed by
195+
// _bjs_awaitPromise's resolve/reject callbacks.
196+
if let imported = skeleton.imported {
197+
let hasAsyncImport = imported.children.contains { file in
198+
file.functions.contains(where: { $0.effects.isAsync })
199+
|| file.types.contains(where: { type in
200+
type.methods.contains(where: { $0.effects.isAsync })
201+
})
202+
}
203+
if hasAsyncImport {
204+
closureSignatures.insert(
205+
ClosureSignature(
206+
parameters: [.jsValue],
207+
returnType: .void,
208+
moduleName: skeleton.moduleName
209+
)
210+
)
211+
}
212+
}
192213

193214
guard !closureSignatures.isEmpty else { return nil }
194215

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -212,13 +212,13 @@ public struct ImportTS {
212212
}
213213
}
214214

215-
/// Prepends a `continuationPtr: Int32` parameter to the ABI parameter list.
215+
/// Prepends `resolveRef: Int32, rejectRef: Int32` parameters to the ABI parameter list.
216216
///
217-
/// Used for async imports where the JS side needs the continuation pointer
218-
/// to resolve/reject the Promise.
219-
func prependContinuationPtr() {
220-
abiParameterSignatures.insert(("continuationPtr", .i32), at: 0)
221-
abiParameterForwardings.insert("continuationPtr", at: 0)
217+
/// Used for async imports where the JS side receives closure-backed
218+
/// resolve/reject callbacks as object references.
219+
func prependClosureCallbackParams() {
220+
abiParameterSignatures.insert(contentsOf: [("resolveRef", .i32), ("rejectRef", .i32)], at: 0)
221+
abiParameterForwardings.insert(contentsOf: ["resolveRef", "rejectRef"], at: 0)
222222
}
223223

224224
func call(skipExceptionCheck: Bool = false) throws {
@@ -289,20 +289,20 @@ public struct ImportTS {
289289
}
290290

291291
func liftAsyncReturnValue(originalReturnType: BridgeType) {
292-
// For async imports, we use the continuation-pointer pattern.
293-
// The extern function takes a leading `continuationPtr: Int32` and returns void.
294-
// The JS side attaches .then/.catch handlers and calls back into Wasm
295-
// via bjs_resolve_promise_continuation / bjs_reject_promise_continuation.
292+
// For async imports, the extern function takes leading `resolveRef: Int32, rejectRef: Int32`
293+
// and returns void. The JS side calls the resolve/reject closures when the Promise settles.
296294
abiReturnType = nil
297295

298296
// Wrap the existing body (parameter lowering + extern call) in _bjs_awaitPromise
299297
let innerBody = body
300298
body = CodeFragmentPrinter()
301299

302300
if originalReturnType == .void {
303-
body.write("_ = try await _bjs_awaitPromise { continuationPtr in")
301+
body.write("_ = try await _bjs_awaitPromise(makeClosure: { JSTypedClosure($0) }) { resolveRef, rejectRef in")
304302
} else {
305-
body.write("let resolved = try await _bjs_awaitPromise { continuationPtr in")
303+
body.write(
304+
"let resolved = try await _bjs_awaitPromise(makeClosure: { JSTypedClosure($0) }) { resolveRef, rejectRef in"
305+
)
306306
}
307307
body.indent {
308308
body.write(lines: innerBody.lines)
@@ -431,7 +431,7 @@ public struct ImportTS {
431431
returnType: abiReturnType
432432
)
433433
if function.effects.isAsync {
434-
builder.prependContinuationPtr()
434+
builder.prependClosureCallbackParams()
435435
}
436436
for param in function.parameters {
437437
try builder.lowerParameter(param: param)
@@ -466,7 +466,7 @@ public struct ImportTS {
466466
returnType: abiReturnType
467467
)
468468
if method.effects.isAsync {
469-
builder.prependContinuationPtr()
469+
builder.prependClosureCallbackParams()
470470
}
471471
try builder.lowerParameter(param: selfParameter)
472472
for param in method.parameters {

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 32 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,6 @@ public struct BridgeJSLink {
135135
var importObjectBuilders: [ImportObjectBuilder] = []
136136
var enumStaticAssignments: [String] = []
137137
var needsImportsObject: Bool = false
138-
var hasAsyncImports: Bool = false
139138
}
140139

141140
private func collectLinkData() throws -> LinkData {
@@ -238,19 +237,12 @@ public struct BridgeJSLink {
238237
if function.from == nil {
239238
data.needsImportsObject = true
240239
}
241-
if function.effects.isAsync {
242-
data.hasAsyncImports = true
243-
}
244240
try renderImportedFunction(importObjectBuilder: importObjectBuilder, function: function)
245241
}
246242
for type in fileSkeleton.types {
247243
if type.constructor != nil, type.from == nil {
248244
data.needsImportsObject = true
249245
}
250-
// Check for async methods in imported types
251-
for method in type.methods where method.effects.isAsync {
252-
data.hasAsyncImports = true
253-
}
254246
try renderImportedType(importObjectBuilder: importObjectBuilder, type: type)
255247
}
256248
}
@@ -322,85 +314,6 @@ public struct BridgeJSLink {
322314
]
323315
}
324316

325-
/// Generates helper functions for the continuation-pointer pattern used by async imports.
326-
///
327-
/// These encode a JS value as `(kind, payload1, payload2)` matching the `RawJSValue`
328-
/// encoding from `_CJavaScriptKit.h`, then call the appropriate Wasm export to resume
329-
/// the Swift continuation.
330-
private func generatePromiseContinuationHelpers() -> [String] {
331-
let printer = CodeFragmentPrinter()
332-
// Helper to encode a JS value into (kind, payload1, payload2) and call the resolve export
333-
printer.write("function bjs_resolvePromiseContinuation(ptr, value) {")
334-
printer.indent {
335-
printer.write(lines: generateJSValueEncoding(variableName: "value"))
336-
printer.write(
337-
"\(JSGlueVariableScope.reservedInstance).exports.bjs_resolve_promise_continuation(ptr, kind, payload1, payload2);"
338-
)
339-
}
340-
printer.write("}")
341-
// Helper to encode a JS value into (kind, payload1, payload2) and call the reject export
342-
printer.write("function bjs_rejectPromiseContinuation(ptr, error) {")
343-
printer.indent {
344-
printer.write(lines: generateJSValueEncoding(variableName: "error"))
345-
printer.write(
346-
"\(JSGlueVariableScope.reservedInstance).exports.bjs_reject_promise_continuation(ptr, kind, payload1, payload2);"
347-
)
348-
}
349-
printer.write("}")
350-
return printer.lines
351-
}
352-
353-
/// Generates JS code that encodes a variable into `(kind, payload1, payload2)`.
354-
///
355-
/// The encoding matches `JavaScriptValueKind` from `_CJavaScriptKit.h`:
356-
/// - Boolean(0): payload1 = 1 or 0
357-
/// - String(1): payload1 = retained object ID
358-
/// - Number(2): payload2 = the number
359-
/// - Object(3): payload1 = retained object ID
360-
/// - Null(4): no payload
361-
/// - Undefined(5): no payload
362-
/// - Symbol(7): payload1 = retained object ID
363-
/// - BigInt(8): payload1 = retained object ID
364-
private func generateJSValueEncoding(variableName: String) -> [String] {
365-
let s = JSGlueVariableScope.reservedSwift
366-
return [
367-
"let kind, payload1 = 0, payload2 = 0;",
368-
"if (\(variableName) === null) {",
369-
" kind = 4;",
370-
"} else if (\(variableName) === undefined) {",
371-
" kind = 5;",
372-
"} else {",
373-
" const type = typeof \(variableName);",
374-
" switch (type) {",
375-
" case \"boolean\":",
376-
" kind = 0;",
377-
" payload1 = \(variableName) ? 1 : 0;",
378-
" break;",
379-
" case \"number\":",
380-
" kind = 2;",
381-
" payload2 = \(variableName);",
382-
" break;",
383-
" case \"string\":",
384-
" kind = 1;",
385-
" payload1 = \(s).memory.retain(\(variableName));",
386-
" break;",
387-
" case \"symbol\":",
388-
" kind = 7;",
389-
" payload1 = \(s).memory.retain(\(variableName));",
390-
" break;",
391-
" case \"bigint\":",
392-
" kind = 8;",
393-
" payload1 = \(s).memory.retain(\(variableName));",
394-
" break;",
395-
" default:",
396-
" kind = 3;",
397-
" payload1 = \(s).memory.retain(\(variableName));",
398-
" break;",
399-
" }",
400-
"}",
401-
]
402-
}
403-
404317
private func generateAddImports(needsImportsObject: Bool) throws -> CodeFragmentPrinter {
405318
let printer = CodeFragmentPrinter()
406319
let allStructs = skeletons.compactMap { $0.exported?.structs }.flatMap { $0 }
@@ -726,7 +639,26 @@ public struct BridgeJSLink {
726639
let collector = ClosureSignatureCollectorVisitor()
727640
var walker = BridgeTypeWalker(visitor: collector)
728641
walker.walk(unified)
729-
let closureSignatures = walker.visitor.signatures
642+
var closureSignatures = walker.visitor.signatures
643+
644+
// Inject (JSValue) -> Void closure signature when async imports exist
645+
if let imported = unified.imported {
646+
let hasAsyncImport = imported.children.contains { file in
647+
file.functions.contains(where: { $0.effects.isAsync })
648+
|| file.types.contains(where: { type in
649+
type.methods.contains(where: { $0.effects.isAsync })
650+
})
651+
}
652+
if hasAsyncImport {
653+
closureSignatures.insert(
654+
ClosureSignature(
655+
parameters: [.jsValue],
656+
returnType: .void,
657+
moduleName: moduleName
658+
)
659+
)
660+
}
661+
}
730662

731663
guard !closureSignatures.isEmpty else { continue }
732664

@@ -1057,11 +989,6 @@ public struct BridgeJSLink {
1057989
try printer.indent {
1058990
printer.write(lines: generateVariableDeclarations())
1059991

1060-
// Generate Promise continuation helpers when async imports exist
1061-
if data.hasAsyncImports {
1062-
printer.write(lines: generatePromiseContinuationHelpers())
1063-
}
1064-
1065992
let bodyPrinter = CodeFragmentPrinter()
1066993
let allStructs = exportedSkeletons.flatMap { $0.structs }
1067994
for structDef in allStructs {
@@ -2327,36 +2254,34 @@ extension BridgeJSLink {
23272254
/// Generates the call expression for an async import.
23282255
///
23292256
/// Instead of lowering the return value, this assigns the result to `promise`
2330-
/// and attaches `.then`/`.catch` handlers that call the resolve/reject continuations.
2257+
/// and passes the resolve/reject closure refs to `.then`.
23312258
func callAsync(name: String, fromObjectExpr: String) {
23322259
let calleeExpr = Self.propertyAccessExpr(objectExpr: fromObjectExpr, propertyName: name)
23332260
let callExpr = "\(calleeExpr)(\(parameterForwardings.joined(separator: ", ")))"
23342261
body.write("const promise = \(callExpr);")
2335-
body.write("promise.then(")
2336-
body.indent {
2337-
body.write("(value) => { bjs_resolvePromiseContinuation(continuationPtr, value); },")
2338-
body.write("(error) => { bjs_rejectPromiseContinuation(continuationPtr, error); }")
2339-
}
2340-
body.write(");")
2262+
body.write("promise.then(resolve, reject);")
23412263
}
23422264

2343-
/// Renders an async import function with continuation-pointer pattern.
2265+
/// Renders an async import function with resolve/reject closure refs.
23442266
///
2345-
/// The generated function takes `continuationPtr` as the first parameter,
2267+
/// The generated function takes `resolveRef` and `rejectRef` as the first parameters,
23462268
/// wraps the import call in try/catch, attaches Promise handlers, and
2347-
/// calls reject continuation on synchronous errors.
2269+
/// calls reject on synchronous errors.
23482270
func renderAsyncFunction(name: String?) -> [String] {
23492271
let printer = CodeFragmentPrinter()
2350-
let allParams = ["continuationPtr"] + parameterNames
2272+
let allParams = ["resolveRef", "rejectRef"] + parameterNames
23512273
printer.write("function\(name.map { " \($0)" } ?? "")(\(allParams.joined(separator: ", "))) {")
23522274
printer.indent {
2275+
let s = JSGlueVariableScope.reservedSwift
2276+
printer.write("const resolve = \(s).memory.getObject(resolveRef);")
2277+
printer.write("const reject = \(s).memory.getObject(rejectRef);")
23532278
printer.write("try {")
23542279
printer.indent {
23552280
printer.write(contentsOf: body)
23562281
}
23572282
printer.write("} catch (error) {")
23582283
printer.indent {
2359-
printer.write("bjs_rejectPromiseContinuation(continuationPtr, error);")
2284+
printer.write("reject(error);")
23602285
}
23612286
printer.write("}")
23622287
}
@@ -2417,18 +2342,13 @@ extension BridgeJSLink {
24172342
)
24182343
}
24192344

2420-
/// Generates an async method call with continuation-pointer pattern.
2345+
/// Generates an async method call with resolve/reject closure refs.
24212346
func callAsyncMethod(name: String) {
24222347
let objectExpr = "\(JSGlueVariableScope.reservedSwift).memory.getObject(self)"
24232348
let calleeExpr = Self.propertyAccessExpr(objectExpr: objectExpr, propertyName: name)
24242349
let callExpr = "\(calleeExpr)(\(parameterForwardings.joined(separator: ", ")))"
24252350
body.write("const promise = \(callExpr);")
2426-
body.write("promise.then(")
2427-
body.indent {
2428-
body.write("(value) => { bjs_resolvePromiseContinuation(continuationPtr, value); },")
2429-
body.write("(error) => { bjs_rejectPromiseContinuation(continuationPtr, error); }")
2430-
}
2431-
body.write(");")
2351+
body.write("promise.then(resolve, reject);")
24322352
}
24332353

24342354
func callStaticMethod(on objectExpr: String, name: String, returnType: BridgeType) throws -> String? {

0 commit comments

Comments
 (0)