Skip to content

Commit e55519b

Browse files
BridgeJS now supports JSValue as a parameter/return type for imported calls and exported protocol/class interfaces.
- Added `BridgeType.jsValue` plumbing and Swift type recognition (`Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift`, `Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift`). - Implemented JSValue ABI as `(kind: i32, payload1: i32, payload2: f64)` for parameters + side-channel payload getters for imported returns (`Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift`, `Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift`). - Added `JSValue` BridgeJS intrinsics (`Sources/JavaScriptKit/BridgeJSIntrinsics.swift`) and export-side stack lowering/lifting (`Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift`). - Updated protocol test input and snapshots (`Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Protocol.swift`, snapshots under `Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/`). Validation: - Ran `npm ci` (to satisfy TS2Swift’s `typescript` dependency) and `swift test --package-path ./Plugins/BridgeJS` (all passing).
1 parent 8784a38 commit e55519b

File tree

56 files changed

+961
-96
lines changed

Some content is hidden

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

56 files changed

+961
-96
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,9 @@ struct StackCodegen {
808808
return "Float.bridgeJSLiftParameter(_swift_js_pop_param_f32())"
809809
case .double:
810810
return "Double.bridgeJSLiftParameter(_swift_js_pop_param_f64())"
811+
case .jsValue:
812+
return
813+
"JSValue.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32(), _swift_js_pop_param_f64())"
811814
case .jsObject:
812815
return "JSObject.bridgeJSLiftParameter(_swift_js_pop_param_int32())"
813816
case .swiftHeapObject(let className):
@@ -953,6 +956,8 @@ struct StackCodegen {
953956
return ["_swift_js_push_f32(\(raw: accessor))"]
954957
case .double:
955958
return ["_swift_js_push_f64(\(raw: accessor))"]
959+
case .jsValue:
960+
return ["\(raw: accessor).bridgeJSLowerReturn()"]
956961
case .jsObject:
957962
return ["_swift_js_push_int(\(raw: accessor).bridgeJSLowerReturn())"]
958963
case .swiftHeapObject:
@@ -1646,6 +1651,7 @@ extension BridgeType {
16461651
case .float: return "Float"
16471652
case .double: return "Double"
16481653
case .string: return "String"
1654+
case .jsValue: return "JSValue"
16491655
case .jsObject(nil): return "JSObject"
16501656
case .jsObject(let name?): return name
16511657
case .swiftHeapObject(let name): return name
@@ -1674,6 +1680,7 @@ extension BridgeType {
16741680
static let float = LiftingIntrinsicInfo(parameters: [("value", .f32)])
16751681
static let double = LiftingIntrinsicInfo(parameters: [("value", .f64)])
16761682
static let string = LiftingIntrinsicInfo(parameters: [("bytes", .i32), ("length", .i32)])
1683+
static let jsValue = LiftingIntrinsicInfo(parameters: [("kind", .i32), ("payload1", .i32), ("payload2", .f64)])
16771684
static let jsObject = LiftingIntrinsicInfo(parameters: [("value", .i32)])
16781685
static let swiftHeapObject = LiftingIntrinsicInfo(parameters: [("value", .pointer)])
16791686
static let unsafePointer = LiftingIntrinsicInfo(parameters: [("pointer", .pointer)])
@@ -1691,6 +1698,7 @@ extension BridgeType {
16911698
case .float: return .float
16921699
case .double: return .double
16931700
case .string: return .string
1701+
case .jsValue: return .jsValue
16941702
case .jsObject: return .jsObject
16951703
case .swiftHeapObject: return .swiftHeapObject
16961704
case .unsafePointer: return .unsafePointer
@@ -1724,6 +1732,7 @@ extension BridgeType {
17241732
static let float = LoweringIntrinsicInfo(returnType: .f32)
17251733
static let double = LoweringIntrinsicInfo(returnType: .f64)
17261734
static let string = LoweringIntrinsicInfo(returnType: nil)
1735+
static let jsValue = LoweringIntrinsicInfo(returnType: nil)
17271736
static let jsObject = LoweringIntrinsicInfo(returnType: .i32)
17281737
static let swiftHeapObject = LoweringIntrinsicInfo(returnType: .pointer)
17291738
static let unsafePointer = LoweringIntrinsicInfo(returnType: .pointer)
@@ -1743,6 +1752,7 @@ extension BridgeType {
17431752
case .float: return .float
17441753
case .double: return .double
17451754
case .string: return .string
1755+
case .jsValue: return .jsValue
17461756
case .jsObject: return .jsObject
17471757
case .swiftHeapObject: return .swiftHeapObject
17481758
case .unsafePointer: return .unsafePointer

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,13 @@ extension BridgeType {
859859
static let float = LoweringParameterInfo(loweredParameters: [("value", .f32)])
860860
static let double = LoweringParameterInfo(loweredParameters: [("value", .f64)])
861861
static let string = LoweringParameterInfo(loweredParameters: [("value", .i32)])
862+
static let jsValue = LoweringParameterInfo(
863+
loweredParameters: [
864+
("kind", .i32),
865+
("payload1", .i32),
866+
("payload2", .f64),
867+
]
868+
)
862869
static let jsObject = LoweringParameterInfo(loweredParameters: [("value", .i32)])
863870
static let void = LoweringParameterInfo(loweredParameters: [])
864871
}
@@ -870,6 +877,7 @@ extension BridgeType {
870877
case .float: return .float
871878
case .double: return .double
872879
case .string: return .string
880+
case .jsValue: return .jsValue
873881
case .jsObject: return .jsObject
874882
case .void: return .void
875883
case .closure:
@@ -951,6 +959,7 @@ extension BridgeType {
951959
static let float = LiftingReturnInfo(valueToLift: .f32)
952960
static let double = LiftingReturnInfo(valueToLift: .f64)
953961
static let string = LiftingReturnInfo(valueToLift: .i32)
962+
static let jsValue = LiftingReturnInfo(valueToLift: .i32)
954963
static let jsObject = LiftingReturnInfo(valueToLift: .i32)
955964
static let void = LiftingReturnInfo(valueToLift: nil)
956965
}
@@ -964,6 +973,7 @@ extension BridgeType {
964973
case .float: return .float
965974
case .double: return .double
966975
case .string: return .string
976+
case .jsValue: return .jsValue
967977
case .jsObject: return .jsObject
968978
case .void: return .void
969979
case .closure:

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ public struct BridgeJSLink {
245245
"let \(JSGlueVariableScope.reservedStorageToReturnOptionalFloat);",
246246
"let \(JSGlueVariableScope.reservedStorageToReturnOptionalDouble);",
247247
"let \(JSGlueVariableScope.reservedStorageToReturnOptionalHeapObject);",
248+
"let \(JSGlueVariableScope.reservedStorageToReturnJSValuePayload1);",
249+
"let \(JSGlueVariableScope.reservedStorageToReturnJSValuePayload2);",
248250
"let \(JSGlueVariableScope.reservedTmpRetTag);",
249251
"let \(JSGlueVariableScope.reservedTmpRetStrings) = [];",
250252
"let \(JSGlueVariableScope.reservedTmpRetInts) = [];",
@@ -657,6 +659,20 @@ public struct BridgeJSLink {
657659
printer.write("return pointer || 0;")
658660
}
659661
printer.write("}")
662+
printer.write("bjs[\"swift_js_get_jsvalue_payload1\"] = function() {")
663+
printer.indent {
664+
printer.write("const payload1 = \(JSGlueVariableScope.reservedStorageToReturnJSValuePayload1);")
665+
printer.write("\(JSGlueVariableScope.reservedStorageToReturnJSValuePayload1) = 0;")
666+
printer.write("return payload1;")
667+
}
668+
printer.write("}")
669+
printer.write("bjs[\"swift_js_get_jsvalue_payload2\"] = function() {")
670+
printer.indent {
671+
printer.write("const payload2 = \(JSGlueVariableScope.reservedStorageToReturnJSValuePayload2);")
672+
printer.write("\(JSGlueVariableScope.reservedStorageToReturnJSValuePayload2) = 0.0;")
673+
printer.write("return payload2;")
674+
}
675+
printer.write("}")
660676

661677
for unified in skeletons {
662678
let moduleName = unified.moduleName
@@ -3409,6 +3425,8 @@ extension BridgeType {
34093425
return "number"
34103426
case .bool:
34113427
return "boolean"
3428+
case .jsValue:
3429+
return "any"
34123430
case .jsObject(let name):
34133431
return name ?? "any"
34143432
case .swiftHeapObject(let name):

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ final class JSGlueVariableScope {
1818
static let reservedStorageToReturnOptionalFloat = "tmpRetOptionalFloat"
1919
static let reservedStorageToReturnOptionalDouble = "tmpRetOptionalDouble"
2020
static let reservedStorageToReturnOptionalHeapObject = "tmpRetOptionalHeapObject"
21+
static let reservedStorageToReturnJSValuePayload1 = "tmpRetJSValuePayload1"
22+
static let reservedStorageToReturnJSValuePayload2 = "tmpRetJSValuePayload2"
2123
static let reservedTextEncoder = "textEncoder"
2224
static let reservedTextDecoder = "textDecoder"
2325
static let reservedTmpRetTag = "tmpRetTag"
@@ -49,6 +51,8 @@ final class JSGlueVariableScope {
4951
reservedStorageToReturnOptionalFloat,
5052
reservedStorageToReturnOptionalDouble,
5153
reservedStorageToReturnOptionalHeapObject,
54+
reservedStorageToReturnJSValuePayload1,
55+
reservedStorageToReturnJSValuePayload2,
5256
reservedTextEncoder,
5357
reservedTextDecoder,
5458
reservedTmpRetTag,
@@ -233,6 +237,143 @@ struct IntrinsicJSFragment: Sendable {
233237
}
234238
)
235239

240+
private static func jsValueKindExpression(for valueExpr: String) -> String {
241+
// JavaScriptValueKind:
242+
// 0: boolean, 1: string, 2: number, 3: object, 4: null, 5: undefined, 7: symbol, 8: bigint
243+
"(\(valueExpr) === null) ? 4 : (\(valueExpr) === undefined) ? 5 : (typeof \(valueExpr) === \"boolean\") ? 0 : (typeof \(valueExpr) === \"string\") ? 1 : (typeof \(valueExpr) === \"number\") ? 2 : (typeof \(valueExpr) === \"symbol\") ? 7 : (typeof \(valueExpr) === \"bigint\") ? 8 : 3"
244+
}
245+
246+
/// Lowers a JS value into the (kind, payload1, payload2) triple used by BridgeJS for `JSValue`.
247+
///
248+
/// - kind: i32 JavaScriptValueKind tag
249+
/// - payload1: i32 (boolean as 0/1, or retained SwiftRuntimeHeap id for string/object/symbol/bigint)
250+
/// - payload2: f64 (number payload)
251+
static let jsValueLowerParameter = IntrinsicJSFragment(
252+
parameters: ["value"],
253+
printCode: { arguments, scope, printer, cleanupCode in
254+
let value = arguments[0]
255+
let kindVar = scope.variable("kind")
256+
let payload1Var = scope.variable("payload1")
257+
let payload2Var = scope.variable("payload2")
258+
259+
printer.write("const \(kindVar) = \(jsValueKindExpression(for: value));")
260+
printer.write("let \(payload1Var) = 0;")
261+
printer.write("let \(payload2Var) = 0.0;")
262+
263+
printer.write("switch (\(kindVar) | 0) {")
264+
printer.indent {
265+
printer.write("case 0: { \(payload1Var) = \(value) ? 1 : 0; break; }")
266+
printer.write("case 2: { \(payload2Var) = +\(value); break; }")
267+
printer.write(
268+
"case 1: case 3: case 7: case 8: { \(payload1Var) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value)); break; }"
269+
)
270+
printer.write("case 4: case 5: { break; }")
271+
printer.write("default: { throw new Error(\"Unknown JSValue kind: \" + String(\(kindVar))); }")
272+
}
273+
printer.write("}")
274+
275+
return [kindVar, payload1Var, payload2Var]
276+
}
277+
)
278+
279+
/// Lifts a Swift-returned `JSValue` from the (tag + tmpRet payload stacks) side channel.
280+
static let jsValueLiftReturn = IntrinsicJSFragment(
281+
parameters: [],
282+
printCode: { _, scope, printer, cleanupCode in
283+
let kindVar = scope.variable("kind")
284+
let resultVar = scope.variable("ret")
285+
printer.write("const \(kindVar) = (\(JSGlueVariableScope.reservedTmpRetTag) | 0);")
286+
printer.write("let \(resultVar);")
287+
printer.write("switch (\(kindVar)) {")
288+
printer.indent {
289+
printer.write("case 0: {")
290+
printer.indent {
291+
printer.write("const b = \(JSGlueVariableScope.reservedTmpRetInts).pop();")
292+
printer.write("\(resultVar) = (b !== 0);")
293+
}
294+
printer.write("break; }")
295+
printer.write("case 2: {")
296+
printer.indent {
297+
printer.write("\(resultVar) = \(JSGlueVariableScope.reservedTmpRetF64s).pop();")
298+
}
299+
printer.write("break; }")
300+
printer.write("case 4: { \(resultVar) = null; break; }")
301+
printer.write("case 5: { \(resultVar) = undefined; break; }")
302+
printer.write("case 1: case 3: case 7: case 8: {")
303+
printer.indent {
304+
printer.write("const objectId = \(JSGlueVariableScope.reservedTmpRetInts).pop();")
305+
printer.write("\(resultVar) = \(JSGlueVariableScope.reservedSwift).memory.getObject(objectId);")
306+
printer.write("\(JSGlueVariableScope.reservedSwift).memory.release(objectId);")
307+
}
308+
printer.write("break; }")
309+
printer.write(
310+
"default: { throw new Error(\"Unknown JSValue kind returned from Swift: \" + String(\(kindVar))); }"
311+
)
312+
}
313+
printer.write("}")
314+
return [resultVar]
315+
}
316+
)
317+
318+
/// Lifts a Swift-sent `JSValue` parameter from the (kind, payload1, payload2) triple.
319+
static let jsValueLiftParameter = IntrinsicJSFragment(
320+
parameters: ["kind", "payload1", "payload2"],
321+
printCode: { arguments, scope, printer, cleanupCode in
322+
let kind = arguments[0]
323+
let payload1 = arguments[1]
324+
let payload2 = arguments[2]
325+
let resultVar = scope.variable("value")
326+
327+
printer.write("let \(resultVar);")
328+
printer.write("switch (\(kind) | 0) {")
329+
printer.indent {
330+
printer.write("case 0: { \(resultVar) = (\(payload1) !== 0); break; }")
331+
printer.write("case 2: { \(resultVar) = \(payload2); break; }")
332+
printer.write(
333+
"case 1: case 3: case 7: case 8: { \(resultVar) = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(payload1)); break; }"
334+
)
335+
printer.write("case 4: { \(resultVar) = null; break; }")
336+
printer.write("case 5: { \(resultVar) = undefined; break; }")
337+
printer.write(
338+
"default: { throw new Error(\"Unknown JSValue kind passed from Swift: \" + String(\(kind))); }"
339+
)
340+
}
341+
printer.write("}")
342+
return [resultVar]
343+
}
344+
)
345+
346+
/// Lowers a JS `JSValue` return into an i32 kind, storing payloads in side-channel vars.
347+
static let jsValueLowerReturn = IntrinsicJSFragment(
348+
parameters: ["value"],
349+
printCode: { arguments, scope, printer, cleanupCode in
350+
let value = arguments[0]
351+
let kindVar = scope.variable("kind")
352+
let payload1Var = scope.variable("payload1")
353+
let payload2Var = scope.variable("payload2")
354+
355+
printer.write("const \(kindVar) = \(jsValueKindExpression(for: value));")
356+
printer.write("let \(payload1Var) = 0;")
357+
printer.write("let \(payload2Var) = 0.0;")
358+
359+
printer.write("switch (\(kindVar) | 0) {")
360+
printer.indent {
361+
printer.write("case 0: { \(payload1Var) = \(value) ? 1 : 0; break; }")
362+
printer.write("case 2: { \(payload2Var) = +\(value); break; }")
363+
printer.write(
364+
"case 1: case 3: case 7: case 8: { \(payload1Var) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value)); break; }"
365+
)
366+
printer.write("case 4: case 5: { break; }")
367+
printer.write("default: { throw new Error(\"Unknown JSValue kind: \" + String(\(kindVar))); }")
368+
}
369+
printer.write("}")
370+
371+
printer.write("\(JSGlueVariableScope.reservedStorageToReturnJSValuePayload1) = \(payload1Var);")
372+
printer.write("\(JSGlueVariableScope.reservedStorageToReturnJSValuePayload2) = \(payload2Var);")
373+
return [kindVar]
374+
}
375+
)
376+
236377
static let swiftHeapObjectLowerParameter = IntrinsicJSFragment(
237378
parameters: ["value"],
238379
printCode: { arguments, scope, printer, cleanupCode in
@@ -1372,6 +1513,7 @@ struct IntrinsicJSFragment: Sendable {
13721513
switch type {
13731514
case .int, .float, .double, .bool, .unsafePointer: return .identity
13741515
case .string: return .stringLowerParameter
1516+
case .jsValue: return .jsValueLowerParameter
13751517
case .jsObject: return .jsObjectLowerParameter
13761518
case .swiftHeapObject:
13771519
return .swiftHeapObjectLowerParameter
@@ -1416,6 +1558,7 @@ struct IntrinsicJSFragment: Sendable {
14161558
case .int, .float, .double: return .identity
14171559
case .bool: return .boolLiftReturn
14181560
case .string: return .stringLiftReturn
1561+
case .jsValue: return .jsValueLiftReturn
14191562
case .jsObject: return .jsObjectLiftReturn
14201563
case .swiftHeapObject(let name): return .swiftHeapObjectLiftReturn(name)
14211564
case .unsafePointer: return .identity
@@ -1462,6 +1605,7 @@ struct IntrinsicJSFragment: Sendable {
14621605
case .int, .float, .double: return .identity
14631606
case .bool: return .boolLiftParameter
14641607
case .string: return .stringLiftParameter
1608+
case .jsValue: return .jsValueLiftParameter
14651609
case .jsObject: return .jsObjectLiftParameter
14661610
case .unsafePointer: return .identity
14671611
case .swiftHeapObject(let name):
@@ -1563,6 +1707,7 @@ struct IntrinsicJSFragment: Sendable {
15631707
case .int, .float, .double: return .identity
15641708
case .bool: return .boolLowerReturn
15651709
case .string: return .stringLowerReturn
1710+
case .jsValue: return .jsValueLowerReturn
15661711
case .jsObject: return .jsObjectLowerReturn
15671712
case .unsafePointer: return .identity
15681713
case .swiftHeapObject(let name):
@@ -2756,6 +2901,13 @@ struct IntrinsicJSFragment: Sendable {
27562901
allStructs: [ExportedStruct]
27572902
) -> IntrinsicJSFragment {
27582903
switch field.type {
2904+
case .jsValue:
2905+
return IntrinsicJSFragment(
2906+
parameters: ["value"],
2907+
printCode: { _, _, _, _ in
2908+
fatalError("JSValue is not supported as a Swift struct field in BridgeJS")
2909+
}
2910+
)
27592911
case .string:
27602912
return IntrinsicJSFragment(
27612913
parameters: ["value"],
@@ -3242,6 +3394,13 @@ struct IntrinsicJSFragment: Sendable {
32423394
allStructs: [ExportedStruct]
32433395
) -> IntrinsicJSFragment {
32443396
switch field.type {
3397+
case .jsValue:
3398+
return IntrinsicJSFragment(
3399+
parameters: [],
3400+
printCode: { _, _, _, _ in
3401+
fatalError("JSValue is not supported as a Swift struct field in BridgeJS")
3402+
}
3403+
)
32453404
case .string:
32463405
return IntrinsicJSFragment(
32473406
parameters: [],

0 commit comments

Comments
 (0)