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
6 changes: 4 additions & 2 deletions Plugins/BridgeJS/Sources/BridgeJSCore/ClosureCodegen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,9 @@ public struct ClosureCodegen {
decls.append(try renderClosureInvokeHandler(signature))
}

let format = BasicFormat()
return decls.map { $0.formatted(using: format).description }.joined(separator: "\n\n")
return withSpan("Format Closure Glue") {
let format = BasicFormat()
return decls.map { $0.formatted(using: format).description }.joined(separator: "\n\n")
}
}
}
64 changes: 36 additions & 28 deletions Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,45 +47,53 @@ public class ExportSwift {
func renderSwiftGlue() throws -> String? {
var decls: [DeclSyntax] = []

let protocolCodegen = ProtocolCodegen()
for proto in skeleton.protocols {
decls.append(contentsOf: try protocolCodegen.renderProtocolWrapper(proto, moduleName: moduleName))
try withSpan("Render Protocols") { [self] in
let protocolCodegen = ProtocolCodegen()
for proto in skeleton.protocols {
decls.append(contentsOf: try protocolCodegen.renderProtocolWrapper(proto, moduleName: moduleName))
}
}

let enumCodegen = EnumCodegen()
for enumDef in skeleton.enums {
if let enumHelpers = enumCodegen.renderEnumHelpers(enumDef) {
decls.append(enumHelpers)
}
try withSpan("Render Enums") { [self] in
let enumCodegen = EnumCodegen()
for enumDef in skeleton.enums {
if let enumHelpers = enumCodegen.renderEnumHelpers(enumDef) {
decls.append(enumHelpers)
}

for staticMethod in enumDef.staticMethods {
decls.append(try renderSingleExportedFunction(function: staticMethod))
}
for staticMethod in enumDef.staticMethods {
decls.append(try renderSingleExportedFunction(function: staticMethod))
}

for staticProperty in enumDef.staticProperties {
decls.append(
contentsOf: try renderSingleExportedProperty(
property: staticProperty,
context: .enumStatic(enumDef: enumDef)
for staticProperty in enumDef.staticProperties {
decls.append(
contentsOf: try renderSingleExportedProperty(
property: staticProperty,
context: .enumStatic(enumDef: enumDef)
)
)
)
}
}
}

let structCodegen = StructCodegen()
for structDef in skeleton.structs {
decls.append(contentsOf: structCodegen.renderStructHelpers(structDef))
decls.append(contentsOf: try renderSingleExportedStruct(struct: structDef))
}
try withSpan("Render Structs") { [self] in
let structCodegen = StructCodegen()
for structDef in skeleton.structs {
decls.append(contentsOf: structCodegen.renderStructHelpers(structDef))
decls.append(contentsOf: try renderSingleExportedStruct(struct: structDef))
}

for function in skeleton.functions {
decls.append(try renderSingleExportedFunction(function: function))
for function in skeleton.functions {
decls.append(try renderSingleExportedFunction(function: function))
}
for klass in skeleton.classes {
decls.append(contentsOf: try renderSingleExportedClass(klass: klass))
}
}
for klass in skeleton.classes {
decls.append(contentsOf: try renderSingleExportedClass(klass: klass))
return withSpan("Format Export Glue") {
let format = BasicFormat()
return decls.map { $0.formatted(using: format).description }.joined(separator: "\n\n")
}
let format = BasicFormat()
return decls.map { $0.formatted(using: format).description }.joined(separator: "\n\n")
}

class ExportedThunkBuilder {
Expand Down
30 changes: 19 additions & 11 deletions Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,34 @@ public struct ImportTS {
var decls: [DeclSyntax] = []

for skeleton in self.skeleton.children {
for getter in skeleton.globalGetters {
let getterDecls = try renderSwiftGlobalGetter(getter, topLevelDecls: &decls)
decls.append(contentsOf: getterDecls)
try withSpan("Render Global Getters") {
for getter in skeleton.globalGetters {
let getterDecls = try renderSwiftGlobalGetter(getter, topLevelDecls: &decls)
decls.append(contentsOf: getterDecls)
}
}
for function in skeleton.functions {
let thunkDecls = try renderSwiftThunk(function, topLevelDecls: &decls)
decls.append(contentsOf: thunkDecls)
try withSpan("Render Functions") {
for function in skeleton.functions {
let thunkDecls = try renderSwiftThunk(function, topLevelDecls: &decls)
decls.append(contentsOf: thunkDecls)
}
}
for type in skeleton.types {
let typeDecls = try renderSwiftType(type, topLevelDecls: &decls)
decls.append(contentsOf: typeDecls)
try withSpan("Render Types") {
for type in skeleton.types {
let typeDecls = try renderSwiftType(type, topLevelDecls: &decls)
decls.append(contentsOf: typeDecls)
}
}
}
if decls.isEmpty {
// No declarations to import
return nil
}

let format = BasicFormat()
return decls.map { $0.formatted(using: format).description }.joined(separator: "\n\n")
return withSpan("Format Import Glue") {
let format = BasicFormat()
return decls.map { $0.formatted(using: format).description }.joined(separator: "\n\n")
}
}

func renderSwiftGlobalGetter(
Expand Down
115 changes: 115 additions & 0 deletions Plugins/BridgeJS/Sources/BridgeJSCore/Misc.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
import class Foundation.FileHandle
import class Foundation.ProcessInfo
import func Foundation.open
import func Foundation.strerror
import var Foundation.errno
import var Foundation.O_WRONLY
import var Foundation.O_CREAT
import var Foundation.O_TRUNC

// MARK: - ProgressReporting

public struct ProgressReporting {
Expand All @@ -20,6 +29,112 @@ public struct ProgressReporting {
}
}

// MARK: - Profiling

/// A simple time-profiler to emit `chrome://tracing` format
public final class Profiling {
nonisolated(unsafe) static var current: Profiling?

let startTime: ContinuousClock.Instant
let clock = ContinuousClock()
let output: @Sendable (String) -> Void
var firstEntry = true

init(output: @Sendable @escaping (String) -> Void) {
self.startTime = ContinuousClock.now
self.output = output
}

public static func with(body: @escaping () throws -> Void) rethrows -> Void {
guard let outputPath = ProcessInfo.processInfo.environment["BRIDGE_JS_PROFILING"] else {
return try body()
}
let fd = open(outputPath, O_WRONLY | O_CREAT | O_TRUNC, 0o644)
guard fd >= 0 else {
let error = String(cString: strerror(errno))
fatalError("Failed to open profiling output file \(outputPath): \(error)")
}
let output = FileHandle(fileDescriptor: fd, closeOnDealloc: true)
let profiling = Profiling(output: { output.write($0.data(using: .utf8) ?? Data()) })
defer {
profiling.output("]\n")
}
Profiling.current = profiling
defer {
Profiling.current = nil
}
return try body()
}

private func formatTimestamp(instant: ContinuousClock.Instant) -> Int {
let duration = self.startTime.duration(to: instant)
let (seconds, attoseconds) = duration.components
// Convert to microseconds
return Int(seconds * 1_000_000 + attoseconds / 1_000_000_000_000)
}

func begin(_ label: String, _ instant: ContinuousClock.Instant) {
let entry = #"{"ph":"B","pid":1,"name":\#(JSON.serialize(label)),"ts":\#(formatTimestamp(instant: instant))}"#
if firstEntry {
firstEntry = false
output("[\n\(entry)")
} else {
output(",\n\(entry)")
}
}

func end(_ label: String, _ instant: ContinuousClock.Instant) {
let entry = #"{"ph":"E","pid":1,"name":\#(JSON.serialize(label)),"ts":\#(formatTimestamp(instant: instant))}"#
output(",\n\(entry)")
}
}

/// Mark a span of code with a label and measure the duration.
public func withSpan<T>(_ label: String, body: @escaping () throws -> T) rethrows -> T {
guard let profiling = Profiling.current else {
return try body()
}
profiling.begin(label, profiling.clock.now)
defer {
profiling.end(label, profiling.clock.now)
}
return try body()
}

/// Foundation-less JSON serialization
private enum JSON {
static func serialize(_ value: String) -> String {
// https://www.ietf.org/rfc/rfc4627.txt
var output = "\""
for scalar in value.unicodeScalars {
switch scalar {
case "\"":
output += "\\\""
case "\\":
output += "\\\\"
case "\u{08}":
output += "\\b"
case "\u{0C}":
output += "\\f"
case "\n":
output += "\\n"
case "\r":
output += "\\r"
case "\t":
output += "\\t"
case "\u{20}"..."\u{21}", "\u{23}"..."\u{5B}", "\u{5D}"..."\u{10FFFF}":
output.unicodeScalars.append(scalar)
default:
var hex = String(scalar.value, radix: 16, uppercase: true)
hex = String(repeating: "0", count: 4 - hex.count) + hex
output += "\\u" + hex
}
}
output += "\""
return output
}
}

// MARK: - DiagnosticError

import SwiftSyntax
Expand Down
Loading
Loading