Skip to content

Commit 7188a52

Browse files
committed
BridgeJS: Route container stack ABI emission through shared codecs
The non-generic bridging paths cloned each container's stack ABI (array loops with the typed-array fast path, dictionary key/value ordering, the optional presence-flag protocol) inline into every thunk, while the generic paths already described those shapes once via the codec combinators. Route the non-generic array/dictionary/optional emission through the same combinators so each container's stack ABI is described exactly once and instantiated with an element codec: - __bjs_primitiveCodecs becomes a token-keyed table (Bool, Int, ..., String, JSValue) referenced by both combinator instantiations and the generic type-handle registration hooks (same canonical order). - @js struct elements compose directly with structHelpers.<T> (already codec-shaped); associated-value enums adapt their helper through the new __bjs_enumCodec combinator; other element shapes (case enums, JSObject, heap objects, ...) get a local codec literal built from the single element stack fragment description. - __bjs_optionalCodec gains an isUndefinedOr parameter so the JSUndefinedOr flavor instantiates the same combinator instead of a bespoke inline form; stack-convention optional parameters and returns now route through it too. Nested compositions recurse (e.g. __bjs_arrayCodec(__bjs_optionalCodec(__bjs_primitiveCodecs.Int))). - Emission gating: the combinators and the primitive codec table are emitted through the intrinsic registry whenever any linked module uses container bridging (generic or not); builds bridging no containers pay nothing. The generic runtime (codecByTypeId, registration hooks, afterInitialize) stays gated on generics, independently. - Paths that cannot use the combinators keep inline code with comments: side-channel/sentinel optional returns, and optional parameters whose presence flag arrives as a wasm parameter rather than on the i32 stack. Deletes the inline array/dictionary loop fragments, stackOptionalLower, and optionalLiftReturnStruct from JSGlueGen; snapshots re-recorded.
1 parent 60403ee commit 7188a52

21 files changed

