Skip to content

Commit f8e6dec

Browse files
committed
BridgeJS: Unify thunk argument preparation
1 parent 991755e commit f8e6dec

82 files changed

Lines changed: 3871 additions & 1658 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Benchmarks/Sources/Generated/BridgeJS.swift

Lines changed: 297 additions & 116 deletions
Large diffs are not rendered by default.

Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,11 @@ public func _bjs_PlayBridgeJS_init() -> UnsafeMutableRawPointer {
179179
@_cdecl("bjs_PlayBridgeJS_updateDetailed")
180180
public func _bjs_PlayBridgeJS_updateDetailed(_ _self: UnsafeMutableRawPointer, _ swiftSourceBytes: Int32, _ swiftSourceLength: Int32, _ dtsSourceBytes: Int32, _ dtsSourceLength: Int32) -> Void {
181181
#if arch(wasm32)
182+
let _tmp_dtsSource = String.bridgeJSLiftParameter(dtsSourceBytes, dtsSourceLength)
183+
let _tmp_swiftSource = String.bridgeJSLiftParameter(swiftSourceBytes, swiftSourceLength)
184+
let _tmp__self = PlayBridgeJS.bridgeJSLiftParameter(_self)
182185
do {
183-
let ret = try PlayBridgeJS.bridgeJSLiftParameter(_self).updateDetailed(swiftSource: String.bridgeJSLiftParameter(swiftSourceBytes, swiftSourceLength), dtsSource: String.bridgeJSLiftParameter(dtsSourceBytes, dtsSourceLength))
186+
let ret = try _tmp__self.updateDetailed(swiftSource: _tmp_swiftSource, dtsSource: _tmp_dtsSource)
184187
return ret.bridgeJSLowerReturn()
185188
} catch let error {
186189
if let error = error.thrownValue.object {

Plugins/BridgeJS/Sources/BridgeJSCore/ClosureCodegen.swift

Lines changed: 19 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,14 @@ public struct ClosureCodegen {
112112

113113
let helperEnumDecl: DeclSyntax = "\(raw: helperEnumDeclPrinter.lines.joined(separator: "\n"))"
114114

115-
let initAccessModifier = accessLevel.modifierKeyword.map { "\($0) " } ?? ""
115+
let accessModifier = accessLevel.modifierKeyword.map { "\($0) " } ?? ""
116+
let declaration = signature.sendingParameters ? "static func sending" : "init"
117+
let returnClause = signature.sendingParameters ? " -> Self" : ""
118+
let initializer = signature.sendingParameters ? "Self" : "self.init"
116119
let typedClosureExtension: DeclSyntax = """
117120
extension JSTypedClosure where Signature == \(raw: swiftClosureType) {
118-
\(raw: initAccessModifier)init(fileID: StaticString = #fileID, line: UInt32 = #line, _ body: @escaping \(raw: swiftClosureType)) {
119-
self.init(
121+
\(raw: accessModifier)\(raw: declaration)(fileID: StaticString = #fileID, line: UInt32 = #line, _ body: @escaping \(raw: swiftClosureType))\(raw: returnClause) {
122+
\(raw: initializer)(
120123
makeClosure: \(raw: externABIName),
121124
body: body,
122125
fileID: fileID,
@@ -135,149 +138,22 @@ public struct ClosureCodegen {
135138
let swiftClosureType = swiftClosureType(for: signature)
136139
let boxType = "_BridgeJSTypedClosureBox<\(swiftClosureType)>"
137140
let abiName = "invoke_swift_closure_\(signature.moduleName)_\(signature.mangleName)"
138-
139-
// Build ABI parameters directly with WasmCoreType (no string conversion needed)
140-
var abiParams: [(name: String, type: WasmCoreType)] = [("boxPtr", .pointer)]
141-
var liftedParams: [String] = []
142-
143-
for (index, paramType) in signature.parameters.enumerated() {
144-
let paramName = "param\(index)"
145-
let liftInfo = try paramType.liftParameterInfo()
146-
147-
for (argName, wasmType) in liftInfo.parameters {
148-
let fullName =
149-
liftInfo.parameters.count > 1 ? "\(paramName)\(argName.capitalizedFirstLetter)" : paramName
150-
abiParams.append((fullName, wasmType))
151-
}
152-
153-
let argNames = liftInfo.parameters.map { (argName, _) in
154-
liftInfo.parameters.count > 1 ? "\(paramName)\(argName.capitalizedFirstLetter)" : paramName
155-
}
156-
liftedParams.append(
157-
"\(paramType.swiftType).bridgeJSLiftParameter(\(argNames.joined(separator: ", ")))"
158-
)
159-
}
160-
161-
let tryPrefix = signature.isThrows ? "try " : ""
162-
let closureCallExpr = ExprSyntax("\(raw: tryPrefix)closure(\(raw: liftedParams.joined(separator: ", ")))")
163-
let asyncTryPrefix = (signature.isThrows ? "try " : "") + "await "
164-
let asyncClosureCallExpr = ExprSyntax(
165-
"\(raw: asyncTryPrefix)closure(\(raw: liftedParams.joined(separator: ", ")))"
141+
let builder = try ExportSwift.ExportedThunkBuilder(
142+
effects: Effects(isAsync: signature.isAsync, isThrows: signature.isThrows),
143+
returnType: signature.returnType
166144
)
167-
168-
let abiReturnWasmType =
169-
signature.isAsync
170-
? try BridgeType.jsObject(nil).loweringReturnInfo().returnType
171-
: try signature.returnType.loweringReturnInfo().returnType
172-
173-
// Build signature using SwiftSignatureBuilder
174-
let funcSignature = SwiftSignatureBuilder.buildABIFunctionSignature(
175-
abiParameters: abiParams,
176-
returnType: abiReturnWasmType
145+
builder.abiParameterSignatures.append(("boxPtr", .pointer))
146+
builder.parameterBindings.append(
147+
"let closure = Unmanaged<\(raw: boxType)>.fromOpaque(boxPtr).takeUnretainedValue().closure"
177148
)
178-
179-
let emitCallAndLower: (CodeFragmentPrinter) -> Void = { printer in
180-
if signature.returnType == .void {
181-
printer.write(closureCallExpr.description)
182-
} else {
183-
printer.write("let result = \(closureCallExpr)")
184-
switch signature.returnType {
185-
case .swiftProtocol(let protocolName):
186-
printer.write(
187-
"return _bridgeJSUnwrapProtocolExportable(result, \"\(protocolName)\").bridgeJSLowerAsProtocolReturn()"
188-
)
189-
case .nullable(.swiftProtocol(let protocolName), _):
190-
printer.write("if let result {")
191-
printer.indent {
192-
printer.write(
193-
"_swift_js_return_optional_object(1, _bridgeJSUnwrapProtocolExportable(result, \"\(protocolName)\").bridgeJSLowerAsProtocolReturn())"
194-
)
195-
}
196-
printer.write("} else {")
197-
printer.indent {
198-
printer.write("_swift_js_return_optional_object(0, 0)")
199-
}
200-
printer.write("}")
201-
default:
202-
printer.write("return result.bridgeJSLowerReturn()")
203-
}
204-
}
205-
}
206-
207-
let emitAsyncCallAndLower: (CodeFragmentPrinter) -> Void = { printer in
208-
printer.write("let closure = Unmanaged<\(boxType)>.fromOpaque(boxPtr).takeUnretainedValue().closure")
209-
let resolveType = signature.returnType
210-
let resolveName = "Promise_resolve_\(resolveType.mangleTypeName)"
211-
let rejectName = "Promise_reject"
212-
let closureHead: String
213-
if signature.isThrows {
214-
let returnSpelling = resolveType == .void ? "" : " -> \(resolveType.closureSwiftType)"
215-
closureHead = " () async throws(JSException)\(returnSpelling) in"
216-
} else {
217-
closureHead = ""
218-
}
219-
printer.write("return _bjs_makePromise(resolve: \(resolveName), reject: \(rejectName)) {\(closureHead)")
220-
printer.indent {
221-
if resolveType == .void {
222-
printer.write(asyncClosureCallExpr.description)
223-
} else {
224-
printer.write("return \(asyncClosureCallExpr)")
225-
}
226-
}
227-
printer.write("}")
228-
}
229-
230-
let catchPlaceholderStmt = abiReturnWasmType?.swiftReturnPlaceholderStmt
231-
232-
// Build function declaration using helper
233-
let funcDecl = SwiftCodePattern.buildExposedFunctionDecl(
234-
abiName: abiName,
235-
signature: funcSignature
236-
) { printer in
237-
if signature.isAsync {
238-
emitAsyncCallAndLower(printer)
239-
} else if signature.isThrows {
240-
printer.write(
241-
"let closure = Unmanaged<\(boxType)>.fromOpaque(boxPtr).takeUnretainedValue().closure"
242-
)
243-
printer.write("do {")
244-
printer.indent {
245-
emitCallAndLower(printer)
246-
}
247-
printer.write("} catch let error {")
248-
printer.indent {
249-
printer.write("if let error = error.thrownValue.object {")
250-
printer.indent {
251-
printer.write("withExtendedLifetime(error) {")
252-
printer.indent {
253-
printer.write("_swift_js_throw(Int32(bitPattern: $0.id))")
254-
}
255-
printer.write("}")
256-
}
257-
printer.write("} else {")
258-
printer.indent {
259-
printer.write("let jsError = JSError(message: error.description)")
260-
printer.write("withExtendedLifetime(jsError.jsObject) {")
261-
printer.indent {
262-
printer.write("_swift_js_throw(Int32(bitPattern: $0.id))")
263-
}
264-
printer.write("}")
265-
}
266-
printer.write("}")
267-
if let catchPlaceholderStmt {
268-
printer.write(catchPlaceholderStmt)
269-
}
270-
}
271-
printer.write("}")
272-
} else {
273-
printer.write(
274-
"let closure = Unmanaged<\(boxType)>.fromOpaque(boxPtr).takeUnretainedValue().closure"
275-
)
276-
emitCallAndLower(printer)
277-
}
149+
for (index, paramType) in signature.parameters.enumerated() {
150+
try builder.liftParameter(
151+
param: Parameter(label: nil, name: "param\(index)", type: paramType)
152+
)
278153
}
279-
280-
return DeclSyntax(funcDecl)
154+
builder.call(name: "closure", returnType: signature.returnType)
155+
try builder.lowerReturnValue(returnType: signature.returnType)
156+
return builder.render(abiName: abiName)
281157
}
282158

283159
public func renderSupport(for skeleton: BridgeJSSkeleton) throws -> String? {

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 13 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,7 @@ public class ExportSwift {
190190
/// helper. Set for every `async` thunk.
191191
var asyncResolveReturnType: BridgeType?
192192

193-
/// Stack-using parameter lifts hoisted ahead of the deferred async closure.
194-
var asyncHoistedBindings: [CodeBlockItemSyntax] = []
193+
var parameterBindings: [CodeBlockItemSyntax] = []
195194

196195
init(effects: Effects, returnType: BridgeType) throws {
197196
self.effects = effects
@@ -254,7 +253,10 @@ public class ExportSwift {
254253
)
255254
}
256255

257-
liftedParameterExprs.append(liftingExpr)
256+
let varName = "_tmp_\(param.name)"
257+
let binding: CodeBlockItemSyntax = "let \(raw: varName) = \(liftingExpr)"
258+
parameterBindings.append(binding)
259+
liftedParameterExprs.append(ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(varName))))
258260
for (name, type) in zip(argumentsToLift, liftingInfo.parameters.map { $0.type }) {
259261
abiParameterSignatures.append((name, type))
260262
}
@@ -310,7 +312,6 @@ public class ExportSwift {
310312
}
311313

312314
func call(name: String, returnType: BridgeType) {
313-
generateParameterLifting()
314315
let item = renderCallStatement(callee: "\(raw: name)", returnType: returnType)
315316
append(item)
316317
}
@@ -326,48 +327,13 @@ public class ExportSwift {
326327

327328
func callMethod(methodName: String, returnType: BridgeType) {
328329
let (_, selfExpr) = removeFirstLiftedParameter()
329-
generateParameterLifting()
330330
let item = renderCallStatement(
331331
callee: "\(raw: selfExpr).\(raw: methodName)",
332332
returnType: returnType
333333
)
334334
append(item)
335335
}
336336

337-
/// Generates intermediate variables for stack-using parameters if needed for LIFO compatibility
338-
private func generateParameterLifting() {
339-
let stackParamIndices = parameters.enumerated().compactMap { index, param -> Int? in
340-
param.type.isStackUsingParameter ? index : nil
341-
}
342-
343-
if effects.isAsync {
344-
// Drain stack parameters before the deferred `Task` or the shared stack is corrupted.
345-
for index in stackParamIndices.reversed() {
346-
let param = parameters[index]
347-
let expr = liftedParameterExprs[index]
348-
let varName = "_tmp_\(param.name)"
349-
var binding: CodeBlockItemSyntax = "let \(raw: varName) = \(expr)"
350-
if !asyncHoistedBindings.isEmpty {
351-
binding = binding.with(\.leadingTrivia, .newline)
352-
}
353-
asyncHoistedBindings.append(binding)
354-
liftedParameterExprs[index] = ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(varName)))
355-
}
356-
return
357-
}
358-
359-
guard stackParamIndices.count > 1 else { return }
360-
361-
for index in stackParamIndices.reversed() {
362-
let param = parameters[index]
363-
let expr = liftedParameterExprs[index]
364-
let varName = "_tmp_\(param.name)"
365-
366-
append("let \(raw: varName) = \(expr)")
367-
liftedParameterExprs[index] = ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(varName)))
368-
}
369-
}
370-
371337
func callPropertyGetter(propertyName: String, returnType: BridgeType) {
372338
let (_, selfExpr) = removeFirstLiftedParameter()
373339
if returnType == .void {
@@ -456,10 +422,11 @@ public class ExportSwift {
456422
/// so the body must also read the captured value.
457423
/// See: https://github.com/swiftlang/swift/issues/89320
458424
private var asyncThrowsBodyForcesCapture: Bool {
459-
effects.isThrows && abiParameterSignatures.isEmpty && asyncHoistedBindings.isEmpty
425+
effects.isThrows && parameterBindings.isEmpty
460426
}
461427

462428
func render(abiName: String) -> DeclSyntax {
429+
var bindings = Array(parameterBindings.reversed())
463430
let body: CodeBlockItemListSyntax
464431
if effects.isAsync, let resolveType = asyncResolveReturnType {
465432
let resolveName = "Promise_resolve_\(resolveType.mangleTypeName)"
@@ -468,17 +435,15 @@ public class ExportSwift {
468435
returnSpelling: resolveType.swiftType,
469436
forcesCapture: forcesCapture
470437
)
471-
var hoistedBindings = asyncHoistedBindings
472438
var bodyItems = self.body
473439
if forcesCapture {
474-
hoistedBindings.append("let __bjs_capture = 0")
440+
bindings.append("let __bjs_capture = 0")
475441
if !bodyItems.isEmpty {
476442
bodyItems[0] = bodyItems[0].with(\.leadingTrivia, .newline)
477443
}
478444
bodyItems.insert("_ = __bjs_capture", at: 0)
479445
}
480446
body = """
481-
\(CodeBlockItemListSyntax(hoistedBindings))
482447
return _bjs_makePromise(resolve: \(raw: resolveName), reject: Promise_reject) {\(raw: closureHead)
483448
\(CodeBlockItemListSyntax(bodyItems))
484449
}
@@ -504,6 +469,10 @@ public class ExportSwift {
504469
} else {
505470
body = CodeBlockItemListSyntax(self.body)
506471
}
472+
let preparedBody: CodeBlockItemListSyntax = """
473+
\(CodeBlockItemListSyntax(bindings.map { $0.with(\.leadingTrivia, .newline) }))
474+
\(body)
475+
"""
507476
// Build function signature using SwiftSignatureBuilder
508477
let signature = SwiftSignatureBuilder.buildABIFunctionSignature(
509478
abiParameters: abiParameterSignatures,
@@ -515,7 +484,7 @@ public class ExportSwift {
515484
abiName: abiName,
516485
signature: signature
517486
) { printer in
518-
printer.write(multilineString: body.description)
487+
printer.write(multilineString: preparedBody.description)
519488
}
520489

521490
return DeclSyntax(funcDecl)
@@ -1744,19 +1713,6 @@ extension BridgeType {
17441713
return false
17451714
}
17461715

1747-
var isStackUsingParameter: Bool {
1748-
switch self {
1749-
case .swiftStruct, .array, .dictionary, .associatedValueEnum:
1750-
return true
1751-
case .nullable(let wrapped, _):
1752-
return wrapped.isStackUsingParameter
1753-
case .alias(_, let underlying):
1754-
return underlying.isStackUsingParameter
1755-
default:
1756-
return false
1757-
}
1758-
}
1759-
17601716
struct LiftingIntrinsicInfo: Sendable {
17611717
let parameters: [(name: String, type: WasmCoreType)]
17621718

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ public struct ImportTS {
339339
body = CodeFragmentPrinter()
340340

341341
let tryKeyword = effects.isThrows ? "try" : "try!"
342-
let rejectFactory = "makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>($0) }"
342+
let rejectFactory = "makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }"
343343
if returnType == .void {
344344
let resolveFactory = "makeResolveClosure: { JSTypedClosure<() -> Void>($0) }"
345345
body.write(
@@ -348,7 +348,7 @@ public struct ImportTS {
348348
} else {
349349
let resolveSwiftType = returnType.closureSwiftType
350350
let resolveFactory =
351-
"makeResolveClosure: { JSTypedClosure<(sending \(resolveSwiftType)) -> Void>($0) }"
351+
"makeResolveClosure: { JSTypedClosure<(sending \(resolveSwiftType)) -> Void>.sending($0) }"
352352
body.write(
353353
"let resolved = \(tryKeyword) await _bjs_awaitPromise(\(resolveFactory), \(rejectFactory)) { resolveRef, rejectRef in"
354354
)

0 commit comments

Comments
 (0)