Skip to content

Commit afa6857

Browse files
committed
BridgeJS: Register core generic type handles in JavaScriptKit
1 parent e5ddc9f commit afa6857

106 files changed

Lines changed: 249 additions & 558 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Benchmarks/Sources/Generated/BridgeJS.swift

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2275,21 +2275,6 @@ fileprivate func _bjs_Benchmarks_register_type_handles_extern(_ base: UnsafePoin
22752275
@_expose(wasm, "bjs_Benchmarks_register_type_handles")
22762276
public func _bjs_Benchmarks_register_type_handles() {
22772277
let typeIds: [Int32] = [
2278-
Bool.bridgeJSTypeID,
2279-
Int.bridgeJSTypeID,
2280-
Int8.bridgeJSTypeID,
2281-
UInt8.bridgeJSTypeID,
2282-
Int16.bridgeJSTypeID,
2283-
UInt16.bridgeJSTypeID,
2284-
Int32.bridgeJSTypeID,
2285-
UInt32.bridgeJSTypeID,
2286-
UInt.bridgeJSTypeID,
2287-
Int64.bridgeJSTypeID,
2288-
UInt64.bridgeJSTypeID,
2289-
Float.bridgeJSTypeID,
2290-
Double.bridgeJSTypeID,
2291-
String.bridgeJSTypeID,
2292-
JSValue.bridgeJSTypeID,
22932278
SimpleStruct.bridgeJSTypeID,
22942279
Address.bridgeJSTypeID,
22952280
Person.bridgeJSTypeID,

Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.swift

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -295,21 +295,6 @@ fileprivate func _bjs_PlayBridgeJS_register_type_handles_extern(_ base: UnsafePo
295295
@_expose(wasm, "bjs_PlayBridgeJS_register_type_handles")
296296
public func _bjs_PlayBridgeJS_register_type_handles() {
297297
let typeIds: [Int32] = [
298-
Bool.bridgeJSTypeID,
299-
Int.bridgeJSTypeID,
300-
Int8.bridgeJSTypeID,
301-
UInt8.bridgeJSTypeID,
302-
Int16.bridgeJSTypeID,
303-
UInt16.bridgeJSTypeID,
304-
Int32.bridgeJSTypeID,
305-
UInt32.bridgeJSTypeID,
306-
UInt.bridgeJSTypeID,
307-
Int64.bridgeJSTypeID,
308-
UInt64.bridgeJSTypeID,
309-
Float.bridgeJSTypeID,
310-
Double.bridgeJSTypeID,
311-
String.bridgeJSTypeID,
312-
JSValue.bridgeJSTypeID,
313298
PlayBridgeJSOutput.bridgeJSTypeID,
314299
PlayBridgeJSDiagnostic.bridgeJSTypeID,
315300
PlayBridgeJSResult.bridgeJSTypeID,

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,10 @@ struct GenericConformanceCodegen {
908908
/// registered type's `bridgeJSTypeID` into a buffer, in the canonical order of
909909
/// `BridgeJSSkeleton.typeRegistrationEntries`, and passes it to the JS import
910910
/// hook of the same name, which pairs the IDs with its codec array by index.
911+
///
912+
/// Only the module's own `@JS` types are listed; the core (primitive) handles are
913+
/// registered once by the JavaScriptKit library itself
914+
/// (`_bjs_core_register_type_handles`).
911915
public struct GenericTypeRegistrationCodegen {
912916
public init() {}
913917

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,11 @@ public struct BridgeJSLink {
348348
declarations.append(" return;")
349349
declarations.append(" }")
350350
declarations.append(" __bjs_typeHandlesRegistered = true;")
351+
// The core (primitive) handles live in the JavaScriptKit library, so
352+
// they are registered once here rather than by every module.
353+
declarations.append(
354+
" \(JSGlueVariableScope.reservedInstance).exports[\"\(ABINameGenerator.coreTypeRegistrationFunctionName)\"]();"
355+
)
351356
for skeleton in skeletons {
352357
guard skeleton.typeRegistrationEntries != nil else { continue }
353358
let name = ABINameGenerator.typeRegistrationFunctionName(moduleName: skeleton.moduleName)
@@ -426,46 +431,70 @@ public struct BridgeJSLink {
426431
)
427432
}
428433

434+
/// Pairs the type IDs Swift pushed with codecs in the matching skeleton order.
435+
private func writeTypeHandleRegistrationBody(into printer: CodeFragmentPrinter) {
436+
printer.write(
437+
"const typeIds = new Int32Array(\(JSGlueVariableScope.reservedMemory).buffer, base >>> 0, count >>> 0);"
438+
)
439+
printer.write("for (let i = 0; i < count; i++) {")
440+
printer.indent {
441+
printer.write("\(JSGlueVariableScope.reservedCodecByTypeId).set(typeIds[i], codecs[i]);")
442+
}
443+
printer.write("}")
444+
}
445+
446+
/// Installs the `bjs_core_register_type_handles` hook. The core handles are
447+
/// owned by the JavaScriptKit library rather than by generated code, so the
448+
/// wasm import exists in every binary that links JavaScriptKit and the hook
449+
/// is always installed; without generics anywhere in the build it is a no-op
450+
/// and the registration export is never called.
451+
private func generateCoreTypeRegistrationHook(into printer: CodeFragmentPrinter) throws {
452+
let hookName = ABINameGenerator.coreTypeRegistrationFunctionName
453+
guard hasGenerics else {
454+
printer.write("bjs[\"\(hookName)\"] = function() {};")
455+
return
456+
}
457+
try ContainerCodecJS.registerPrimitiveCodecs(context: makeCodecPrintContext(printer: printer))
458+
printer.write("bjs[\"\(hookName)\"] = function(base, count) {")
459+
printer.indent {
460+
// Same canonical order as `_bjs_core_register_type_handles` in the
461+
// JavaScriptKit library.
462+
printer.write("const codecs = [")
463+
printer.indent {
464+
for primitive in BridgeType.genericBridgeablePrimitives {
465+
printer.write("\(JSGlueVariableScope.reservedPrimitiveCodecs).\(primitive.token),")
466+
}
467+
}
468+
printer.write("];")
469+
writeTypeHandleRegistrationBody(into: printer)
470+
}
471+
printer.write("}")
472+
}
473+
429474
/// Installs the per-module `bjs_<Module>_register_type_handles` import
430475
/// hooks. A module with a registration function always carries the wasm
431476
/// import, so a hook is always installed; without generics anywhere in the
432477
/// build it is a no-op and the registration export is never called.
433478
private func generateTypeRegistrationHooks(into printer: CodeFragmentPrinter) throws {
479+
try generateCoreTypeRegistrationHook(into: printer)
434480
for skeleton in skeletons {
435-
guard skeleton.typeRegistrationEntries != nil else { continue }
481+
guard let moduleEntries = skeleton.typeRegistrationEntries else { continue }
436482
let hookName = ABINameGenerator.typeRegistrationFunctionName(moduleName: skeleton.moduleName)
437483
guard hasGenerics else {
438484
printer.write("bjs[\"\(hookName)\"] = function() {};")
439485
continue
440486
}
441-
// The hooks resolve type IDs against the shared primitive codec table.
442-
try ContainerCodecJS.registerPrimitiveCodecs(context: makeCodecPrintContext(printer: printer))
443-
let moduleEntries = skeleton.exported?.genericBridgeableTypeEntries ?? []
444487
printer.write("bjs[\"\(hookName)\"] = function(base, count) {")
445488
try printer.indent {
446-
// Same canonical order as the Swift registration function:
447-
// primitives first, then the module's own types.
489+
// Same order as the module's Swift registration function.
448490
printer.write("const codecs = [")
449-
printer.indent {
450-
for primitive in BridgeType.genericBridgeablePrimitives {
451-
printer.write("\(JSGlueVariableScope.reservedPrimitiveCodecs).\(primitive.token),")
452-
}
453-
}
454-
printer.write("].concat([")
455491
try printer.indent {
456492
for entry in moduleEntries {
457493
try appendGenericCodecLiteral(type: entry.bridgeType, into: printer)
458494
}
459495
}
460-
printer.write("]);")
461-
printer.write(
462-
"const typeIds = new Int32Array(\(JSGlueVariableScope.reservedMemory).buffer, base >>> 0, count >>> 0);"
463-
)
464-
printer.write("for (let i = 0; i < count; i++) {")
465-
printer.indent {
466-
printer.write("\(JSGlueVariableScope.reservedCodecByTypeId).set(typeIds[i], codecs[i]);")
467-
}
468-
printer.write("}")
496+
printer.write("];")
497+
writeTypeHandleRegistrationBody(into: printer)
469498
}
470499
printer.write("}")
471500
}

Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ public struct ABINameGenerator {
3232
"bjs_\(moduleName)_register_type_handles"
3333
}
3434

35+
/// Name of the core type-handle registration function. Unlike the per-module
36+
/// ones, this is defined once in the JavaScriptKit library (see
37+
/// `_bjs_core_register_type_handles` in `BridgeJSIntrinsics.swift`) so the
38+
/// primitive handles exist exactly once in the final binary and the JS glue
39+
/// registers their codecs once per linked bundle.
40+
public static let coreTypeRegistrationFunctionName = "bjs_core_register_type_handles"
41+
3542
/// Generates ABI name using standardized namespace + context pattern
3643
public static func generateABIName(
3744
baseName: String,
@@ -383,17 +390,17 @@ extension ExportedSkeleton {
383390

384391
extension BridgeJSSkeleton {
385392
/// The ordered list of types this module registers type handles for, or
386-
/// `nil` when it emits no registration function. Primitive handles are
387-
/// library singletons, so every module re-registering them writes the same
388-
/// ID-to-codec pair, and a pure-import build still gets a populated table.
393+
/// `nil` when it emits no registration function.
394+
///
395+
/// Only the module's own `@JS` types appear here: the core (primitive)
396+
/// handles are owned by the JavaScriptKit library, which registers them once
397+
/// for the whole binary via ``ABINameGenerator/coreTypeRegistrationFunctionName``.
398+
/// A module that only *uses* generics therefore needs no registration
399+
/// function of its own.
389400
public var typeRegistrationEntries: [GenericBridgeableTypeEntry]? {
390401
let exportedEntries = exported?.genericBridgeableTypeEntries ?? []
391-
let hasGenericImports = imported?.hasGenericDeclarations ?? false
392-
guard !exportedEntries.isEmpty || hasGenericImports else { return nil }
393-
let primitives = BridgeType.genericBridgeablePrimitives.map {
394-
GenericBridgeableTypeEntry(swiftName: $0.token, bridgeType: $0.type)
395-
}
396-
return primitives + exportedEntries
402+
guard !exportedEntries.isEmpty else { return nil }
403+
return exportedEntries
397404
}
398405
}
399406

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import Foundation
2+
import Testing
3+
4+
@testable import BridgeJSSkeleton
5+
6+
/// The core (primitive) generic type handles are registered by the JavaScriptKit
7+
/// library itself, not by generated code, so the ordering contract between the
8+
/// Swift buffer and the JS codec array spans two repositories' worth of source:
9+
/// `_bjs_core_register_type_handles` in `Sources/JavaScriptKit/BridgeJSIntrinsics.swift`
10+
/// and `BridgeType.genericBridgeablePrimitives` here.
11+
///
12+
/// The generated glue checks the *count* at registration time; this test checks
13+
/// the *order* at build time so a reordering cannot silently mis-pair codecs.
14+
@Suite struct CoreTypeRegistrationContractTests {
15+
private static var repositoryRoot: URL {
16+
URL(fileURLWithPath: #filePath)
17+
.deletingLastPathComponent() // BridgeJSToolTests
18+
.deletingLastPathComponent() // Tests
19+
.deletingLastPathComponent() // BridgeJS
20+
.deletingLastPathComponent() // Plugins
21+
.deletingLastPathComponent() // <repo root>
22+
}
23+
24+
@Test
25+
func coreTypeHandleOrderMatchesGenericBridgeablePrimitives() throws {
26+
let intrinsics = Self.repositoryRoot
27+
.appendingPathComponent("Sources/JavaScriptKit/BridgeJSIntrinsics.swift")
28+
let source = try String(contentsOf: intrinsics, encoding: .utf8)
29+
30+
let beginMarker = "// BEGIN bjs_core_type_handles"
31+
let endMarker = "// END bjs_core_type_handles"
32+
guard let begin = source.range(of: beginMarker), let end = source.range(of: endMarker) else {
33+
Issue.record("Could not find the core type handle list markers in \(intrinsics.path)")
34+
return
35+
}
36+
37+
let names =
38+
source[begin.upperBound..<end.lowerBound]
39+
.split(separator: "\n")
40+
.compactMap { line -> String? in
41+
let trimmed = line.trimmingCharacters(in: .whitespaces)
42+
guard trimmed.hasSuffix(".bridgeJSTypeID,") else { return nil }
43+
return String(trimmed.dropLast(".bridgeJSTypeID,".count))
44+
}
45+
46+
#expect(names == BridgeType.genericBridgeablePrimitives.map(\.token))
47+
}
48+
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Alias.swift

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -415,21 +415,6 @@ fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePoin
415415
@_expose(wasm, "bjs_TestModule_register_type_handles")
416416
public func _bjs_TestModule_register_type_handles() {
417417
let typeIds: [Int32] = [
418-
Bool.bridgeJSTypeID,
419-
Int.bridgeJSTypeID,
420-
Int8.bridgeJSTypeID,
421-
UInt8.bridgeJSTypeID,
422-
Int16.bridgeJSTypeID,
423-
UInt16.bridgeJSTypeID,
424-
Int32.bridgeJSTypeID,
425-
UInt32.bridgeJSTypeID,
426-
UInt.bridgeJSTypeID,
427-
Int64.bridgeJSTypeID,
428-
UInt64.bridgeJSTypeID,
429-
Float.bridgeJSTypeID,
430-
Double.bridgeJSTypeID,
431-
String.bridgeJSTypeID,
432-
JSValue.bridgeJSTypeID,
433418
PolygonReference.bridgeJSTypeID,
434419
TagReference.bridgeJSTypeID,
435420
InnerTag.bridgeJSTypeID,

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/AliasInClosure.swift

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -200,21 +200,6 @@ fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePoin
200200
@_expose(wasm, "bjs_TestModule_register_type_handles")
201201
public func _bjs_TestModule_register_type_handles() {
202202
let typeIds: [Int32] = [
203-
Bool.bridgeJSTypeID,
204-
Int.bridgeJSTypeID,
205-
Int8.bridgeJSTypeID,
206-
UInt8.bridgeJSTypeID,
207-
Int16.bridgeJSTypeID,
208-
UInt16.bridgeJSTypeID,
209-
Int32.bridgeJSTypeID,
210-
UInt32.bridgeJSTypeID,
211-
UInt.bridgeJSTypeID,
212-
Int64.bridgeJSTypeID,
213-
UInt64.bridgeJSTypeID,
214-
Float.bridgeJSTypeID,
215-
Double.bridgeJSTypeID,
216-
String.bridgeJSTypeID,
217-
JSValue.bridgeJSTypeID,
218203
PolygonReference.bridgeJSTypeID,
219204
]
220205
typeIds.withUnsafeBufferPointer { buffer in

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.swift

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -664,21 +664,6 @@ fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePoin
664664
@_expose(wasm, "bjs_TestModule_register_type_handles")
665665
public func _bjs_TestModule_register_type_handles() {
666666
let typeIds: [Int32] = [
667-
Bool.bridgeJSTypeID,
668-
Int.bridgeJSTypeID,
669-
Int8.bridgeJSTypeID,
670-
UInt8.bridgeJSTypeID,
671-
Int16.bridgeJSTypeID,
672-
UInt16.bridgeJSTypeID,
673-
Int32.bridgeJSTypeID,
674-
UInt32.bridgeJSTypeID,
675-
UInt.bridgeJSTypeID,
676-
Int64.bridgeJSTypeID,
677-
UInt64.bridgeJSTypeID,
678-
Float.bridgeJSTypeID,
679-
Double.bridgeJSTypeID,
680-
String.bridgeJSTypeID,
681-
JSValue.bridgeJSTypeID,
682667
Point.bridgeJSTypeID,
683668
Direction.bridgeJSTypeID,
684669
Status.bridgeJSTypeID,

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Async.swift

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -734,21 +734,6 @@ fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePoin
734734
@_expose(wasm, "bjs_TestModule_register_type_handles")
735735
public func _bjs_TestModule_register_type_handles() {
736736
let typeIds: [Int32] = [
737-
Bool.bridgeJSTypeID,
738-
Int.bridgeJSTypeID,
739-
Int8.bridgeJSTypeID,
740-
UInt8.bridgeJSTypeID,
741-
Int16.bridgeJSTypeID,
742-
UInt16.bridgeJSTypeID,
743-
Int32.bridgeJSTypeID,
744-
UInt32.bridgeJSTypeID,
745-
UInt.bridgeJSTypeID,
746-
Int64.bridgeJSTypeID,
747-
UInt64.bridgeJSTypeID,
748-
Float.bridgeJSTypeID,
749-
Double.bridgeJSTypeID,
750-
String.bridgeJSTypeID,
751-
JSValue.bridgeJSTypeID,
752737
AsyncPoint.bridgeJSTypeID,
753738
AsyncDirection.bridgeJSTypeID,
754739
AsyncTheme.bridgeJSTypeID,

0 commit comments

Comments
 (0)