Skip to content

Commit d7e34a5

Browse files
committed
feat: add opt-in "swift" identity mode with Swift-owned strong cache
Introduces a third value for @js(identityMode:): .swift, alongside the existing .none (default) and .pointer (weak JS-side cache shipped in main). With .swift mode, Swift owns the wrapper lifetime via per-class tables: var _<Class>_identityTable: [UnsafeMutableRawPointer: Int32] // ptr → id var _<Class>_idToPointer: [Int32: UnsafeMutableRawPointer] // id → ptr var _<Class>_wrapperRefs: [Int32] // id → JS ref var _<Class>_freeIds: [Int32] var _<Class>_nextId: Int32 On return, Swift looks up the pointer; on miss it retains, allocates an id, and pushes (id, freshBit) on the existing i32 stack. JS pops the pair, and either returns cache[id] (hit) or builds a wrapper and calls back via bjs_<Class>_register_wrapper to install the retained JS ref. Release is driven by an explicit bjs_<Class>_release_wrapper(id) export. The miss-heavy regression of .pointer mode (FinalizationRegistry.register + new WeakRef + Map.set account for 88% of miss cost per Phase 0 profiling) is addressed: .swift mode keeps no WeakRef, no FinalizationRegistry, no per-miss Map.set — just a dense array indexed by Swift-assigned id. Per-class opt-in via @js(identityMode: .swift); project-wide default via "identityMode": "swift" in bridge-js.config.json. A JSIdentityMode enum replaces the legacy identityMode: Bool macro parameter; true/false literals are still accepted at parse time for backward compatibility. Changes: - New JSIdentityMode enum + macro parameter migration (Bool → enum) - Skeleton.identityMode: Bool? → String?, with per-class and config-default resolution threaded through ExportSwift and BridgeJSLink - New Wasm intrinsic _swift_js_release_ref - ExportSwift emits per-class tables + register/release thunks + bridgeJSStackPush override for array-element returns - BridgeJSLink emits a standalone SwiftIdentityHeapObject template (no SwiftHeapObject inheritance, no FinalizationRegistry, no WeakRef) with a use-after-release guard on every instance member - New BridgeJSSwiftIdentityTests target for config-default opt-in E2E coverage; extends BridgeJSIdentityTests with per-class opt-in scenarios - *SwiftIdentity benchmark variants + three-mode harness in identity-benchmarks.js - DocC article Identity-Modes-For-Exported-Classes.md; cross-references from Exporting-Swift-Class.md, Exporting-Swift-to-JavaScript.md, BridgeJS-Configuration.md
1 parent 34c5d0d commit d7e34a5

39 files changed

Lines changed: 5852 additions & 82 deletions

Benchmarks/Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ let package = Package(
1212
.macCatalyst(.v13),
1313
],
1414
dependencies: [
15-
.package(path: "../")
15+
.package(name: "JavaScriptKit", path: "../")
1616
],
1717
targets: [
1818
.executableTarget(
1919
name: "Benchmarks",
2020
dependencies: [
21-
"JavaScriptKit",
21+
.product(name: "JavaScriptKit", package: "JavaScriptKit"),
2222
.product(name: "JavaScriptFoundationCompat", package: "JavaScriptKit"),
2323
],
2424
exclude: ["Generated/JavaScript", "bridge-js.d.ts"],

Benchmarks/Sources/Benchmarks.swift

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -305,11 +305,11 @@ nonisolated(unsafe) var _cachedPool: [SimpleClass] = []
305305
}
306306
}
307307

308-
// MARK: - Identity Mode Benchmark Variants
309-
// These classes use @JS(identityMode: true) so that identity cache benchmarks
308+
// MARK: - Identity Mode Benchmark Variants (pointer mode)
309+
// These classes use @JS(identityMode: .pointer) so that identity cache benchmarks
310310
// can run in the SAME build alongside the non-identity classes above.
311311

312-
@JS(identityMode: true)
312+
@JS(identityMode: .pointer)
313313
class SimpleClassIdentity {
314314
@JS var name: String
315315
@JS var count: Int
@@ -326,7 +326,7 @@ class SimpleClassIdentity {
326326
}
327327
}
328328

329-
@JS(identityMode: true)
329+
@JS(identityMode: .pointer)
330330
class ClassRoundtripIdentity {
331331
@JS init() {}
332332

@@ -345,7 +345,7 @@ class ClassRoundtripIdentity {
345345

346346
nonisolated(unsafe) var _cachedPoolIdentity: [SimpleClassIdentity] = []
347347

348-
@JS(identityMode: true)
348+
@JS(identityMode: .pointer)
349349
class IdentityCacheBenchmarkIdentity {
350350
@JS init() {}
351351

@@ -360,6 +360,61 @@ class IdentityCacheBenchmarkIdentity {
360360
}
361361
}
362362

363+
// MARK: - Identity Mode Benchmark Variants (swift-owned cache)
364+
// Parallel set using @JS(identityMode: .swift) so the third mode can run
365+
// in the same build alongside the other two.
366+
367+
@JS(identityMode: .swift)
368+
class SimpleClassSwiftIdentity {
369+
@JS var name: String
370+
@JS var count: Int
371+
@JS var flag: Bool
372+
@JS var rate: Float
373+
@JS var precise: Double
374+
375+
@JS init(name: String, count: Int, flag: Bool, rate: Float, precise: Double) {
376+
self.name = name
377+
self.count = count
378+
self.flag = flag
379+
self.rate = rate
380+
self.precise = precise
381+
}
382+
}
383+
384+
@JS(identityMode: .swift)
385+
class ClassRoundtripSwiftIdentity {
386+
@JS init() {}
387+
388+
@JS func roundtripSimpleClassSwiftIdentity(_ obj: SimpleClassSwiftIdentity) -> SimpleClassSwiftIdentity {
389+
return obj
390+
}
391+
392+
@JS func makeSimpleClassSwiftIdentity() -> SimpleClassSwiftIdentity {
393+
return SimpleClassSwiftIdentity(name: "Hello", count: 42, flag: true, rate: 0.5, precise: 3.14159)
394+
}
395+
396+
@JS func takeSimpleClassSwiftIdentity(_ obj: SimpleClassSwiftIdentity) {
397+
// consume without returning
398+
}
399+
}
400+
401+
nonisolated(unsafe) var _cachedPoolSwiftIdentity: [SimpleClassSwiftIdentity] = []
402+
403+
@JS(identityMode: .swift)
404+
class IdentityCacheBenchmarkSwiftIdentity {
405+
@JS init() {}
406+
407+
@JS func setupPool(_ count: Int) {
408+
_cachedPoolSwiftIdentity = (0..<count).map {
409+
SimpleClassSwiftIdentity(name: "Item \($0)", count: $0, flag: true, rate: 0.5, precise: 3.14)
410+
}
411+
}
412+
413+
@JS func getPoolRepeated() -> [SimpleClassSwiftIdentity] {
414+
return _cachedPoolSwiftIdentity
415+
}
416+
}
417+
363418
// MARK: - Array Performance Tests
364419

365420
@JS struct Point {

0 commit comments

Comments
 (0)