Skip to content

Commit 78cebea

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

6 files changed

Lines changed: 62 additions & 237 deletions

File tree

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: 22 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,6 @@ public class ExportSwift {
179179

180180
class ExportedThunkBuilder {
181181
var body: [CodeBlockItemSyntax] = []
182-
var liftedParameterExprs: [ExprSyntax] = []
183182
var parameters: [Parameter] = []
184183
var abiParameterSignatures: [(name: String, type: WasmCoreType)] = []
185184
var abiReturnType: WasmCoreType?
@@ -190,8 +189,7 @@ public class ExportSwift {
190189
/// helper. Set for every `async` thunk.
191190
var asyncResolveReturnType: BridgeType?
192191

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

196194
init(effects: Effects, returnType: BridgeType) throws {
197195
self.effects = effects
@@ -254,7 +252,7 @@ public class ExportSwift {
254252
)
255253
}
256254

257-
liftedParameterExprs.append(liftingExpr)
255+
parameterBindings.insert("let \(raw: param.name) = \(liftingExpr)", at: 0)
258256
for (name, type) in zip(argumentsToLift, liftingInfo.parameters.map { $0.type }) {
259257
abiParameterSignatures.append((name, type))
260258
}
@@ -269,15 +267,16 @@ public class ExportSwift {
269267
}
270268
}
271269

272-
private func removeFirstLiftedParameter() -> (parameter: Parameter, expr: ExprSyntax) {
273-
let parameter = parameters.removeFirst()
274-
let expr = liftedParameterExprs.removeFirst()
275-
return (parameter, expr)
270+
private func removeFirstLiftedParameter() -> String {
271+
return parameters.removeFirst().name
276272
}
277273

278274
private func renderCallStatement(callee: ExprSyntax, returnType: BridgeType) -> CodeBlockItemSyntax {
279-
let labeledParams = zip(parameters, liftedParameterExprs).map { param, expr in
280-
LabeledExprSyntax(label: param.label, expression: expr)
275+
let labeledParams = parameters.map { param in
276+
LabeledExprSyntax(
277+
label: param.label,
278+
expression: DeclReferenceExprSyntax(baseName: .identifier(param.name))
279+
)
281280
}
282281
var callExpr: ExprSyntax =
283282
"\(raw: callee)(\(raw: labeledParams.map { $0.description }.joined(separator: ", ")))"
@@ -310,7 +309,6 @@ public class ExportSwift {
310309
}
311310

312311
func call(name: String, returnType: BridgeType) {
313-
generateParameterLifting()
314312
let item = renderCallStatement(callee: "\(raw: name)", returnType: returnType)
315313
append(item)
316314
}
@@ -325,51 +323,16 @@ public class ExportSwift {
325323
}
326324

327325
func callMethod(methodName: String, returnType: BridgeType) {
328-
let (_, selfExpr) = removeFirstLiftedParameter()
329-
generateParameterLifting()
326+
let selfExpr = removeFirstLiftedParameter()
330327
let item = renderCallStatement(
331328
callee: "\(raw: selfExpr).\(raw: methodName)",
332329
returnType: returnType
333330
)
334331
append(item)
335332
}
336333

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-
371334
func callPropertyGetter(propertyName: String, returnType: BridgeType) {
372-
let (_, selfExpr) = removeFirstLiftedParameter()
335+
let selfExpr = removeFirstLiftedParameter()
373336
if returnType == .void {
374337
append("\(raw: selfExpr).\(raw: propertyName)")
375338
} else {
@@ -379,13 +342,13 @@ public class ExportSwift {
379342
}
380343

381344
func callPropertySetter(propertyName: String) {
382-
let (_, selfExpr) = removeFirstLiftedParameter()
383-
let (_, newValueExpr) = removeFirstLiftedParameter()
345+
let selfExpr = removeFirstLiftedParameter()
346+
let newValueExpr = removeFirstLiftedParameter()
384347
append("\(raw: selfExpr).\(raw: propertyName) = \(raw: newValueExpr)")
385348
}
386349

387350
func callStaticPropertySetter(klassName: String, propertyName: String) {
388-
let (_, newValueExpr) = removeFirstLiftedParameter()
351+
let newValueExpr = removeFirstLiftedParameter()
389352
append("\(raw: klassName).\(raw: propertyName) = \(raw: newValueExpr)")
390353
}
391354

@@ -456,10 +419,11 @@ public class ExportSwift {
456419
/// so the body must also read the captured value.
457420
/// See: https://github.com/swiftlang/swift/issues/89320
458421
private var asyncThrowsBodyForcesCapture: Bool {
459-
effects.isThrows && abiParameterSignatures.isEmpty && asyncHoistedBindings.isEmpty
422+
effects.isThrows && parameterBindings.isEmpty
460423
}
461424

462425
func render(abiName: String) -> DeclSyntax {
426+
var bindings = parameterBindings
463427
let body: CodeBlockItemListSyntax
464428
if effects.isAsync, let resolveType = asyncResolveReturnType {
465429
let resolveName = "Promise_resolve_\(resolveType.mangleTypeName)"
@@ -468,17 +432,15 @@ public class ExportSwift {
468432
returnSpelling: resolveType.swiftType,
469433
forcesCapture: forcesCapture
470434
)
471-
var hoistedBindings = asyncHoistedBindings
472435
var bodyItems = self.body
473436
if forcesCapture {
474-
hoistedBindings.append("let __bjs_capture = 0")
437+
bindings.append("let __bjs_capture = 0")
475438
if !bodyItems.isEmpty {
476439
bodyItems[0] = bodyItems[0].with(\.leadingTrivia, .newline)
477440
}
478441
bodyItems.insert("_ = __bjs_capture", at: 0)
479442
}
480443
body = """
481-
\(CodeBlockItemListSyntax(hoistedBindings))
482444
return _bjs_makePromise(resolve: \(raw: resolveName), reject: Promise_reject) {\(raw: closureHead)
483445
\(CodeBlockItemListSyntax(bodyItems))
484446
}
@@ -504,6 +466,10 @@ public class ExportSwift {
504466
} else {
505467
body = CodeBlockItemListSyntax(self.body)
506468
}
469+
let preparedBody: CodeBlockItemListSyntax = """
470+
\(CodeBlockItemListSyntax(bindings.map { $0.with(\.leadingTrivia, .newline) }))
471+
\(body)
472+
"""
507473
// Build function signature using SwiftSignatureBuilder
508474
let signature = SwiftSignatureBuilder.buildABIFunctionSignature(
509475
abiParameters: abiParameterSignatures,
@@ -515,7 +481,7 @@ public class ExportSwift {
515481
abiName: abiName,
516482
signature: signature
517483
) { printer in
518-
printer.write(multilineString: body.description)
484+
printer.write(multilineString: preparedBody.description)
519485
}
520486

521487
return DeclSyntax(funcDecl)
@@ -1744,19 +1710,6 @@ extension BridgeType {
17441710
return false
17451711
}
17461712

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-
17601713
struct LiftingIntrinsicInfo: Sendable {
17611714
let parameters: [(name: String, type: WasmCoreType)]
17621715

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)