Skip to content

Commit 83eae16

Browse files
authored
Merge pull request #798 from swiftwasm/yt/fix-struct-closure-param
BridgeJS: Unify @js struct parameter lowering onto the stack ABI
2 parents e53155d + 0e11da5 commit 83eae16

13 files changed

Lines changed: 356 additions & 117 deletions

File tree

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -933,19 +933,10 @@ extension BridgeType {
933933
case .associatedValueEnum:
934934
return LoweringParameterInfo(loweredParameters: [("caseId", .i32)])
935935
case .swiftStruct:
936-
switch context {
937-
case .importTS:
938-
// Swift structs are bridged as JS objects (object IDs) in imported signatures.
939-
return LoweringParameterInfo(loweredParameters: [("objectId", .i32)])
940-
case .exportSwift:
941-
return LoweringParameterInfo(loweredParameters: [])
942-
}
936+
// `@JS struct` parameters always use the stack ABI (same as arrays/dictionaries).
937+
return LoweringParameterInfo(loweredParameters: [])
943938
case .namespaceEnum:
944939
throw BridgeJSCoreError("Namespace enums cannot be used as parameters")
945-
case .nullable(.swiftStruct, _) where context == .importTS:
946-
// Optional `@JS struct`s bridge through the stack (isSome discriminator + fields),
947-
// like optional arrays/dictionaries, rather than the non-optional object-id ABI.
948-
return LoweringParameterInfo(loweredParameters: [("isSome", .i32)])
949940
case .nullable(let wrappedType, _):
950941
let wrappedInfo = try wrappedType.loweringParameterInfo(context: context)
951942
var params = [("isSome", WasmCoreType.i32)]
@@ -1005,24 +996,16 @@ extension BridgeType {
1005996
case .associatedValueEnum:
1006997
return LiftingReturnInfo(valueToLift: .i32)
1007998
case .swiftStruct:
1008-
switch context {
1009-
case .importTS:
1010-
// Swift structs are bridged as JS objects (object IDs) in imported signatures.
1011-
return LiftingReturnInfo(valueToLift: .i32)
1012-
case .exportSwift:
1013-
return LiftingReturnInfo(valueToLift: nil)
1014-
}
999+
// `@JS struct` returns always use the stack ABI (same as arrays/dictionaries).
1000+
return LiftingReturnInfo(valueToLift: nil)
10151001
case .namespaceEnum:
10161002
throw BridgeJSCoreError("Namespace enums cannot be used as return values")
10171003
case .nullable(let wrappedType, _):
1018-
// jsObject and `@JS struct` use the stack ABI for optionals — the thunk returns
1019-
// void and the value (plus isSome discriminator) flows through the stacks.
1004+
// jsObject uses the stack ABI for optionals — the thunk returns void and the
1005+
// value (plus isSome discriminator) flows through the stacks.
10201006
if case .jsObject = wrappedType {
10211007
return LiftingReturnInfo(valueToLift: nil)
10221008
}
1023-
if case .swiftStruct = wrappedType, context == .importTS {
1024-
return LiftingReturnInfo(valueToLift: nil)
1025-
}
10261009
let wrappedInfo = try wrappedType.liftingReturnInfo(context: context)
10271010
return LiftingReturnInfo(valueToLift: wrappedInfo.valueToLift)
10281011
case .array, .dictionary:

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,23 +1437,18 @@ struct IntrinsicJSFragment: Sendable {
14371437
}
14381438
)
14391439
case .swiftStruct(let fullName):
1440-
switch context {
1441-
case .importTS:
1442-
return .jsObjectLiftRetainedObjectId
1443-
case .exportSwift:
1444-
let base = fullName.replacingOccurrences(of: ".", with: "_")
1445-
return IntrinsicJSFragment(
1446-
parameters: [],
1447-
printCode: { arguments, context in
1448-
let (scope, printer) = (context.scope, context.printer)
1449-
let resultVar = scope.variable("structValue")
1450-
printer.write(
1451-
"const \(resultVar) = \(JSGlueVariableScope.reservedStructHelpers).\(base).lift();"
1452-
)
1453-
return [resultVar]
1454-
}
1455-
)
1456-
}
1440+
let base = fullName.replacingOccurrences(of: ".", with: "_")
1441+
return IntrinsicJSFragment(
1442+
parameters: [],
1443+
printCode: { arguments, context in
1444+
let (scope, printer) = (context.scope, context.printer)
1445+
let resultVar = scope.variable("structValue")
1446+
printer.write(
1447+
"const \(resultVar) = \(JSGlueVariableScope.reservedStructHelpers).\(base).lift();"
1448+
)
1449+
return [resultVar]
1450+
}
1451+
)
14571452
case .closure:
14581453
return IntrinsicJSFragment(
14591454
parameters: ["funcRef"],
@@ -1497,12 +1492,7 @@ struct IntrinsicJSFragment: Sendable {
14971492
case .associatedValueEnum(let fullName):
14981493
return associatedValueLowerReturn(fullName: fullName)
14991494
case .swiftStruct(let fullName):
1500-
switch context {
1501-
case .importTS:
1502-
return .jsObjectLowerReturn
1503-
case .exportSwift:
1504-
return swiftStructLowerReturn(fullName: fullName)
1505-
}
1495+
return swiftStructLowerReturn(fullName: fullName)
15061496
case .closure:
15071497
return IntrinsicJSFragment(
15081498
parameters: ["value"],

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Async.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -507,20 +507,20 @@ func _$Promise_resolve_8JSObjectC(_ promise: JSObject, _ value: JSObject) throws
507507
508508
#if arch(wasm32)
509509
@_extern(wasm, module: "bjs", name: "promise_resolve_TestModule_10AsyncPointV")
510-
fileprivate func promise_resolve_TestModule_10AsyncPointV_extern(_ promise: Int32, _ value: Int32) -> Void
510+
fileprivate func promise_resolve_TestModule_10AsyncPointV_extern(_ promise: Int32) -> Void
511511
#else
512-
fileprivate func promise_resolve_TestModule_10AsyncPointV_extern(_ promise: Int32, _ value: Int32) -> Void {
512+
fileprivate func promise_resolve_TestModule_10AsyncPointV_extern(_ promise: Int32) -> Void {
513513
fatalError("Only available on WebAssembly")
514514
}
515515
#endif
516-
@inline(never) fileprivate func promise_resolve_TestModule_10AsyncPointV(_ promise: Int32, _ value: Int32) -> Void {
517-
return promise_resolve_TestModule_10AsyncPointV_extern(promise, value)
516+
@inline(never) fileprivate func promise_resolve_TestModule_10AsyncPointV(_ promise: Int32) -> Void {
517+
return promise_resolve_TestModule_10AsyncPointV_extern(promise)
518518
}
519519
520520
func _$Promise_resolve_10AsyncPointV(_ promise: JSObject, _ value: AsyncPoint) throws(JSException) -> Void {
521-
let valueObjectId = value.bridgeJSLowerParameter()
521+
let _ = value.bridgeJSLowerParameter()
522522
let promiseValue = promise.bridgeJSLowerParameter()
523-
promise_resolve_TestModule_10AsyncPointV(promiseValue, valueObjectId)
523+
promise_resolve_TestModule_10AsyncPointV(promiseValue)
524524
if let error = _swift_js_take_exception() { throw error }
525525
}
526526

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClosure.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2684,20 +2684,20 @@ func _$Promise_resolve_SS(_ promise: JSObject, _ value: String) throws(JSExcepti
26842684
26852685
#if arch(wasm32)
26862686
@_extern(wasm, module: "bjs", name: "promise_resolve_TestModule_6AnimalV")
2687-
fileprivate func promise_resolve_TestModule_6AnimalV_extern(_ promise: Int32, _ value: Int32) -> Void
2687+
fileprivate func promise_resolve_TestModule_6AnimalV_extern(_ promise: Int32) -> Void
26882688
#else
2689-
fileprivate func promise_resolve_TestModule_6AnimalV_extern(_ promise: Int32, _ value: Int32) -> Void {
2689+
fileprivate func promise_resolve_TestModule_6AnimalV_extern(_ promise: Int32) -> Void {
26902690
fatalError("Only available on WebAssembly")
26912691
}
26922692
#endif
2693-
@inline(never) fileprivate func promise_resolve_TestModule_6AnimalV(_ promise: Int32, _ value: Int32) -> Void {
2694-
return promise_resolve_TestModule_6AnimalV_extern(promise, value)
2693+
@inline(never) fileprivate func promise_resolve_TestModule_6AnimalV(_ promise: Int32) -> Void {
2694+
return promise_resolve_TestModule_6AnimalV_extern(promise)
26952695
}
26962696
26972697
func _$Promise_resolve_6AnimalV(_ promise: JSObject, _ value: Animal) throws(JSException) -> Void {
2698-
let valueObjectId = value.bridgeJSLowerParameter()
2698+
let _ = value.bridgeJSLowerParameter()
26992699
let promiseValue = promise.bridgeJSLowerParameter()
2700-
promise_resolve_TestModule_6AnimalV(promiseValue, valueObjectId)
2700+
promise_resolve_TestModule_6AnimalV(promiseValue)
27012701
if let error = _swift_js_take_exception() { throw error }
27022702
}
27032703

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftStructImports.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,25 +48,25 @@ fileprivate func _bjs_struct_lift_Point_extern() -> Int32 {
4848

4949
#if arch(wasm32)
5050
@_extern(wasm, module: "TestModule", name: "bjs_translate")
51-
fileprivate func bjs_translate_extern(_ point: Int32, _ dx: Int32, _ dy: Int32) -> Int32
51+
fileprivate func bjs_translate_extern(_ dx: Int32, _ dy: Int32) -> Void
5252
#else
53-
fileprivate func bjs_translate_extern(_ point: Int32, _ dx: Int32, _ dy: Int32) -> Int32 {
53+
fileprivate func bjs_translate_extern(_ dx: Int32, _ dy: Int32) -> Void {
5454
fatalError("Only available on WebAssembly")
5555
}
5656
#endif
57-
@inline(never) fileprivate func bjs_translate(_ point: Int32, _ dx: Int32, _ dy: Int32) -> Int32 {
58-
return bjs_translate_extern(point, dx, dy)
57+
@inline(never) fileprivate func bjs_translate(_ dx: Int32, _ dy: Int32) -> Void {
58+
return bjs_translate_extern(dx, dy)
5959
}
6060

6161
func _$translate(_ point: Point, _ dx: Int, _ dy: Int) throws(JSException) -> Point {
6262
let dyValue = dy.bridgeJSLowerParameter()
6363
let dxValue = dx.bridgeJSLowerParameter()
64-
let pointObjectId = point.bridgeJSLowerParameter()
65-
let ret = bjs_translate(pointObjectId, dxValue, dyValue)
64+
let _ = point.bridgeJSLowerParameter()
65+
bjs_translate(dxValue, dyValue)
6666
if let error = _swift_js_take_exception() {
6767
throw error
6868
}
69-
return Point.bridgeJSLiftReturn(ret)
69+
return Point.bridgeJSLiftReturn()
7070
}
7171

7272
#if arch(wasm32)

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -280,11 +280,10 @@ export async function createInstantiator(options, swift) {
280280
setException(error);
281281
}
282282
}
283-
bjs["promise_resolve_TestModule_10AsyncPointV"] = function(promise, value) {
283+
bjs["promise_resolve_TestModule_10AsyncPointV"] = function(promise) {
284284
try {
285-
const value1 = swift.memory.getObject(value);
286-
swift.memory.release(value);
287-
swift.memory.getObject(promise)[__bjs_promiseSettlers].resolve(value1);
285+
const structValue = structHelpers.AsyncPoint.lift();
286+
swift.memory.getObject(promise)[__bjs_promiseSettlers].resolve(structValue);
288287
} catch (error) {
289288
setException(error);
290289
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -345,11 +345,10 @@ export async function createInstantiator(options, swift) {
345345
setException(error);
346346
}
347347
}
348-
bjs["promise_resolve_TestModule_6AnimalV"] = function(promise, value) {
348+
bjs["promise_resolve_TestModule_6AnimalV"] = function(promise) {
349349
try {
350-
const value1 = swift.memory.getObject(value);
351-
swift.memory.release(value);
352-
swift.memory.getObject(promise)[__bjs_promiseSettlers].resolve(value1);
350+
const structValue = structHelpers.Animal.lift();
351+
swift.memory.getObject(promise)[__bjs_promiseSettlers].resolve(structValue);
353352
} catch (error) {
354353
setException(error);
355354
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStructImports.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -223,12 +223,11 @@ export async function createInstantiator(options, swift) {
223223
}
224224
bjs["swift_js_closure_unregister"] = function(funcRef) {}
225225
const TestModule = importObject["TestModule"] = importObject["TestModule"] || {};
226-
TestModule["bjs_translate"] = function bjs_translate(point, dx, dy) {
226+
TestModule["bjs_translate"] = function bjs_translate(dx, dy) {
227227
try {
228-
const value = swift.memory.getObject(point);
229-
swift.memory.release(point);
230-
let ret = imports.translate(value, dx, dy);
231-
return swift.memory.retain(ret);
228+
const structValue = structHelpers.Point.lift();
229+
let ret = imports.translate(structValue, dx, dy);
230+
structHelpers.Point.lower(ret);
232231
} catch (error) {
233232
setException(error);
234233
}

Sources/JavaScriptKit/BridgeJSIntrinsics.swift

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,13 +1133,12 @@ where StackLiftResult == Self {
11331133
}
11341134

11351135
extension _BridgedSwiftStruct {
1136-
@_spi(BridgeJS) public consuming func bridgeJSLowerParameter() -> Int32 {
1137-
return toJSObject().bridgeJSLowerReturn()
1138-
}
1139-
1140-
@_spi(BridgeJS) public static func bridgeJSLiftReturn(_ objectId: Int32) -> Self {
1141-
let jsObject = JSObject.bridgeJSLiftReturn(objectId)
1142-
return Self(unsafelyCopying: jsObject)
1136+
/// Lower a struct parameter onto the shared stacks for the peer to `lift()`.
1137+
///
1138+
/// Same convention as arrays/dictionaries. Use ``toJSObject()`` when a real JS object
1139+
/// representation is needed (e.g. `init(unsafelyCopying:)` round-trips).
1140+
@_spi(BridgeJS) public consuming func bridgeJSLowerParameter() {
1141+
bridgeJSStackPush()
11431142
}
11441143

11451144
@_spi(BridgeJS) public static func bridgeJSLiftReturn() -> Self {

Tests/BridgeJSRuntimeTests/ExportAPITests.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,6 +1339,15 @@ enum GraphOperations {
13391339
let noneStr = none.map { "(\($0.dx),\($0.dy))" } ?? "nil"
13401340
return "\(someStr) | \(noneStr)"
13411341
}
1342+
1343+
/// Swift→JS callback with a struct parameter (ExportSwift stack ABI).
1344+
@JS func observeVector(_ callback: (Vector2D) -> Void) {
1345+
callback(Vector2D(dx: 1.5, dy: 2.5))
1346+
}
1347+
1348+
@JS func mapVector(_ vector: Vector2D, _ callback: (Vector2D) -> Vector2D) -> Vector2D {
1349+
return callback(vector)
1350+
}
13421351
}
13431352

13441353
@JS enum NestedStructGroupA {

0 commit comments

Comments
 (0)