Skip to content

Commit 7f70e5c

Browse files
committed
BridgeJS: Hoist stack codecs into named module-scope helpers
Two sites described the same stack ABI twice. Container element positions without a named codec emitted a local `const elemCodec = {lower, lift}` literal, re-declared at every lower/lift site, and the generic type-handle registration emitted an anonymous `{lower, lift}` literal per module type. Emit one named `{lower, lift}` helper per type shape at module scope instead, next to the combinators and the primitive codec table, and have both the element positions and the registration table reference it. Helpers for `@JS` structs and associated-value enums delegate to the existing `structHelpers`/`enumHelpers` entries rather than re-emitting their marshalling; the thin adapter is what lets a helper be a module-scope const even though those tables are populated later, in `createExports`. The associated-value enum combinator is gone: its adapter is now the type's helper. Composed codecs (`[T]`, `T?`, `[String: T]` and their nestings) are hoisted the same way and deduplicated across the whole glue, so a generated thunk no longer builds a codec on every call — previously each call ran e.g. `__bjs_dictCodec(__bjs_optionalCodec(__bjs_primitiveCodecs.Int)).lower(...)`. Helper names derived from type names are qualified with the declaring module, and composed names inherit that qualification from their element, so two modules declaring same-named `@JS` types cannot mint the same identifier.
1 parent db10037 commit 7f70e5c

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)