@@ -135,6 +135,7 @@ public struct BridgeJSLink {
135135 var importObjectBuilders : [ ImportObjectBuilder ] = [ ]
136136 var enumStaticAssignments : [ String ] = [ ]
137137 var needsImportsObject : Bool = false
138+ var hasAsyncImports : Bool = false
138139 }
139140
140141 private func collectLinkData( ) throws -> LinkData {
@@ -237,12 +238,19 @@ public struct BridgeJSLink {
237238 if function. from == nil {
238239 data. needsImportsObject = true
239240 }
241+ if function. effects. isAsync {
242+ data. hasAsyncImports = true
243+ }
240244 try renderImportedFunction ( importObjectBuilder: importObjectBuilder, function: function)
241245 }
242246 for type in fileSkeleton. types {
243247 if type. constructor != nil , type. from == nil {
244248 data. needsImportsObject = true
245249 }
250+ // Check for async methods in imported types
251+ for method in type. methods where method. effects. isAsync {
252+ data. hasAsyncImports = true
253+ }
246254 try renderImportedType ( importObjectBuilder: importObjectBuilder, type: type)
247255 }
248256 }
@@ -314,6 +322,85 @@ public struct BridgeJSLink {
314322 ]
315323 }
316324
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+
317404 private func generateAddImports( needsImportsObject: Bool ) throws -> CodeFragmentPrinter {
318405 let printer = CodeFragmentPrinter ( )
319406 let allStructs = skeletons. compactMap { $0. exported? . structs } . flatMap { $0 }
@@ -970,6 +1057,11 @@ public struct BridgeJSLink {
9701057 try printer. indent {
9711058 printer. write ( lines: generateVariableDeclarations ( ) )
9721059
1060+ // Generate Promise continuation helpers when async imports exist
1061+ if data. hasAsyncImports {
1062+ printer. write ( lines: generatePromiseContinuationHelpers ( ) )
1063+ }
1064+
9731065 let bodyPrinter = CodeFragmentPrinter ( )
9741066 let allStructs = exportedSkeletons. flatMap { $0. structs }
9751067 for structDef in allStructs {
@@ -2232,6 +2324,46 @@ extension BridgeJSLink {
22322324 return printer. lines
22332325 }
22342326
2327+ /// Generates the call expression for an async import.
2328+ ///
2329+ /// Instead of lowering the return value, this assigns the result to `promise`
2330+ /// and attaches `.then`/`.catch` handlers that call the resolve/reject continuations.
2331+ func callAsync( name: String , fromObjectExpr: String ) {
2332+ let calleeExpr = Self . propertyAccessExpr ( objectExpr: fromObjectExpr, propertyName: name)
2333+ let callExpr = " \( calleeExpr) ( \( parameterForwardings. joined ( separator: " , " ) ) ) "
2334+ 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 ( " ); " )
2341+ }
2342+
2343+ /// Renders an async import function with continuation-pointer pattern.
2344+ ///
2345+ /// The generated function takes `continuationPtr` as the first parameter,
2346+ /// wraps the import call in try/catch, attaches Promise handlers, and
2347+ /// calls reject continuation on synchronous errors.
2348+ func renderAsyncFunction( name: String ? ) -> [ String ] {
2349+ let printer = CodeFragmentPrinter ( )
2350+ let allParams = [ " continuationPtr " ] + parameterNames
2351+ printer. write ( " function \( name. map { " \( $0) " } ?? " " ) ( \( allParams. joined ( separator: " , " ) ) ) { " )
2352+ printer. indent {
2353+ printer. write ( " try { " )
2354+ printer. indent {
2355+ printer. write ( contentsOf: body)
2356+ }
2357+ printer. write ( " } catch (error) { " )
2358+ printer. indent {
2359+ printer. write ( " bjs_rejectPromiseContinuation(continuationPtr, error); " )
2360+ }
2361+ printer. write ( " } " )
2362+ }
2363+ printer. write ( " } " )
2364+ return printer. lines
2365+ }
2366+
22352367 func call( name: String , fromObjectExpr: String , returnType: BridgeType ) throws -> String ? {
22362368 let calleeExpr = Self . propertyAccessExpr ( objectExpr: fromObjectExpr, propertyName: name)
22372369 return try self . call ( calleeExpr: calleeExpr, returnType: returnType)
@@ -2285,6 +2417,20 @@ extension BridgeJSLink {
22852417 )
22862418 }
22872419
2420+ /// Generates an async method call with continuation-pointer pattern.
2421+ func callAsyncMethod( name: String ) {
2422+ let objectExpr = " \( JSGlueVariableScope . reservedSwift) .memory.getObject(self) "
2423+ let calleeExpr = Self . propertyAccessExpr ( objectExpr: objectExpr, propertyName: name)
2424+ let callExpr = " \( calleeExpr) ( \( parameterForwardings. joined ( separator: " , " ) ) ) "
2425+ 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 ( " ); " )
2432+ }
2433+
22882434 func callStaticMethod( on objectExpr: String , name: String , returnType: BridgeType ) throws -> String ? {
22892435 let calleeExpr = Self . propertyAccessExpr ( objectExpr: objectExpr, propertyName: name)
22902436 return try call (
@@ -3124,19 +3270,27 @@ extension BridgeJSLink {
31243270 }
31253271 let jsName = function. jsName ?? function. name
31263272 let importRootExpr = function. from == . global ? " globalThis " : " imports "
3127- // For async functions, the JS handler returns the Promise as a jsObject.
3128- // The Swift side handles awaiting and lifting the resolved value.
3129- let abiReturnType : BridgeType = function. effects. isAsync ? . jsObject( nil ) : function. returnType
3130- let returnExpr = try thunkBuilder. call (
3131- name: jsName,
3132- fromObjectExpr: importRootExpr,
3133- returnType: abiReturnType
3134- )
3135- let funcLines = thunkBuilder. renderFunction (
3136- name: function. abiName ( context: nil ) ,
3137- returnExpr: returnExpr,
3138- returnType: abiReturnType
3139- )
3273+
3274+ let funcLines : [ String ]
3275+ if function. effects. isAsync {
3276+ // For async functions, use the continuation-pointer pattern.
3277+ // The generated function takes continuationPtr as first param,
3278+ // calls the import, attaches .then/.catch on the returned Promise,
3279+ // and calls resolve/reject continuations.
3280+ thunkBuilder. callAsync ( name: jsName, fromObjectExpr: importRootExpr)
3281+ funcLines = thunkBuilder. renderAsyncFunction ( name: function. abiName ( context: nil ) )
3282+ } else {
3283+ let returnExpr = try thunkBuilder. call (
3284+ name: jsName,
3285+ fromObjectExpr: importRootExpr,
3286+ returnType: function. returnType
3287+ )
3288+ funcLines = thunkBuilder. renderFunction (
3289+ name: function. abiName ( context: nil ) ,
3290+ returnExpr: returnExpr,
3291+ returnType: function. returnType
3292+ )
3293+ }
31403294 if function. from == nil {
31413295 importObjectBuilder. appendDts (
31423296 [
@@ -3339,13 +3493,19 @@ extension BridgeJSLink {
33393493 for param in method. parameters {
33403494 try thunkBuilder. liftParameter ( param: param)
33413495 }
3342- let abiReturnType : BridgeType = method. effects. isAsync ? . jsObject( nil ) : method. returnType
3343- let returnExpr = try thunkBuilder. callMethod ( name: method. jsName ?? method. name, returnType: abiReturnType)
3344- let funcLines = thunkBuilder. renderFunction (
3345- name: method. abiName ( context: context) ,
3346- returnExpr: returnExpr,
3347- returnType: abiReturnType
3348- )
3496+
3497+ let funcLines : [ String ]
3498+ if method. effects. isAsync {
3499+ thunkBuilder. callAsyncMethod ( name: method. jsName ?? method. name)
3500+ funcLines = thunkBuilder. renderAsyncFunction ( name: method. abiName ( context: context) )
3501+ } else {
3502+ let returnExpr = try thunkBuilder. callMethod ( name: method. jsName ?? method. name, returnType: method. returnType)
3503+ funcLines = thunkBuilder. renderFunction (
3504+ name: method. abiName ( context: context) ,
3505+ returnExpr: returnExpr,
3506+ returnType: method. returnType
3507+ )
3508+ }
33493509 return ( funcLines, [ ] )
33503510 }
33513511
0 commit comments