Lines changed: 6395 additions & 2168 deletions

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 31 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -339,8 +339,6 @@ public struct BridgeJSLink {
339339
]
340340
if hasGenerics {
341341
declarations.append("const \(JSGlueVariableScope.reservedCodecByTypeId) = new Map();")
342-
declarations.append("let \(JSGlueVariableScope.reservedStringCodec);")
343-
declarations.append("let \(JSGlueVariableScope.reservedPrimitiveCodecs);")
344342
declarations.append("let __bjs_typeHandlesRegistered = false;")
345343
// Registration is hybrid eager+lazy. The registration exports
346344
// execute Swift code, which must not happen before WASI
@@ -404,6 +402,16 @@ public struct BridgeJSLink {
404402
printer.write(lines: lines)
405403
}
406404

405+
/// A print context detached from any thunk, used for codec literal emission.
406+
private func makeCodecPrintContext(printer: CodeFragmentPrinter) -> IntrinsicJSFragment.PrintCodeContext {
407+
IntrinsicJSFragment.PrintCodeContext(
408+
scope: JSGlueVariableScope(intrinsicRegistry: intrinsicRegistry),
409+
printer: printer,
410+
hasDirectAccessToSwiftClass: false,
411+
classNamespaces: intrinsicRegistry.classNamespaces
412+
)
413+
}
414+
407415
/// Emits a `{ lower, lift }` codec literal for one bridgeable type.
408416
/// `prefix` is prepended to the opening brace (e.g. an assignment) and
409417
/// `suffix` is appended to the closing brace (e.g. `","` in an array).
@@ -413,51 +421,13 @@ public struct BridgeJSLink {
413421
prefix: String = "",
414422
suffix: String = ","
415423
) throws {
416-
func context() -> IntrinsicJSFragment.PrintCodeContext {
417-
IntrinsicJSFragment.PrintCodeContext(
418-
scope: JSGlueVariableScope(intrinsicRegistry: intrinsicRegistry),
419-
printer: printer,
420-
hasDirectAccessToSwiftClass: false,
421-
classNamespaces: intrinsicRegistry.classNamespaces
422-
)
423-
}
424-
let lowerFragment = try IntrinsicJSFragment.stackLowerFragment(elementType: type)
425-
let liftFragment = try IntrinsicJSFragment.stackLiftFragment(elementType: type)
426-
printer.write("\(prefix){")
427-
try printer.indent {
428-
printer.write("lower: (v) => {")
429-
try printer.indent {
430-
_ = try lowerFragment.printCode(["v"], context())
431-
}
432-
printer.write("},")
433-
printer.write("lift: () => {")
434-
try printer.indent {
435-
let results = try liftFragment.printCode([], context())
436-
printer.write("return \(results[0]);")
437-
}
438-
printer.write("},")
439-
}
440-
printer.write("}\(suffix)")
441-
}
442-
443-
/// Emits the shared primitive codecs. Codec bodies are emitted once and
444-
/// shared by every module's registration hook.
445-
private func generatePrimitiveCodecs(into printer: CodeFragmentPrinter) throws {
446-
let stringCodec = JSGlueVariableScope.reservedStringCodec
447-
// The String codec is named so the dictionary codec combinator can
448-
// lower/lift keys through it.
449-
try appendGenericCodecLiteral(type: .string, into: printer, prefix: "\(stringCodec) = ", suffix: ";")
450-
printer.write("\(JSGlueVariableScope.reservedPrimitiveCodecs) = [")
451-
try printer.indent {
452-
for primitive in BridgeType.genericBridgeablePrimitives {
453-
if case .string = primitive.type {
454-
printer.write("\(stringCodec),")
455-
} else {
456-
try appendGenericCodecLiteral(type: primitive.type, into: printer)
457-
}
458-
}
459-
}
460-
printer.write("];")
424+
try ContainerCodecJS.writeCodecLiteral(
425+
type: type,
426+
into: printer,
427+
context: makeCodecPrintContext(printer: printer),
428+
prefix: prefix,
429+
suffix: suffix
430+
)
461431
}
462432

463433
/// Installs the per-module `bjs_<Module>_register_type_handles` import hooks.
@@ -475,12 +445,20 @@ public struct BridgeJSLink {
475445
printer.write("bjs[\"\(hookName)\"] = function() {};")
476446
continue
477447
}
448+
// The hooks resolve type IDs against the shared primitive codec table.
449+
try ContainerCodecJS.registerPrimitiveCodecs(context: makeCodecPrintContext(printer: printer))
478450
let moduleEntries = skeleton.exported?.genericBridgeableTypeEntries ?? []
479451
printer.write("bjs[\"\(hookName)\"] = function(base, count) {")
480452
try printer.indent {
481453
// Same canonical order as the Swift registration function:
482454
// primitives first, then the module's own types.
483-
printer.write("const codecs = \(JSGlueVariableScope.reservedPrimitiveCodecs).concat([")
455+
printer.write("const codecs = [")
456+
printer.indent {
457+
for primitive in BridgeType.genericBridgeablePrimitives {
458+
printer.write("\(JSGlueVariableScope.reservedPrimitiveCodecs).\(primitive.token),")
459+
}
460+
}
461+
printer.write("].concat([")
484462
try printer.indent {
485463
for entry in moduleEntries {
486464
try appendGenericCodecLiteral(type: entry.bridgeType, into: printer)
@@ -676,9 +654,6 @@ public struct BridgeJSLink {
676654
printer.write("}")
677655
}
678656
}
679-
if hasGenerics {
680-
try generatePrimitiveCodecs(into: printer)
681-
}
682657
try generateTypeRegistrationHooks(into: printer)
683658

684659
// Always provided: the runtime's `_bjs_makePromise` imports it unconditionally.
@@ -2556,6 +2531,11 @@ extension BridgeJSLink {
25562531
}
25572532

25582533
func declareGenericCodecs(genericParameters: [String]) {
2534+
if !genericParameters.isEmpty {
2535+
// Generic call sites instantiate the shared container codec
2536+
// combinators with the codecs resolved from type IDs.
2537+
ContainerCodecJS.registerCombinators(scope: scope)
2538+
}
25592539
for genericParam in genericParameters {
25602540
let typeIdParam = scope.variable("\(genericParam.lowercased())TypeId")
25612541
let codecVar = scope.variable("codec\(genericParam)")

0 commit comments

Comments
 (0)