Skip to content

Commit a7531eb

Browse files
Merge pull request #412 from swiftwasm/swift-class-to-jsvalue-codegen
2 parents 828f970 + 76cb92d commit a7531eb

Some content is hidden

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

46 files changed

+547
-30
lines changed

Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@
1919
}
2020
}
2121
}
22-
]
22+
],
23+
"moduleName" : "Benchmarks"
2324
}

Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ public func _bjs_PlayBridgeJS_deinit(pointer: UnsafeMutableRawPointer) {
5656
Unmanaged<PlayBridgeJS>.fromOpaque(pointer).release()
5757
}
5858

59+
extension PlayBridgeJS: ConvertibleToJSValue {
60+
var jsValue: JSValue {
61+
@_extern(wasm, module: "PlayBridgeJS", name: "bjs_PlayBridgeJS_wrap")
62+
func _bjs_PlayBridgeJS_wrap(_: UnsafeMutableRawPointer) -> Int32
63+
return .object(JSObject(id: UInt32(bitPattern: _bjs_PlayBridgeJS_wrap(Unmanaged.passRetained(self).toOpaque()))))
64+
}
65+
}
66+
5967
@_expose(wasm, "bjs_PlayBridgeJSOutput_outputJs")
6068
@_cdecl("bjs_PlayBridgeJSOutput_outputJs")
6169
public func _bjs_PlayBridgeJSOutput_outputJs(_self: UnsafeMutableRawPointer) -> Void {
@@ -112,4 +120,12 @@ public func _bjs_PlayBridgeJSOutput_exportSwiftGlue(_self: UnsafeMutableRawPoint
112120
@_cdecl("bjs_PlayBridgeJSOutput_deinit")
113121
public func _bjs_PlayBridgeJSOutput_deinit(pointer: UnsafeMutableRawPointer) {
114122
Unmanaged<PlayBridgeJSOutput>.fromOpaque(pointer).release()
123+
}
124+
125+
extension PlayBridgeJSOutput: ConvertibleToJSValue {
126+
var jsValue: JSValue {
127+
@_extern(wasm, module: "PlayBridgeJS", name: "bjs_PlayBridgeJSOutput_wrap")
128+
func _bjs_PlayBridgeJSOutput_wrap(_: UnsafeMutableRawPointer) -> Int32
129+
return .object(JSObject(id: UInt32(bitPattern: _bjs_PlayBridgeJSOutput_wrap(Unmanaged.passRetained(self).toOpaque()))))
130+
}
115131
}

Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,6 @@
120120
],
121121
"functions" : [
122122

123-
]
123+
],
124+
"moduleName" : "PlayBridgeJS"
124125
}

Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import class Foundation.JSONDecoder
1717
}
1818

