Skip to content

Commit 1cabe6c

Browse files
committed
BridgeJS: Share named stack codecs across generated glue
1 parent c839ea2 commit 1cabe6c

23 files changed

Lines changed: 1039 additions & 1228 deletions

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -420,22 +420,13 @@ public struct BridgeJSLink {
420420
)
421421
}
422422

423-
/// Emits a `{ lower, lift }` codec literal for one bridgeable type.
424-
/// `prefix` is prepended to the opening brace (e.g. an assignment) and
425-
/// `suffix` is appended to the closing brace (e.g. `","` in an array).
426-
private func appendGenericCodecLiteral(
427-
type: BridgeType,
428-
into printer: CodeFragmentPrinter,
429-
prefix: String = "",
430-
suffix: String = ","
431-
) throws {
432-
try ContainerCodecJS.writeCodecLiteral(
433-
type: type,
434-
into: printer,
435-
context: makeCodecPrintContext(printer: printer),
436-
prefix: prefix,
437-
suffix: suffix
438-
)
423+
/// Returns the module-scope codec helper for one bridgeable type, declaring
424+
/// it if this is the first reference.
425+
///
426+
/// The registration table and the container combinators' element positions
427+
/// go through the same helper, so a type's stack ABI is described once.
428+
private func genericCodecReference(type: BridgeType, into printer: CodeFragmentPrinter) throws -> String {
429+
try ContainerCodecJS.codecExpression(for: type, context: makeCodecPrintContext(printer: printer))
439430
}
440431

441432
/// Writes the body shared by every registration hook: verify the codec array
@@ -509,10 +500,13 @@ public struct BridgeJSLink {
509500
printer.write("bjs[\"\(hookName)\"] = function(base, count) {")
510501
try printer.indent {
511502
// Same order as the module's Swift registration function.
503+
let codecNames = try moduleEntries.map {
504+
try genericCodecReference(type: $0.bridgeType, into: printer)
505+
}
512506
printer.write("const codecs = [")
513-
try printer.indent {
514-
for entry in moduleEntries {
515-
try appendGenericCodecLiteral(type: entry.bridgeType, into: printer)
507+
printer.indent {
508+
for name in codecNames {
509+
printer.write("\(name),")
516510
}
517511
}
518512
printer.write("];")
@@ -1303,6 +1297,18 @@ public struct BridgeJSLink {
13031297
printer.nextLine()
13041298
}
13051299

1300+
// The named codec helpers come after the intrinsics because they are
1301+
// built out of the combinators and the primitive codec table, and
1302+
// before everything that uses them: they are hoisted here so that no
1303+
// call site ever composes a codec. Helpers that delegate to the
1304+
// `structHelpers` / `enumHelpers` tables only read those tables when
1305+
// called, so declaring them ahead of the tables being populated is
1306+
// fine.
1307+
if intrinsicRegistry.hasNamedCodecs {
1308+
printer.write(lines: intrinsicRegistry.emitNamedCodecLines())
1309+
printer.nextLine()
1310+
}
1311+
13061312
printer.write(lines: bodyPrinter.lines)
13071313
}
13081314
printer.indent()
@@ -1406,12 +1412,54 @@ public struct BridgeJSLink {
14061412
}
14071413
}
14081414
}
1415+
intrinsicRegistry.typeOwnerModules = collectTypeOwnerModules()
14091416
let data = try collectLinkData()
14101417
let outputJs = try generateJavaScript(data: data)
14111418
let outputDts = generateTypeScript(data: data)
14121419
return (outputJs, outputDts)
14131420
}
14141421

1422+
/// Maps every type name a `BridgeType` can carry to the module that declares
1423+
/// it, so identifiers minted from type names can be module-qualified.
1424+
///
1425+
/// A name declared by two modules is a pre-existing ambiguity in the
1426+
/// skeleton format (`BridgeType` carries only the name), so the first
1427+
/// declaration wins, which keeps the output deterministic.
1428+
private func collectTypeOwnerModules() -> [String: String] {
1429+
var result: [String: String] = [:]
1430+
func record(_ name: String, _ moduleName: String) {
1431+
if result[name] == nil {
1432+
result[name] = moduleName
1433+
}
1434+
}
1435+
for unified in skeletons {
1436+
let moduleName = unified.moduleName
1437+
if let skeleton = unified.exported {
1438+
for structDef in skeleton.structs {
1439+
record(structDef.name, moduleName)
1440+
record(structDef.abiName, moduleName)
1441+
}
1442+
for klass in skeleton.classes {
1443+
record(klass.name, moduleName)
1444+
record(klass.abiName, moduleName)
1445+
}
1446+
for enumDef in skeleton.enums {
1447+
record(enumDef.name, moduleName)
1448+
record(enumDef.abiName, moduleName)
1449+
}
1450+
for protocolDef in skeleton.protocols {
1451+
record(protocolDef.name, moduleName)
1452+
}
1453+
}
1454+
for file in unified.imported?.children ?? [] {
1455+
for type in file.types {
1456+
record(type.name, moduleName)
1457+
}
1458+
}
1459+
}
1460+
return result
1461+
}
1462+
14151463
private func enumHelperAssignments() -> CodeFragmentPrinter {
14161464
let printer = CodeFragmentPrinter()
14171465

0 commit comments

Comments
 (0)