Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -978,7 +978,9 @@ public struct BridgeJSLink {
[structDef.name],
IntrinsicJSFragment.PrintCodeContext(
scope: structScope,
printer: structPrinter
printer: structPrinter,
hasDirectAccessToSwiftClass: false,
classNamespaces: intrinsicRegistry.classNamespaces
)
)
bodyPrinter.write(lines: structPrinter.lines)
Expand All @@ -995,7 +997,9 @@ public struct BridgeJSLink {
[enumDef.valuesName],
IntrinsicJSFragment.PrintCodeContext(
scope: enumScope,
printer: enumPrinter
printer: enumPrinter,
hasDirectAccessToSwiftClass: false,
classNamespaces: intrinsicRegistry.classNamespaces
)
)
bodyPrinter.write(lines: enumPrinter.lines)
Expand Down Expand Up @@ -1079,6 +1083,14 @@ public struct BridgeJSLink {

public func link() throws -> (outputJs: String, outputDts: String) {
intrinsicRegistry.reset()
intrinsicRegistry.classNamespaces = skeletons.reduce(into: [:]) { result, unified in
guard let skeleton = unified.exported else { return }
for klass in skeleton.classes {
if let namespace = klass.namespace {
result[klass.name] = namespace
}
}
}
let data = try collectLinkData()
let outputJs = try generateJavaScript(data: data)
let outputDts = generateTypeScript(data: data)
Expand Down Expand Up @@ -1144,8 +1156,11 @@ public struct BridgeJSLink {

for klass in classes.sorted(by: { $0.name < $1.name }) {
let wrapperFunctionName = "bjs_\(klass.name)_wrap"
let namespacePath = (klass.namespace ?? []).map { ".\($0)" }.joined()
let exportsPath =
namespacePath.isEmpty ? "_exports['\(klass.name)']" : "_exports\(namespacePath).\(klass.name)"
wrapperLines.append("importObject[\"\(moduleName)\"][\"\(wrapperFunctionName)\"] = function(pointer) {")
wrapperLines.append(" const obj = _exports['\(klass.name)'].__construct(pointer);")
wrapperLines.append(" const obj = \(exportsPath).__construct(pointer);")
wrapperLines.append(" return \(JSGlueVariableScope.reservedSwift).memory.retain(obj);")
wrapperLines.append("};")
}
Expand Down Expand Up @@ -1252,7 +1267,8 @@ public struct BridgeJSLink {
self.context = IntrinsicJSFragment.PrintCodeContext(
scope: scope,
printer: body,
hasDirectAccessToSwiftClass: hasDirectAccessToSwiftClass
hasDirectAccessToSwiftClass: hasDirectAccessToSwiftClass,
classNamespaces: intrinsicRegistry.classNamespaces
)
}

Expand Down Expand Up @@ -2147,7 +2163,9 @@ extension BridgeJSLink {
self.context = context
self.printContext = IntrinsicJSFragment.PrintCodeContext(
scope: scope,
printer: body
printer: body,
hasDirectAccessToSwiftClass: false,
classNamespaces: intrinsicRegistry.classNamespaces
)
}

Expand Down
39 changes: 31 additions & 8 deletions Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,36 @@ struct IntrinsicJSFragment: Sendable {
/// The printer to print the main fragment code.
var printer: CodeFragmentPrinter
/// Whether the fragment has direct access to the SwiftHeapObject classes.
/// If false, the fragment needs to use `_exports["<class name>"]` to access the class.
/// If false, the fragment needs to use `_exports` to access the class.
var hasDirectAccessToSwiftClass: Bool = true
/// Maps class names to their namespace path components for resolving `_exports` access.
var classNamespaces: [String: [String]] = [:]

func with<T>(_ keyPath: WritableKeyPath<PrintCodeContext, T>, _ value: T) -> PrintCodeContext {
var new = self
new[keyPath: keyPath] = value
return new
}

private func unqualifiedClassName(for qualifiedName: String) -> String {
qualifiedName.split(separator: ".").last.map(String.init) ?? qualifiedName
}

private func exportsAccess(forClass name: String) -> String {
if let namespace = classNamespaces[name], !namespace.isEmpty {
let path = namespace.map { ".\($0)" }.joined()
return "_exports\(path).\(name)"
}
return "_exports['\(name)']"
}

func classReference(forQualifiedName qualifiedName: String) -> String {
if hasDirectAccessToSwiftClass {
return unqualifiedClassName(for: qualifiedName)
}
let unqualified = unqualifiedClassName(for: qualifiedName)
return exportsAccess(forClass: unqualified)
}
}

/// A function that prints the fragment code.
Expand Down Expand Up @@ -555,8 +577,9 @@ struct IntrinsicJSFragment: Sendable {
return IntrinsicJSFragment(
parameters: ["value"],
printCode: { arguments, context in
let classRef = context.classReference(forQualifiedName: name)
return [
"\(context.hasDirectAccessToSwiftClass ? name : "_exports['\(name)']").__construct(\(arguments[0]))"
"\(classRef).__construct(\(arguments[0]))"
]
}
)
Expand All @@ -565,7 +588,8 @@ struct IntrinsicJSFragment: Sendable {
return IntrinsicJSFragment(
parameters: ["pointer"],
printCode: { arguments, context in
return ["_exports['\(name)'].__construct(\(arguments[0]))"]
let classRef = context.classReference(forQualifiedName: name)
return ["\(classRef).__construct(\(arguments[0]))"]
}
)
}
Expand Down Expand Up @@ -874,10 +898,8 @@ struct IntrinsicJSFragment: Sendable {
printer.write(
"\(JSGlueVariableScope.reservedStorageToReturnOptionalHeapObject) = undefined;"
)
let constructExpr =
context.hasDirectAccessToSwiftClass
? "\(className).__construct(\(pointerVar))"
: "_exports['\(className)'].__construct(\(pointerVar))"
let classRef = context.classReference(forQualifiedName: className)
let constructExpr = "\(classRef).__construct(\(pointerVar))"
printer.write(
"const \(resultVar) = \(pointerVar) === null ? \(absenceLiteral) : \(constructExpr);"
)
Expand Down Expand Up @@ -2075,8 +2097,9 @@ struct IntrinsicJSFragment: Sendable {
let (scope, printer) = (context.scope, context.printer)
let ptrVar = scope.variable("ptr")
let objVar = scope.variable("obj")
let classRef = context.classReference(forQualifiedName: className)
printer.write("const \(ptrVar) = \(scope.popPointer());")
printer.write("const \(objVar) = _exports['\(className)'].__construct(\(ptrVar));")
printer.write("const \(objVar) = \(classRef).__construct(\(ptrVar));")
return [objVar]
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import BridgeJSUtilities
/// Registry for JS helper intrinsics used during code generation.
final class JSIntrinsicRegistry {
private var entries: [String: [String]] = [:]
var classNamespaces: [String: [String]] = [:]

var isEmpty: Bool {
entries.isEmpty
Expand All @@ -19,6 +20,7 @@ final class JSIntrinsicRegistry {

func reset() {
entries.removeAll()
classNamespaces.removeAll()
}

func emitLines() -> [String] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,19 @@ class UUID {
Foundation.UUID().uuidString
}
}

@JS(namespace: "Collections") class Container {
var items: [Greeter]

@JS init() {
self.items = []
}

@JS func getItems() -> [Greeter] {
return items
}

@JS func addItem(_ item: Greeter) {
items.append(item)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,75 @@

],
"swiftCallName" : "UUID"
},
{
"constructor" : {
"abiName" : "bjs_Container_init",
"effects" : {
"isAsync" : false,
"isStatic" : false,
"isThrows" : false
},
"parameters" : [

]
},
"methods" : [
{
"abiName" : "bjs_Container_getItems",
"effects" : {
"isAsync" : false,
"isStatic" : false,
"isThrows" : false
},
"name" : "getItems",
"parameters" : [

],
"returnType" : {
"array" : {
"_0" : {
"swiftHeapObject" : {
"_0" : "Greeter"
}
}
}
}
},
{
"abiName" : "bjs_Container_addItem",
"effects" : {
"isAsync" : false,
"isStatic" : false,
"isThrows" : false
},
"name" : "addItem",
"parameters" : [
{
"label" : "_",
"name" : "item",
"type" : {
"swiftHeapObject" : {
"_0" : "Greeter"
}
}
}
],
"returnType" : {
"void" : {

}
}
}
],
"name" : "Container",
"namespace" : [
"Collections"
],
"properties" : [

],
"swiftCallName" : "Container"
}
],
"enums" : [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,64 @@ fileprivate func _bjs_UUID_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> In
#endif
@inline(never) fileprivate func _bjs_UUID_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 {
return _bjs_UUID_wrap_extern(pointer)
}

@_expose(wasm, "bjs_Container_init")
@_cdecl("bjs_Container_init")
public func _bjs_Container_init() -> UnsafeMutableRawPointer {
#if arch(wasm32)
let ret = Container()
return ret.bridgeJSLowerReturn()
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_Container_getItems")
@_cdecl("bjs_Container_getItems")
public func _bjs_Container_getItems(_ _self: UnsafeMutableRawPointer) -> Void {
#if arch(wasm32)
let ret = Container.bridgeJSLiftParameter(_self).getItems()
ret.bridgeJSStackPush()
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_Container_addItem")
@_cdecl("bjs_Container_addItem")
public func _bjs_Container_addItem(_ _self: UnsafeMutableRawPointer, _ item: UnsafeMutableRawPointer) -> Void {
#if arch(wasm32)
Container.bridgeJSLiftParameter(_self).addItem(_: Greeter.bridgeJSLiftParameter(item))
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_Container_deinit")
@_cdecl("bjs_Container_deinit")
public func _bjs_Container_deinit(_ pointer: UnsafeMutableRawPointer) -> Void {
#if arch(wasm32)
Unmanaged<Container>.fromOpaque(pointer).release()
#else
fatalError("Only available on WebAssembly")
#endif
}

extension Container: ConvertibleToJSValue, _BridgedSwiftHeapObject {
var jsValue: JSValue {
return .object(JSObject(id: UInt32(bitPattern: _bjs_Container_wrap(Unmanaged.passRetained(self).toOpaque()))))
}
}

#if arch(wasm32)
@_extern(wasm, module: "TestModule", name: "bjs_Container_wrap")
fileprivate func _bjs_Container_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32
#else
fileprivate func _bjs_Container_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 {
fatalError("Only available on WebAssembly")
}
#endif
@inline(never) fileprivate func _bjs_Container_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 {
return _bjs_Container_wrap_extern(pointer)
}
Loading