Skip to content

Commit 89e3532

Browse files
BridgeJS: support UnsafePointer-family types (#525)
* BridgeJS: support UnsafePointer-family types Allow UnsafePointer/UnsafeRawPointer/OpaquePointer as BridgeJS parameter/return and struct field types by lowering them to WASM pointers. Updates codegen/runtime intrinsics and adds coverage in generator snapshots + runtime exports. * Format
1 parent 4d01d11 commit 89e3532

File tree

16 files changed

+1657
-1
lines changed

16 files changed

+1657
-1
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,8 @@ struct StackCodegen {
772772
return "JSObject.bridgeJSLiftParameter(_swift_js_pop_param_int32())"
773773
case .swiftHeapObject(let className):
774774
return "\(raw: className).bridgeJSLiftParameter(_swift_js_pop_param_pointer())"
775+
case .unsafePointer:
776+
return "\(raw: type.swiftType).bridgeJSLiftParameter(_swift_js_pop_param_pointer())"
775777
case .swiftProtocol:
776778
// Protocols are handled via JSObject
777779
return "JSObject.bridgeJSLiftParameter(_swift_js_pop_param_int32())"
@@ -872,6 +874,8 @@ struct StackCodegen {
872874
return ["_swift_js_push_int(\(raw: accessor).bridgeJSLowerParameter())"]
873875
case .swiftHeapObject:
874876
return ["_swift_js_push_pointer(\(raw: accessor).bridgeJSLowerReturn())"]
877+
case .unsafePointer:
878+
return ["_swift_js_push_pointer(\(raw: accessor).bridgeJSLowerReturn())"]
875879
case .swiftProtocol:
876880
return ["_swift_js_push_int(\(raw: accessor).bridgeJSLowerParameter())"]
877881
case .caseEnum:
@@ -1421,6 +1425,23 @@ extension WasmCoreType {
14211425
}
14221426
}
14231427

1428+
extension UnsafePointerType {
1429+
var swiftType: String {
1430+
switch kind {
1431+
case .unsafePointer:
1432+
return "UnsafePointer<\(pointee ?? "Never")>"
1433+
case .unsafeMutablePointer:
1434+
return "UnsafeMutablePointer<\(pointee ?? "Never")>"
1435+
case .unsafeRawPointer:
1436+
return "UnsafeRawPointer"
1437+
case .unsafeMutableRawPointer:
1438+
return "UnsafeMutableRawPointer"
1439+
case .opaquePointer:
1440+
return "OpaquePointer"
1441+
}
1442+
}
1443+
}
1444+
14241445
extension BridgeType {
14251446
var swiftType: String {
14261447
switch self {
@@ -1432,6 +1453,7 @@ extension BridgeType {
14321453
case .jsObject(nil): return "JSObject"
14331454
case .jsObject(let name?): return name
14341455
case .swiftHeapObject(let name): return name
1456+
case .unsafePointer(let ptr): return ptr.swiftType
14351457
case .swiftProtocol(let name): return "Any\(name)"
14361458
case .void: return "Void"
14371459
case .optional(let wrappedType): return "Optional<\(wrappedType.swiftType)>"
@@ -1457,6 +1479,7 @@ extension BridgeType {
14571479
static let string = LiftingIntrinsicInfo(parameters: [("bytes", .i32), ("length", .i32)])
14581480
static let jsObject = LiftingIntrinsicInfo(parameters: [("value", .i32)])
14591481
static let swiftHeapObject = LiftingIntrinsicInfo(parameters: [("value", .pointer)])
1482+
static let unsafePointer = LiftingIntrinsicInfo(parameters: [("pointer", .pointer)])
14601483
static let void = LiftingIntrinsicInfo(parameters: [])
14611484
static let caseEnum = LiftingIntrinsicInfo(parameters: [("value", .i32)])
14621485
static let associatedValueEnum = LiftingIntrinsicInfo(parameters: [
@@ -1473,6 +1496,7 @@ extension BridgeType {
14731496
case .string: return .string
14741497
case .jsObject: return .jsObject
14751498
case .swiftHeapObject: return .swiftHeapObject
1499+
case .unsafePointer: return .unsafePointer
14761500
case .swiftProtocol: return .jsObject
14771501
case .void: return .void
14781502
case .optional(let wrappedType):
@@ -1503,6 +1527,7 @@ extension BridgeType {
15031527
static let string = LoweringIntrinsicInfo(returnType: nil)
15041528
static let jsObject = LoweringIntrinsicInfo(returnType: .i32)
15051529
static let swiftHeapObject = LoweringIntrinsicInfo(returnType: .pointer)
1530+
static let unsafePointer = LoweringIntrinsicInfo(returnType: .pointer)
15061531
static let void = LoweringIntrinsicInfo(returnType: nil)
15071532
static let caseEnum = LoweringIntrinsicInfo(returnType: .i32)
15081533
static let rawValueEnum = LoweringIntrinsicInfo(returnType: .i32)
@@ -1520,6 +1545,7 @@ extension BridgeType {
15201545
case .string: return .string
15211546
case .jsObject: return .jsObject
15221547
case .swiftHeapObject: return .swiftHeapObject
1548+
case .unsafePointer: return .unsafePointer
15231549
case .swiftProtocol: return .jsObject
15241550
case .void: return .void
15251551
case .optional: return .optional

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,8 @@ extension BridgeType {
875875
case .closure:
876876
// Swift closure is boxed and passed to JS as a pointer.
877877
return LoweringParameterInfo(loweredParameters: [("pointer", .pointer)])
878+
case .unsafePointer:
879+
return LoweringParameterInfo(loweredParameters: [("pointer", .pointer)])
878880
case .swiftHeapObject(let className):
879881
switch context {
880882
case .importTS:
@@ -959,6 +961,8 @@ extension BridgeType {
959961
case .closure:
960962
// JS returns a callback ID for closures, which Swift lifts to a typed closure.
961963
return LiftingReturnInfo(valueToLift: .i32)
964+
case .unsafePointer:
965+
return LiftingReturnInfo(valueToLift: .pointer)
962966
case .swiftHeapObject(let className):
963967
switch context {
964968
case .importTS:

Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@ public final class SwiftToSkeleton {
163163
}
164164
}
165165

166+
// UnsafePointer family
167+
if let unsafePointerType = Self.parseUnsafePointerType(type) {
168+
return .unsafePointer(unsafePointerType)
169+
}
170+
166171
let typeName: String
167172
if let identifier = type.as(IdentifierTypeSyntax.self) {
168173
typeName = Self.normalizeIdentifier(identifier.name.text)
@@ -248,6 +253,55 @@ public final class SwiftToSkeleton {
248253
return .swiftHeapObject(swiftCallName)
249254
}
250255

256+
fileprivate static func parseUnsafePointerType(_ type: TypeSyntax) -> UnsafePointerType? {
257+
func parse(baseName: String, genericArg: TypeSyntax?) -> UnsafePointerType? {
258+
let pointee = genericArg?.trimmedDescription
259+
switch baseName {
260+
case "UnsafePointer":
261+
return .init(kind: .unsafePointer, pointee: pointee)
262+
case "UnsafeMutablePointer":
263+
return .init(kind: .unsafeMutablePointer, pointee: pointee)
264+
case "UnsafeRawPointer":
265+
return .init(kind: .unsafeRawPointer)
266+
case "UnsafeMutableRawPointer":
267+
return .init(kind: .unsafeMutableRawPointer)
268+
case "OpaquePointer":
269+
return .init(kind: .opaquePointer)
270+
default:
271+
return nil
272+
}
273+
}
274+
275+
if let identifier = type.as(IdentifierTypeSyntax.self) {
276+
let baseName = identifier.name.text
277+
if (baseName == "UnsafePointer" || baseName == "UnsafeMutablePointer"),
278+
let genericArgs = identifier.genericArgumentClause?.arguments,
279+
genericArgs.count == 1,
280+
let argType = TypeSyntax(genericArgs.first?.argument)
281+
{
282+
return parse(baseName: baseName, genericArg: argType)
283+
}
284+
return parse(baseName: baseName, genericArg: nil)
285+
}
286+
287+
if let member = type.as(MemberTypeSyntax.self),
288+
let base = member.baseType.as(IdentifierTypeSyntax.self),
289+
base.name.text == "Swift"
290+
{
291+
let baseName = member.name.text
292+
if (baseName == "UnsafePointer" || baseName == "UnsafeMutablePointer"),
293+
let genericArgs = member.genericArgumentClause?.arguments,
294+
genericArgs.count == 1,
295+
let argType = TypeSyntax(genericArgs.first?.argument)
296+
{
297+
return parse(baseName: baseName, genericArg: argType)
298+
}
299+
return parse(baseName: baseName, genericArg: nil)
300+
}
301+
302+
return nil
303+
}
304+
251305
/// Computes the full Swift call name by walking up the AST hierarchy to find all parent enums
252306
/// This generates the qualified name needed for Swift code generation (e.g., "Networking.API.HTTPServer")
253307
fileprivate static func computeSwiftCallName(for node: some SyntaxProtocol, itemName: String) -> String {

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3298,6 +3298,8 @@ extension BridgeType {
32983298
return name ?? "any"
32993299
case .swiftHeapObject(let name):
33003300
return name
3301+
case .unsafePointer:
3302+
return "number"
33013303
case .optional(let wrappedType):
33023304
return "\(wrappedType.tsType) | null"
33033305
case .caseEnum(let name):

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1300,7 +1300,7 @@ struct IntrinsicJSFragment: Sendable {
13001300
/// Returns a fragment that lowers a JS value to Wasm core values for parameters
13011301
static func lowerParameter(type: BridgeType) throws -> IntrinsicJSFragment {
13021302
switch type {
1303-
case .int, .float, .double, .bool: return .identity
1303+
case .int, .float, .double, .bool, .unsafePointer: return .identity
13041304
case .string: return .stringLowerParameter
13051305
case .jsObject: return .jsObjectLowerParameter
13061306
case .swiftHeapObject:
@@ -1346,6 +1346,7 @@ struct IntrinsicJSFragment: Sendable {
13461346
case .string: return .stringLiftReturn
13471347
case .jsObject: return .jsObjectLiftReturn
13481348
case .swiftHeapObject(let name): return .swiftHeapObjectLiftReturn(name)
1349+
case .unsafePointer: return .identity
13491350
case .swiftProtocol: return .jsObjectLiftReturn
13501351
case .void: return .void
13511352
case .optional(let wrappedType): return .optionalLiftReturn(wrappedType: wrappedType)
@@ -1388,6 +1389,7 @@ struct IntrinsicJSFragment: Sendable {
13881389
case .bool: return .boolLiftParameter
13891390
case .string: return .stringLiftParameter
13901391
case .jsObject: return .jsObjectLiftParameter
1392+
case .unsafePointer: return .identity
13911393
case .swiftHeapObject(let name):
13921394
switch context {
13931395
case .importTS:
@@ -1482,6 +1484,7 @@ struct IntrinsicJSFragment: Sendable {
14821484
case .bool: return .boolLowerReturn
14831485
case .string: return .stringLowerReturn
14841486
case .jsObject: return .jsObjectLowerReturn
1487+
case .unsafePointer: return .identity
14851488
case .swiftHeapObject(let name):
14861489
switch context {
14871490
case .importTS:
@@ -2266,6 +2269,14 @@ struct IntrinsicJSFragment: Sendable {
22662269
return []
22672270
}
22682271
)
2272+
case .unsafePointer:
2273+
return IntrinsicJSFragment(
2274+
parameters: ["value"],
2275+
printCode: { arguments, scope, printer, cleanup in
2276+
printer.write("\(JSGlueVariableScope.reservedTmpParamPointers).push((\(arguments[0]) | 0));")
2277+
return []
2278+
}
2279+
)
22692280
case .jsObject:
22702281
return IntrinsicJSFragment(
22712282
parameters: ["value"],
@@ -2698,6 +2709,15 @@ struct IntrinsicJSFragment: Sendable {
26982709
return [dVar]
26992710
}
27002711
)
2712+
case .unsafePointer:
2713+
return IntrinsicJSFragment(
2714+
parameters: [],
2715+
printCode: { arguments, scope, printer, cleanup in
2716+
let pVar = scope.variable("pointer")
2717+
printer.write("const \(pVar) = \(JSGlueVariableScope.reservedTmpRetPointers).pop();")
2718+
return [pVar]
2719+
}
2720+
)
27012721
case .optional(let wrappedType):
27022722
return IntrinsicJSFragment(
27032723
parameters: [],

Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,28 @@ public struct ClosureSignature: Codable, Equatable, Hashable, Sendable {
107107
}
108108
}
109109

110+
public enum UnsafePointerKind: String, Codable, Equatable, Hashable, Sendable {
111+
case unsafePointer
112+
case unsafeMutablePointer
113+
case unsafeRawPointer
114+
case unsafeMutableRawPointer
115+
case opaquePointer
116+
}
117+
118+
public struct UnsafePointerType: Codable, Equatable, Hashable, Sendable {
119+
public let kind: UnsafePointerKind
120+
/// The pointee type name for generic pointer types (e.g. `UInt8` for `UnsafePointer<UInt8>`).
121+
public let pointee: String?
122+
123+
public init(kind: UnsafePointerKind, pointee: String? = nil) {
124+
self.kind = kind
125+
self.pointee = pointee
126+
}
127+
}
128+
110129
public enum BridgeType: Codable, Equatable, Hashable, Sendable {
111130
case int, float, double, string, bool, jsObject(String?), swiftHeapObject(String), void
131+
case unsafePointer(UnsafePointerType)
112132
indirect case optional(BridgeType)
113133
case caseEnum(String)
114134
case rawValueEnum(String, SwiftEnumRawType)
@@ -831,6 +851,12 @@ extension BridgeType {
831851
self = .void
832852
case "JSObject":
833853
self = .jsObject(nil)
854+
case "UnsafeRawPointer":
855+
self = .unsafePointer(.init(kind: .unsafeRawPointer))
856+
case "UnsafeMutableRawPointer":
857+
self = .unsafePointer(.init(kind: .unsafeMutableRawPointer))
858+
case "OpaquePointer":
859+
self = .unsafePointer(.init(kind: .opaquePointer))
834860
default:
835861
return nil
836862
}
@@ -848,6 +874,8 @@ extension BridgeType {
848874
case .swiftHeapObject:
849875
// UnsafeMutableRawPointer is returned as an i32 pointer
850876
return .pointer
877+
case .unsafePointer:
878+
return .pointer
851879
case .optional(_):
852880
return nil
853881
case .caseEnum:
@@ -891,6 +919,23 @@ extension BridgeType {
891919
return "\(typeName.count)\(typeName)C"
892920
case .swiftHeapObject(let name):
893921
return "\(name.count)\(name)C"
922+
case .unsafePointer(let ptr):
923+
func sanitize(_ s: String) -> String {
924+
s.filter { $0.isNumber || $0.isLetter }
925+
}
926+
let kindCode: String =
927+
switch ptr.kind {
928+
case .unsafePointer: "Sup"
929+
case .unsafeMutablePointer: "Sump"
930+
case .unsafeRawPointer: "Surp"
931+
case .unsafeMutableRawPointer: "Sumrp"
932+
case .opaquePointer: "Sop"
933+
}
934+
if let pointee = ptr.pointee, !pointee.isEmpty {
935+
let p = sanitize(pointee)
936+
return "\(kindCode)\(p.count)\(p)"
937+
}
938+
return kindCode
894939
case .optional(let wrapped):
895940
return "Sq\(wrapped.mangleTypeName)"
896941
case .caseEnum(let name),
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
@JS func takeUnsafeRawPointer(_ p: UnsafeRawPointer) {}
2+
@JS func takeUnsafeMutableRawPointer(_ p: UnsafeMutableRawPointer) {}
3+
@JS func takeOpaquePointer(_ p: OpaquePointer) {}
4+
@JS func takeUnsafePointer(_ p: UnsafePointer<UInt8>) {}
5+
@JS func takeUnsafeMutablePointer(_ p: UnsafeMutablePointer<UInt8>) {}
6+
7+
@JS func returnUnsafeRawPointer() -> UnsafeRawPointer { UnsafeRawPointer(bitPattern: 1)! }
8+
@JS func returnUnsafeMutableRawPointer() -> UnsafeMutableRawPointer { UnsafeMutableRawPointer(bitPattern: 1)! }
9+
@JS func returnOpaquePointer() -> OpaquePointer { OpaquePointer(bitPattern: 1)! }
10+
@JS func returnUnsafePointer() -> UnsafePointer<UInt8> {
11+
UnsafeRawPointer(bitPattern: 1)!.assumingMemoryBound(to: UInt8.self)
12+
}
13+
@JS func returnUnsafeMutablePointer() -> UnsafeMutablePointer<UInt8> {
14+
UnsafeMutableRawPointer(bitPattern: 1)!.assumingMemoryBound(to: UInt8.self)
15+
}
16+
17+
@JS struct PointerFields {
18+
var raw: UnsafeRawPointer
19+
var mutRaw: UnsafeMutableRawPointer
20+
var opaque: OpaquePointer
21+
var ptr: UnsafePointer<UInt8>
22+
var mutPtr: UnsafeMutablePointer<UInt8>
23+
24+
@JS init(
25+
raw: UnsafeRawPointer,
26+
mutRaw: UnsafeMutableRawPointer,
27+
opaque: OpaquePointer,
28+
ptr: UnsafePointer<UInt8>,
29+
mutPtr: UnsafeMutablePointer<UInt8>
30+
) {
31+
self.raw = raw
32+
self.mutRaw = mutRaw
33+
self.opaque = opaque
34+
self.ptr = ptr
35+
self.mutPtr = mutPtr
36+
}
37+
}
38+
39+
@JS func roundTripPointerFields(_ value: PointerFields) -> PointerFields { value }
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
2+
// DO NOT EDIT.
3+
//
4+
// To update this file, just rebuild your project or run
5+
// `swift package bridge-js`.
6+
7+
export interface PointerFields {
8+
raw: number;
9+
mutRaw: number;
10+
opaque: number;
11+
ptr: number;
12+
mutPtr: number;
13+
}
14+
export type Exports = {
15+
takeUnsafeRawPointer(p: number): void;
16+
takeUnsafeMutableRawPointer(p: number): void;
17+
takeOpaquePointer(p: number): void;
18+
takeUnsafePointer(p: number): void;
19+
takeUnsafeMutablePointer(p: number): void;
20+
returnUnsafeRawPointer(): number;
21+
returnUnsafeMutableRawPointer(): number;
22+
returnOpaquePointer(): number;
23+
returnUnsafePointer(): number;
24+
returnUnsafeMutablePointer(): number;
25+
roundTripPointerFields(value: PointerFields): PointerFields;
26+
PointerFields: {
27+
init(raw: number, mutRaw: number, opaque: number, ptr: number, mutPtr: number): PointerFields;
28+
}
29+
}
30+
export type Imports = {
31+
}
32+
export function createInstantiator(options: {
33+
imports: Imports;
34+
}, swift: any): Promise<{
35+
addImports: (importObject: WebAssembly.Imports) => void;
36+
setInstance: (instance: WebAssembly.Instance) => void;
37+
createExports: (instance: WebAssembly.Instance) => Exports;
38+
}>;

0 commit comments

Comments
 (0)