Skip to content

Commit c336a3f

Browse files
[NFC] BridgeJS: Make JSGlueGen and IntrinsicJSFragment methods throwable (#606)
* [NFC] BridgeJS: Make JSGlueGen and IntrinsicJSFragment methods throwable Instead of crashing on errors in JS code generation for BridgeJSLink, we should prefer propagating errors. * BridgeJS: Build fixes for profiling API
2 parents 235a9bd + 3d9678a commit c336a3f

File tree

5 files changed

+249
-223
lines changed

5 files changed

+249
-223
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/Misc.swift

Lines changed: 53 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,3 @@
1-
import class Foundation.FileHandle
2-
import class Foundation.ProcessInfo
3-
import func Foundation.open
4-
import func Foundation.strerror
5-
import var Foundation.errno
6-
import var Foundation.O_WRONLY
7-
import var Foundation.O_CREAT
8-
import var Foundation.O_TRUNC
9-
101
// MARK: - ProgressReporting
112

123
public struct ProgressReporting {
@@ -31,61 +22,66 @@ public struct ProgressReporting {
3122

3223
// MARK: - Profiling
3324

34-
/// A simple time-profiler to emit `chrome://tracing` format
25+
/// A simple time-profiler API
3526
public final class Profiling {
3627
nonisolated(unsafe) static var current: Profiling?
3728

38-
let startTime: ContinuousClock.Instant
39-
let clock = ContinuousClock()
40-
let output: @Sendable (String) -> Void
41-
var firstEntry = true
42-
43-
init(output: @Sendable @escaping (String) -> Void) {
44-
self.startTime = ContinuousClock.now
45-
self.output = output
46-
}
47-
48-
public static func with(body: @escaping () throws -> Void) rethrows -> Void {
49-
guard let outputPath = ProcessInfo.processInfo.environment["BRIDGE_JS_PROFILING"] else {
50-
return try body()
29+
let beginEntry: (_ label: String) -> Void
30+
let endEntry: (_ label: String) -> Void
31+
let finalize: () -> Void
32+
33+
/// Create a profiling instance that outputs Trace Event Format, which
34+
/// can be viewed in chrome://tracing or other compatible viewers.
35+
/// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit?usp=sharing
36+
public static func traceEvent(output: @escaping (String) -> Void) -> Profiling {
37+
let clock = ContinuousClock()
38+
let startTime = clock.now
39+
var firstEntry = true
40+
41+
func formatTimestamp() -> Int {
42+
let duration = startTime.duration(to: .now)
43+
let (seconds, attoseconds) = duration.components
44+
// Convert to microseconds
45+
return Int(seconds * 1_000_000 + attoseconds / 1_000_000_000_000)
5146
}
52-
let fd = open(outputPath, O_WRONLY | O_CREAT | O_TRUNC, 0o644)
53-
guard fd >= 0 else {
54-
let error = String(cString: strerror(errno))
55-
fatalError("Failed to open profiling output file \(outputPath): \(error)")
56-
}
57-
let output = FileHandle(fileDescriptor: fd, closeOnDealloc: true)
58-
let profiling = Profiling(output: { output.write($0.data(using: .utf8) ?? Data()) })
59-
defer {
60-
profiling.output("]\n")
61-
}
62-
Profiling.current = profiling
63-
defer {
64-
Profiling.current = nil
65-
}
66-
return try body()
67-
}
6847

69-
private func formatTimestamp(instant: ContinuousClock.Instant) -> Int {
70-
let duration = self.startTime.duration(to: instant)
71-
let (seconds, attoseconds) = duration.components
72-
// Convert to microseconds
73-
return Int(seconds * 1_000_000 + attoseconds / 1_000_000_000_000)
48+
return Profiling(
49+
beginEntry: { label in
50+
let entry = #"{"ph":"B","pid":1,"name":\#(JSON.serialize(label)),"ts":\#(formatTimestamp())}"#
51+
if firstEntry {
52+
firstEntry = false
53+
output("[\n\(entry)")
54+
} else {
55+
output(",\n\(entry)")
56+
}
57+
},
58+
endEntry: { label in
59+
output(#",\n{"ph":"E","pid":1,"name":\#(JSON.serialize(label)),"ts":\#(formatTimestamp())}"#)
60+
},
61+
finalize: {
62+
output("]\n")
63+
}
64+
)
7465
}
7566

76-
func begin(_ label: String, _ instant: ContinuousClock.Instant) {
77-
let entry = #"{"ph":"B","pid":1,"name":\#(JSON.serialize(label)),"ts":\#(formatTimestamp(instant: instant))}"#
78-
if firstEntry {
79-
firstEntry = false
80-
output("[\n\(entry)")
81-
} else {
82-
output(",\n\(entry)")
83-
}
67+
public init(
68+
beginEntry: @escaping (_ label: String) -> Void,
69+
endEntry: @escaping (_ label: String) -> Void,
70+
finalize: @escaping () -> Void
71+
) {
72+
self.beginEntry = beginEntry
73+
self.endEntry = endEntry
74+
self.finalize = finalize
8475
}
8576

86-
func end(_ label: String, _ instant: ContinuousClock.Instant) {
87-
let entry = #"{"ph":"E","pid":1,"name":\#(JSON.serialize(label)),"ts":\#(formatTimestamp(instant: instant))}"#
88-
output(",\n\(entry)")
77+
public static func with(_ makeCurrent: () -> Profiling?, body: @escaping () throws -> Void) rethrows -> Void {
78+
guard let current = makeCurrent() else {
79+
return try body()
80+
}
81+
defer { current.finalize() }
82+
Profiling.current = current
83+
defer { Profiling.current = nil }
84+
return try body()
8985
}
9086
}
9187

@@ -94,9 +90,9 @@ public func withSpan<T>(_ label: String, body: @escaping () throws -> T) rethrow
9490
guard let profiling = Profiling.current else {
9591
return try body()
9692
}
97-
profiling.begin(label, profiling.clock.now)
93+
profiling.beginEntry(label)
9894
defer {
99-
profiling.end(label, profiling.clock.now)
95+
profiling.endEntry(label)
10096
}
10197
return try body()
10298
}

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -261,19 +261,19 @@ public struct BridgeJSLink {
261261
]
262262
}
263263

264-
private func generateAddImports(needsImportsObject: Bool) -> CodeFragmentPrinter {
264+
private func generateAddImports(needsImportsObject: Bool) throws -> CodeFragmentPrinter {
265265
let printer = CodeFragmentPrinter()
266266
let allStructs = skeletons.compactMap { $0.exported?.structs }.flatMap { $0 }
267267
printer.write("return {")
268-
printer.indent {
268+
try printer.indent {
269269
printer.write(lines: [
270270
"/**",
271271
" * @param {WebAssembly.Imports} importObject",
272272
" */",
273273
"addImports: (importObject, importsContext) => {",
274274
])
275275

276-
printer.indent {
276+
try printer.indent {
277277
printer.write(lines: [
278278
"bjs = {};",
279279
"importObject[\"bjs\"] = bjs;",
@@ -684,20 +684,21 @@ public struct BridgeJSLink {
684684

685685
for signature in closureSignatures.sorted(by: { $0.mangleName < $1.mangleName }) {
686686
let invokeFuncName = "invoke_js_callback_\(moduleName)_\(signature.mangleName)"
687-
printer.write(
688-
lines: generateInvokeFunction(
689-
signature: signature,
690-
functionName: invokeFuncName
691-
)
687+
let invokeLines = try generateInvokeFunction(
688+
signature: signature,
689+
functionName: invokeFuncName
692690
)
691+
printer.write(lines: invokeLines)
693692

694693
let lowerFuncName = "lower_closure_\(moduleName)_\(signature.mangleName)"
695694
let makeFuncName = "make_swift_closure_\(moduleName)_\(signature.mangleName)"
696695
printer.write("bjs[\"\(makeFuncName)\"] = function(boxPtr, file, line) {")
697-
printer.indent {
698-
printer.write(
699-
lines: generateLowerClosureFunction(signature: signature, functionName: lowerFuncName)
696+
try printer.indent {
697+
let lowerLines = try generateLowerClosureFunction(
698+
signature: signature,
699+
functionName: lowerFuncName
700700
)
701+
printer.write(lines: lowerLines)
701702
printer.write(
702703
"return \(JSGlueVariableScope.reservedMakeSwiftClosure)(boxPtr, file, line, \(lowerFuncName));"
703704
)
@@ -714,7 +715,7 @@ public struct BridgeJSLink {
714715
private func generateInvokeFunction(
715716
signature: ClosureSignature,
716717
functionName: String
717-
) -> [String] {
718+
) throws -> [String] {
718719
let printer = CodeFragmentPrinter()
719720
let scope = JSGlueVariableScope(intrinsicRegistry: intrinsicRegistry)
720721
let cleanupCode = CodeFragmentPrinter()
@@ -732,20 +733,20 @@ public struct BridgeJSLink {
732733

733734
printer.nextLine()
734735
printer.write("bjs[\"\(functionName)\"] = function(\(invokeParams.joined(separator: ", "))) {")
735-
printer.indent {
736+
try printer.indent {
736737
printer.write("try {")
737-
printer.indent {
738+
try printer.indent {
738739
printer.write("const callback = \(JSGlueVariableScope.reservedSwift).memory.getObject(callbackId);")
739740

740741
for (index, paramType) in signature.parameters.enumerated() {
741-
let fragment = try! IntrinsicJSFragment.closureLiftParameter(type: paramType)
742+
let fragment = try IntrinsicJSFragment.closureLiftParameter(type: paramType)
742743
let args: [String]
743744
if case .nullable = paramType {
744745
args = ["param\(index)IsSome", "param\(index)Value", "param\(index)"]
745746
} else {
746747
args = ["param\(index)Id", "param\(index)"]
747748
}
748-
_ = fragment.printCode(args, scope, printer, cleanupCode)
749+
_ = try fragment.printCode(args, scope, printer, cleanupCode)
749750
}
750751

751752
let callbackParams = signature.parameters.indices.map { "param\($0)" }.joined(separator: ", ")
@@ -763,14 +764,14 @@ public struct BridgeJSLink {
763764
break
764765
}
765766

766-
let returnFragment = try! IntrinsicJSFragment.closureLowerReturn(type: signature.returnType)
767-
_ = returnFragment.printCode(["result"], scope, printer, cleanupCode)
767+
let returnFragment = try IntrinsicJSFragment.closureLowerReturn(type: signature.returnType)
768+
_ = try returnFragment.printCode(["result"], scope, printer, cleanupCode)
768769
}
769770
printer.write("} catch (error) {")
770-
printer.indent {
771+
try printer.indent {
771772
printer.write("\(JSGlueVariableScope.reservedSetException)?.(error);")
772773
let errorFragment = IntrinsicJSFragment.closureErrorReturn(type: signature.returnType)
773-
_ = errorFragment.printCode([], scope, printer, cleanupCode)
774+
_ = try errorFragment.printCode([], scope, printer, cleanupCode)
774775
}
775776
printer.write("}")
776777
}
@@ -783,21 +784,21 @@ public struct BridgeJSLink {
783784
private func generateLowerClosureFunction(
784785
signature: ClosureSignature,
785786
functionName: String
786-
) -> [String] {
787+
) throws -> [String] {
787788
let printer = CodeFragmentPrinter()
788789
let scope = JSGlueVariableScope(intrinsicRegistry: intrinsicRegistry)
789790
let cleanupCode = CodeFragmentPrinter()
790791

791792
printer.write(
792793
"const \(functionName) = function(\(signature.parameters.indices.map { "param\($0)" }.joined(separator: ", "))) {"
793794
)
794-
printer.indent {
795+
try printer.indent {
795796
var invokeArgs: [String] = ["boxPtr"]
796797

797798
for (index, paramType) in signature.parameters.enumerated() {
798799
let paramName = "param\(index)"
799-
let fragment = try! IntrinsicJSFragment.lowerParameter(type: paramType)
800-
let lowered = fragment.printCode([paramName], scope, printer, cleanupCode)
800+
let fragment = try IntrinsicJSFragment.lowerParameter(type: paramType)
801+
let lowered = try fragment.printCode([paramName], scope, printer, cleanupCode)
801802
invokeArgs.append(contentsOf: lowered)
802803
}
803804

@@ -820,8 +821,8 @@ public struct BridgeJSLink {
820821
}
821822
printer.write("}")
822823

823-
let returnFragment = try! IntrinsicJSFragment.closureLiftReturn(type: signature.returnType)
824-
_ = returnFragment.printCode([invokeResultName], scope, printer, cleanupCode)
824+
let returnFragment = try IntrinsicJSFragment.closureLiftReturn(type: signature.returnType)
825+
_ = try returnFragment.printCode([invokeResultName], scope, printer, cleanupCode)
825826
}
826827
printer.write("};")
827828

@@ -999,7 +1000,7 @@ public struct BridgeJSLink {
9991000
// Main function declaration
10001001
printer.write("export async function createInstantiator(options, \(JSGlueVariableScope.reservedSwift)) {")
10011002

1002-
printer.indent {
1003+
try printer.indent {
10031004
printer.write(lines: generateVariableDeclarations())
10041005

10051006
let bodyPrinter = CodeFragmentPrinter()
@@ -1009,7 +1010,7 @@ public struct BridgeJSLink {
10091010
let structScope = JSGlueVariableScope(intrinsicRegistry: intrinsicRegistry)
10101011
let structCleanup = CodeFragmentPrinter()
10111012
let fragment = IntrinsicJSFragment.structHelper(structDefinition: structDef, allStructs: allStructs)
1012-
_ = fragment.printCode([structDef.name], structScope, structPrinter, structCleanup)
1013+
_ = try fragment.printCode([structDef.name], structScope, structPrinter, structCleanup)
10131014
bodyPrinter.write(lines: structPrinter.lines)
10141015
}
10151016

@@ -1021,11 +1022,11 @@ public struct BridgeJSLink {
10211022
let enumScope = JSGlueVariableScope(intrinsicRegistry: intrinsicRegistry)
10221023
let enumCleanup = CodeFragmentPrinter()
10231024
let fragment = IntrinsicJSFragment.associatedValueEnumHelperFactory(enumDefinition: enumDef)
1024-
_ = fragment.printCode([enumDef.valuesName], enumScope, enumPrinter, enumCleanup)
1025+
_ = try fragment.printCode([enumDef.valuesName], enumScope, enumPrinter, enumCleanup)
10251026
bodyPrinter.write(lines: enumPrinter.lines)
10261027
}
10271028
bodyPrinter.nextLine()
1028-
bodyPrinter.write(contentsOf: generateAddImports(needsImportsObject: data.needsImportsObject))
1029+
bodyPrinter.write(contentsOf: try generateAddImports(needsImportsObject: data.needsImportsObject))
10291030

10301031
if !intrinsicRegistry.isEmpty {
10311032
printer.write(lines: intrinsicRegistry.emitLines())
@@ -1282,7 +1283,7 @@ public struct BridgeJSLink {
12821283
loweringFragment.parameters.count == 1,
12831284
"Lowering fragment should have exactly one parameter to lower"
12841285
)
1285-
let loweredValues = loweringFragment.printCode([param.name], scope, body, cleanupCode)
1286+
let loweredValues = try loweringFragment.printCode([param.name], scope, body, cleanupCode)
12861287
parameterForwardings.append(contentsOf: loweredValues)
12871288
}
12881289

@@ -1314,7 +1315,7 @@ public struct BridgeJSLink {
13141315
body.write("const \(returnVariable) = \(call);")
13151316
fragmentArguments = [returnVariable]
13161317
}
1317-
let liftedValues = liftingFragment.printCode(fragmentArguments, scope, body, cleanupCode)
1318+
let liftedValues = try liftingFragment.printCode(fragmentArguments, scope, body, cleanupCode)
13181319
assert(liftedValues.count <= 1, "Lifting fragment should produce at most one value")
13191320
return liftedValues.first
13201321
}
@@ -1572,19 +1573,19 @@ public struct BridgeJSLink {
15721573
switch enumDefinition.enumType {
15731574
case .simple:
15741575
let fragment = IntrinsicJSFragment.simpleEnumHelper(enumDefinition: enumDefinition)
1575-
_ = fragment.printCode([enumValuesName], scope, printer, cleanup)
1576+
_ = try fragment.printCode([enumValuesName], scope, printer, cleanup)
15761577
jsTopLevelLines.append(contentsOf: printer.lines)
15771578
case .rawValue:
15781579
guard enumDefinition.rawType != nil else {
15791580
throw BridgeJSLinkError(message: "Raw value enum \(enumDefinition.name) is missing rawType")
15801581
}
15811582

15821583
let fragment = IntrinsicJSFragment.rawValueEnumHelper(enumDefinition: enumDefinition)
1583-
_ = fragment.printCode([enumValuesName], scope, printer, cleanup)
1584+
_ = try fragment.printCode([enumValuesName], scope, printer, cleanup)
15841585
jsTopLevelLines.append(contentsOf: printer.lines)
15851586
case .associatedValue:
15861587
let fragment = IntrinsicJSFragment.associatedValueEnumValues(enumDefinition: enumDefinition)
1587-
_ = fragment.printCode([enumValuesName], scope, printer, cleanup)
1588+
_ = try fragment.printCode([enumValuesName], scope, printer, cleanup)
15881589
jsTopLevelLines.append(contentsOf: printer.lines)
15891590
case .namespace:
15901591
break
@@ -2178,7 +2179,7 @@ extension BridgeJSLink {
21782179
valuesToLift = liftingFragment.parameters.map { scope.variable(param.name + $0.capitalizedFirstLetter) }
21792180
parameterNames.append(contentsOf: valuesToLift)
21802181
}
2181-
let liftedValues = liftingFragment.printCode(valuesToLift, scope, body, cleanupCode)
2182+
let liftedValues = try liftingFragment.printCode(valuesToLift, scope, body, cleanupCode)
21822183
assert(liftedValues.count == 1, "Lifting fragment should produce exactly one value")
21832184
parameterForwardings.append(contentsOf: liftedValues)
21842185
}
@@ -2288,7 +2289,7 @@ extension BridgeJSLink {
22882289
)
22892290

22902291
let fragment = try IntrinsicJSFragment.protocolPropertyOptionalToSideChannel(wrappedType: wrappedType)
2291-
_ = fragment.printCode([resultVar], scope, body, cleanupCode)
2292+
_ = try fragment.printCode([resultVar], scope, body, cleanupCode)
22922293

22932294
return nil // Side-channel types return nil (no direct return value)
22942295
}
@@ -2341,7 +2342,7 @@ extension BridgeJSLink {
23412342
loweringFragment: IntrinsicJSFragment
23422343
) throws -> String? {
23432344
assert(loweringFragment.parameters.count <= 1, "Lowering fragment should have at most one parameter")
2344-
let loweredValues = loweringFragment.printCode(returnExpr.map { [$0] } ?? [], scope, body, cleanupCode)
2345+
let loweredValues = try loweringFragment.printCode(returnExpr.map { [$0] } ?? [], scope, body, cleanupCode)
23452346
assert(loweredValues.count <= 1, "Lowering fragment should produce at most one value")
23462347
return loweredValues.first
23472348
}

Plugins/BridgeJS/Sources/BridgeJSLink/CodeFragmentPrinter.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ final class JSIntrinsicRegistry {
66
entries.isEmpty
77
}
88

9-
func register(name: String, build: (CodeFragmentPrinter) -> Void) {
9+
func register(name: String, build: (CodeFragmentPrinter) throws -> Void) rethrows {
1010
guard entries[name] == nil else { return }
1111
let printer = CodeFragmentPrinter()
12-
build(printer)
12+
try build(printer)
1313
entries[name] = printer.lines
1414
}
1515

0 commit comments

Comments
 (0)