1919
func _update(swiftSource: String, dtsSource: String) throws -> PlayBridgeJSOutput {
20-
let exportSwift = ExportSwift(progress: .silent)
20+
let exportSwift = ExportSwift(progress: .silent, moduleName: "Playground")
2121
let sourceFile = Parser.parse(source: swiftSource)
2222
try exportSwift.addSourceFile(sourceFile, "Playground.swift")
2323
let exportResult = try exportSwift.finalize()

Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ struct BridgeJSBuildPlugin: BuildToolPlugin {
4242
executable: try context.tool(named: "BridgeJSTool").url,
4343
arguments: [
4444
"export",
45+
"--module-name",
46+
target.name,
4547
"--output-skeleton",
4648
outputSkeletonPath.path,
4749
"--output-swift",

Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ extension BridgeJSCommandPlugin.Context {
105105
try runBridgeJSTool(
106106
arguments: [
107107
"export",
108+
"--module-name",
109+
target.name,
108110
"--output-skeleton",
109111
generatedJavaScriptDirectory.appending(path: "BridgeJS.ExportSwift.json").path,
110112
"--output-swift",

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ import BridgeJSSkeleton
1616
/// JavaScript glue code and TypeScript definitions.
1717
public class ExportSwift {
1818
let progress: ProgressReporting
19+
let moduleName: String
1920

2021
private var exportedFunctions: [ExportedFunction] = []
2122
private var exportedClasses: [ExportedClass] = []
2223
private var typeDeclResolver: TypeDeclResolver = TypeDeclResolver()
2324

24-
public init(progress: ProgressReporting) {
25+
public init(progress: ProgressReporting, moduleName: String) {
2526
self.progress = progress
27+
self.moduleName = moduleName
2628
}
2729

2830
/// Processes a Swift source file to find declarations marked with @JS
@@ -53,7 +55,11 @@ public class ExportSwift {
5355
}
5456
return (
5557
outputSwift: outputSwift,
56-
outputSkeleton: ExportedSkeleton(functions: exportedFunctions, classes: exportedClasses)
58+
outputSkeleton: ExportedSkeleton(
59+
moduleName: moduleName,
60+
functions: exportedFunctions,
61+
classes: exportedClasses
62+
)
5763
)
5864
}
5965

@@ -676,8 +682,41 @@ public class ExportSwift {
676682
)
677683
}
678684

685+
// Generate ConvertibleToJSValue extension
686+
decls.append(renderConvertibleToJSValueExtension(klass: klass))
687+
679688
return decls
680689
}
690+
691+
/// Generates a ConvertibleToJSValue extension for the exported class
692+
///
693+
/// # Example
694+
///
695+
/// For a class named `Greeter`, this generates:
696+
///
697+
/// ```swift
698+
/// extension Greeter: ConvertibleToJSValue {
699+
/// var jsValue: JSValue {
700+
/// @_extern(wasm, module: "MyModule", name: "bjs_Greeter_wrap")
701+
/// func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32
702+
/// return JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque())))
703+
/// }
704+
/// }
705+
/// ```
706+
func renderConvertibleToJSValueExtension(klass: ExportedClass) -> DeclSyntax {
707+
let wrapFunctionName = "_bjs_\(klass.name)_wrap"
708+
let externFunctionName = "bjs_\(klass.name)_wrap"
709+
710+
return """
711+
extension \(raw: klass.name): ConvertibleToJSValue {
712+
var jsValue: JSValue {
713+
@_extern(wasm, module: "\(raw: moduleName)", name: "\(raw: externFunctionName)")
714+
func \(raw: wrapFunctionName)(_: UnsafeMutableRawPointer) -> Int32
715+
return .object(JSObject(id: UInt32(bitPattern: \(raw: wrapFunctionName)(Unmanaged.passRetained(self).toOpaque()))))
716+
}
717+
}
718+
"""
719+
}
681720
}
682721

683722
extension AttributeListSyntax {

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ struct BridgeJSLink {
203203
bjs["swift_js_release"] = function(id) {
204204
swift.memory.release(id);
205205
}
206+
\(renderSwiftClassWrappers().map { $0.indent(count: 12) }.joined(separator: "\n"))
206207
\(importObjectBuilders.flatMap { $0.importedLines }.map { $0.indent(count: 12) }.joined(separator: "\n"))
207208
},
208209
setInstance: (i) => {
@@ -249,6 +250,39 @@ struct BridgeJSLink {
249250
return (outputJs, outputDts)
250251
}
251252

253+
private func renderSwiftClassWrappers() -> [String] {
254+
var wrapperLines: [String] = []
255+
var modulesByName: [String: [ExportedClass]] = [:]
256+
257+
// Group classes by their module name
258+
for skeleton in exportedSkeletons {
259+
if skeleton.classes.isEmpty { continue }
260+
261+
if modulesByName[skeleton.moduleName] == nil {
262+
modulesByName[skeleton.moduleName] = []
263+
}
264+
modulesByName[skeleton.moduleName]?.append(contentsOf: skeleton.classes)
265+
}
266+
267+
// Generate wrapper functions for each module
268+
for (moduleName, classes) in modulesByName {
269+
wrapperLines.append("// Wrapper functions for module: \(moduleName)")
270+
wrapperLines.append("if (!importObject[\"\(moduleName)\"]) {")
271+
wrapperLines.append(" importObject[\"\(moduleName)\"] = {};")
272+
wrapperLines.append("}")
273+
274+
for klass in classes {
275+
let wrapperFunctionName = "bjs_\(klass.name)_wrap"
276+
wrapperLines.append("importObject[\"\(moduleName)\"][\"\(wrapperFunctionName)\"] = function(pointer) {")
277+
wrapperLines.append(" const obj = \(klass.name).__construct(pointer);")
278+
wrapperLines.append(" return swift.memory.retain(obj);")
279+
wrapperLines.append("};")
280+
}
281+
}
282+
283+
return wrapperLines
284+
}
285+
252286
private func generateImportedTypeDefinitions() -> [String] {
253287
var typeDefinitions: [String] = []
254288

@@ -736,7 +770,9 @@ struct BridgeJSLink {
736770

737771
init(moduleName: String) {
738772
self.moduleName = moduleName
739-
importedLines.append("const \(moduleName) = importObject[\"\(moduleName)\"] = {};")
773+
importedLines.append(
774+
"const \(moduleName) = importObject[\"\(moduleName)\"] = importObject[\"\(moduleName)\"] || {};"
775+
)
740776
}
741777

742778
func assignToImportObject(name: String, function: [String]) {

Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,12 @@ public struct ExportedConstructor: Codable {
9393
}
9494

9595
public struct ExportedSkeleton: Codable {
96+
public let moduleName: String
9697
public let functions: [ExportedFunction]
9798
public let classes: [ExportedClass]
9899

99-
public init(functions: [ExportedFunction], classes: [ExportedClass]) {
100+
public init(moduleName: String, functions: [ExportedFunction], classes: [ExportedClass]) {
101+
self.moduleName = moduleName
100102
self.functions = functions
101103
self.classes = classes
102104
}

Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ import TS2Skeleton
149149
let parser = ArgumentParser(
150150
singleDashOptions: [:],
151151
doubleDashOptions: [
152+
"module-name": OptionRule(
153+
help: "The name of the module for external function references",
154+
required: true
155+
),
152156
"output-skeleton": OptionRule(
153157
help: "The output file path for the skeleton of the exported Swift APIs",
154158
required: true
@@ -168,7 +172,7 @@ import TS2Skeleton
168172
arguments: Array(arguments.dropFirst())
169173
)
170174
let progress = ProgressReporting(verbose: doubleDashOptions["verbose"] == "true")
171-
let exporter = ExportSwift(progress: progress)
175+
let exporter = ExportSwift(progress: progress, moduleName: doubleDashOptions["module-name"]!)
172176
for inputFile in positionalArguments.sorted() {
173177
let sourceURL = URL(fileURLWithPath: inputFile)
174178
guard sourceURL.pathExtension == "swift" else { continue }

0 commit comments

Comments
 (0)