diff --git a/Benchmarks/Package.swift b/Benchmarks/Package.swift index 751e239e6..496d60d00 100644 --- a/Benchmarks/Package.swift +++ b/Benchmarks/Package.swift @@ -12,13 +12,13 @@ let package = Package( .macCatalyst(.v13), ], dependencies: [ - .package(path: "../") + .package(name: "JavaScriptKit", path: "../") ], targets: [ .executableTarget( name: "Benchmarks", dependencies: [ - "JavaScriptKit", + .product(name: "JavaScriptKit", package: "JavaScriptKit"), .product(name: "JavaScriptFoundationCompat", package: "JavaScriptKit"), ], exclude: ["Generated/JavaScript", "bridge-js.d.ts"], diff --git a/Benchmarks/README.md b/Benchmarks/README.md index 65d867eba..834ddcdbf 100644 --- a/Benchmarks/README.md +++ b/Benchmarks/README.md @@ -35,3 +35,60 @@ node run.js --filter=Call node run.js --filter=/^Property access\// node run.js --filter=/string/i ``` + +## Identity Mode Benchmarks + +Compare `identityMode: "pointer"` vs default (`"none"`) for SwiftHeapObject wrapper caching. Both class variants are compiled into the **same build** via per-class `@JS(identityMode: true)` annotations, so no separate builds or config files are needed. + +Requires `--expose-gc` for memory benchmarks. + +### Build once + +```bash +swift package --swift-sdk $SWIFT_SDK_ID js -c release +``` + +### Compare both modes in one run + +```bash +node --expose-gc run.js --identity-mode=both --identity-iterations=1000000 +``` + +### Run only one mode + +```bash +# Only pointer (identity-cached) classes +node --expose-gc run.js --identity-mode=pointer --identity-iterations=1000000 + +# Only non-identity classes +node --expose-gc run.js --identity-mode=none --identity-iterations=1000000 +``` + +### Additional options + +```bash +# Control iteration count (default: 1000000) +node --expose-gc run.js --identity-mode=both --identity-iterations=500000 + +# Control pool sizes for reuse scenarios (default: 1) +node --expose-gc run.js --identity-mode=both --identity-reuse-pools=1,16 + +# Include memory profiling (heap snapshots before/during/after) +node --expose-gc run.js --identity-mode=both --identity-memory + +# Combine with adaptive sampling +node --expose-gc run.js --identity-mode=both --adaptive + +# Filter to specific identity benchmarks +node --expose-gc run.js --adaptive --filter=passBothWaysRoundtrip --identity-mode=both --identity-iterations=1000000 +``` + +### Identity Mode Scenarios + +| Scenario | What it measures | +|----------|-----------------| +| `passBothWaysRoundtrip` | Same object crossing boundary repeatedly (cache hit path) | +| `getPoolRepeated_100` | Bulk return of 100 cached objects (model collection pattern) | +| `churnObjects` | Create, roundtrip, release cycle (FinalizationRegistry cleanup pressure) | +| `swiftConsumesSameObject` | JS passes same object to Swift repeatedly | +| `swiftCreatesObject` | Fresh object creation overhead (cache miss path) | diff --git a/Benchmarks/Sources/Benchmarks.swift b/Benchmarks/Sources/Benchmarks.swift index 59da8c96c..124ca7481 100644 --- a/Benchmarks/Sources/Benchmarks.swift +++ b/Benchmarks/Sources/Benchmarks.swift @@ -257,6 +257,164 @@ enum ComplexResult { } } +// MARK: - Class Array Performance Tests + +nonisolated(unsafe) var _classArrayPool: [SimpleClass] = [] + +@JS class ClassArrayRoundtrip { + @JS init() {} + + @JS func setupPool(_ count: Int) { + _classArrayPool = (0.. [SimpleClass] { + return _classArrayPool + } + + @JS func makeClassArray() -> [SimpleClass] { + return (0..<100).map { + SimpleClass(name: "Item \($0)", count: $0, flag: true, rate: 0.5, precise: 3.14) + } + } + + @JS func takeClassArray(_ values: [SimpleClass]) {} + + @JS func roundtripClassArray(_ values: [SimpleClass]) -> [SimpleClass] { + return values + } +} + +// MARK: - Identity Cache Benchmark + +nonisolated(unsafe) var _cachedPool: [SimpleClass] = [] + +@JS class IdentityCacheBenchmark { + @JS init() {} + + @JS func setupPool(_ count: Int) { + _cachedPool = (0.. [SimpleClass] { + return _cachedPool + } +} + +// MARK: - Identity Mode Benchmark Variants (pointer mode) +// These classes use @JS(identityMode: .pointer) so that identity cache benchmarks +// can run in the SAME build alongside the non-identity classes above. + +@JS(identityMode: .pointer) +class SimpleClassIdentity { + @JS var name: String + @JS var count: Int + @JS var flag: Bool + @JS var rate: Float + @JS var precise: Double + + @JS init(name: String, count: Int, flag: Bool, rate: Float, precise: Double) { + self.name = name + self.count = count + self.flag = flag + self.rate = rate + self.precise = precise + } +} + +@JS(identityMode: .pointer) +class ClassRoundtripIdentity { + @JS init() {} + + @JS func roundtripSimpleClassIdentity(_ obj: SimpleClassIdentity) -> SimpleClassIdentity { + return obj + } + + @JS func makeSimpleClassIdentity() -> SimpleClassIdentity { + return SimpleClassIdentity(name: "Hello", count: 42, flag: true, rate: 0.5, precise: 3.14159) + } + + @JS func takeSimpleClassIdentity(_ obj: SimpleClassIdentity) { + // consume without returning + } +} + +nonisolated(unsafe) var _cachedPoolIdentity: [SimpleClassIdentity] = [] + +@JS(identityMode: .pointer) +class IdentityCacheBenchmarkIdentity { + @JS init() {} + + @JS func setupPool(_ count: Int) { + _cachedPoolIdentity = (0.. [SimpleClassIdentity] { + return _cachedPoolIdentity + } +} + +// MARK: - Identity Mode Benchmark Variants (swift-owned cache) +// Parallel set using @JS(identityMode: .swift) so the third mode can run +// in the same build alongside the other two. + +@JS(identityMode: .swift) +class SimpleClassSwiftIdentity { + @JS var name: String + @JS var count: Int + @JS var flag: Bool + @JS var rate: Float + @JS var precise: Double + + @JS init(name: String, count: Int, flag: Bool, rate: Float, precise: Double) { + self.name = name + self.count = count + self.flag = flag + self.rate = rate + self.precise = precise + } +} + +@JS(identityMode: .swift) +class ClassRoundtripSwiftIdentity { + @JS init() {} + + @JS func roundtripSimpleClassSwiftIdentity(_ obj: SimpleClassSwiftIdentity) -> SimpleClassSwiftIdentity { + return obj + } + + @JS func makeSimpleClassSwiftIdentity() -> SimpleClassSwiftIdentity { + return SimpleClassSwiftIdentity(name: "Hello", count: 42, flag: true, rate: 0.5, precise: 3.14159) + } + + @JS func takeSimpleClassSwiftIdentity(_ obj: SimpleClassSwiftIdentity) { + // consume without returning + } +} + +nonisolated(unsafe) var _cachedPoolSwiftIdentity: [SimpleClassSwiftIdentity] = [] + +@JS(identityMode: .swift) +class IdentityCacheBenchmarkSwiftIdentity { + @JS init() {} + + @JS func setupPool(_ count: Int) { + _cachedPoolSwiftIdentity = (0.. [SimpleClassSwiftIdentity] { + return _cachedPoolSwiftIdentity + } +} + // MARK: - Array Performance Tests @JS struct Point { diff --git a/Benchmarks/Sources/Generated/BridgeJS.swift b/Benchmarks/Sources/Generated/BridgeJS.swift index c9adb2b1a..2bdb9d90b 100644 --- a/Benchmarks/Sources/Generated/BridgeJS.swift +++ b/Benchmarks/Sources/Generated/BridgeJS.swift @@ -1373,6 +1373,840 @@ fileprivate func _bjs_ClassRoundtrip_wrap_extern(_ pointer: UnsafeMutableRawPoin return _bjs_ClassRoundtrip_wrap_extern(pointer) } +@_expose(wasm, "bjs_ClassArrayRoundtrip_init") +@_cdecl("bjs_ClassArrayRoundtrip_init") +public func _bjs_ClassArrayRoundtrip_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ClassArrayRoundtrip() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassArrayRoundtrip_setupPool") +@_cdecl("bjs_ClassArrayRoundtrip_setupPool") +public func _bjs_ClassArrayRoundtrip_setupPool(_ _self: UnsafeMutableRawPointer, _ count: Int32) -> Void { + #if arch(wasm32) + ClassArrayRoundtrip.bridgeJSLiftParameter(_self).setupPool(_: Int.bridgeJSLiftParameter(count)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassArrayRoundtrip_getPool") +@_cdecl("bjs_ClassArrayRoundtrip_getPool") +public func _bjs_ClassArrayRoundtrip_getPool(_ _self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = ClassArrayRoundtrip.bridgeJSLiftParameter(_self).getPool() + ret.bridgeJSStackPush() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassArrayRoundtrip_makeClassArray") +@_cdecl("bjs_ClassArrayRoundtrip_makeClassArray") +public func _bjs_ClassArrayRoundtrip_makeClassArray(_ _self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = ClassArrayRoundtrip.bridgeJSLiftParameter(_self).makeClassArray() + ret.bridgeJSStackPush() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassArrayRoundtrip_takeClassArray") +@_cdecl("bjs_ClassArrayRoundtrip_takeClassArray") +public func _bjs_ClassArrayRoundtrip_takeClassArray(_ _self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + ClassArrayRoundtrip.bridgeJSLiftParameter(_self).takeClassArray(_: [SimpleClass].bridgeJSStackPop()) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassArrayRoundtrip_roundtripClassArray") +@_cdecl("bjs_ClassArrayRoundtrip_roundtripClassArray") +public func _bjs_ClassArrayRoundtrip_roundtripClassArray(_ _self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = ClassArrayRoundtrip.bridgeJSLiftParameter(_self).roundtripClassArray(_: [SimpleClass].bridgeJSStackPop()) + ret.bridgeJSStackPush() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassArrayRoundtrip_deinit") +@_cdecl("bjs_ClassArrayRoundtrip_deinit") +public func _bjs_ClassArrayRoundtrip_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension ClassArrayRoundtrip: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_ClassArrayRoundtrip_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_ClassArrayRoundtrip_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "Benchmarks", name: "bjs_ClassArrayRoundtrip_wrap") +fileprivate func _bjs_ClassArrayRoundtrip_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_ClassArrayRoundtrip_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_ClassArrayRoundtrip_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_ClassArrayRoundtrip_wrap_extern(pointer) +} + +@_expose(wasm, "bjs_IdentityCacheBenchmark_init") +@_cdecl("bjs_IdentityCacheBenchmark_init") +public func _bjs_IdentityCacheBenchmark_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = IdentityCacheBenchmark() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_IdentityCacheBenchmark_setupPool") +@_cdecl("bjs_IdentityCacheBenchmark_setupPool") +public func _bjs_IdentityCacheBenchmark_setupPool(_ _self: UnsafeMutableRawPointer, _ count: Int32) -> Void { + #if arch(wasm32) + IdentityCacheBenchmark.bridgeJSLiftParameter(_self).setupPool(_: Int.bridgeJSLiftParameter(count)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_IdentityCacheBenchmark_getPoolRepeated") +@_cdecl("bjs_IdentityCacheBenchmark_getPoolRepeated") +public func _bjs_IdentityCacheBenchmark_getPoolRepeated(_ _self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = IdentityCacheBenchmark.bridgeJSLiftParameter(_self).getPoolRepeated() + ret.bridgeJSStackPush() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_IdentityCacheBenchmark_deinit") +@_cdecl("bjs_IdentityCacheBenchmark_deinit") +public func _bjs_IdentityCacheBenchmark_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension IdentityCacheBenchmark: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_IdentityCacheBenchmark_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_IdentityCacheBenchmark_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "Benchmarks", name: "bjs_IdentityCacheBenchmark_wrap") +fileprivate func _bjs_IdentityCacheBenchmark_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_IdentityCacheBenchmark_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_IdentityCacheBenchmark_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_IdentityCacheBenchmark_wrap_extern(pointer) +} + +@_expose(wasm, "bjs_SimpleClassIdentity_init") +@_cdecl("bjs_SimpleClassIdentity_init") +public func _bjs_SimpleClassIdentity_init(_ nameBytes: Int32, _ nameLength: Int32, _ count: Int32, _ flag: Int32, _ rate: Float32, _ precise: Float64) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = SimpleClassIdentity(name: String.bridgeJSLiftParameter(nameBytes, nameLength), count: Int.bridgeJSLiftParameter(count), flag: Bool.bridgeJSLiftParameter(flag), rate: Float.bridgeJSLiftParameter(rate), precise: Double.bridgeJSLiftParameter(precise)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassIdentity_name_get") +@_cdecl("bjs_SimpleClassIdentity_name_get") +public func _bjs_SimpleClassIdentity_name_get(_ _self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = SimpleClassIdentity.bridgeJSLiftParameter(_self).name + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassIdentity_name_set") +@_cdecl("bjs_SimpleClassIdentity_name_set") +public func _bjs_SimpleClassIdentity_name_set(_ _self: UnsafeMutableRawPointer, _ valueBytes: Int32, _ valueLength: Int32) -> Void { + #if arch(wasm32) + SimpleClassIdentity.bridgeJSLiftParameter(_self).name = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassIdentity_count_get") +@_cdecl("bjs_SimpleClassIdentity_count_get") +public func _bjs_SimpleClassIdentity_count_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = SimpleClassIdentity.bridgeJSLiftParameter(_self).count + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassIdentity_count_set") +@_cdecl("bjs_SimpleClassIdentity_count_set") +public func _bjs_SimpleClassIdentity_count_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + SimpleClassIdentity.bridgeJSLiftParameter(_self).count = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassIdentity_flag_get") +@_cdecl("bjs_SimpleClassIdentity_flag_get") +public func _bjs_SimpleClassIdentity_flag_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = SimpleClassIdentity.bridgeJSLiftParameter(_self).flag + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassIdentity_flag_set") +@_cdecl("bjs_SimpleClassIdentity_flag_set") +public func _bjs_SimpleClassIdentity_flag_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + SimpleClassIdentity.bridgeJSLiftParameter(_self).flag = Bool.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassIdentity_rate_get") +@_cdecl("bjs_SimpleClassIdentity_rate_get") +public func _bjs_SimpleClassIdentity_rate_get(_ _self: UnsafeMutableRawPointer) -> Float32 { + #if arch(wasm32) + let ret = SimpleClassIdentity.bridgeJSLiftParameter(_self).rate + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassIdentity_rate_set") +@_cdecl("bjs_SimpleClassIdentity_rate_set") +public func _bjs_SimpleClassIdentity_rate_set(_ _self: UnsafeMutableRawPointer, _ value: Float32) -> Void { + #if arch(wasm32) + SimpleClassIdentity.bridgeJSLiftParameter(_self).rate = Float.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassIdentity_precise_get") +@_cdecl("bjs_SimpleClassIdentity_precise_get") +public func _bjs_SimpleClassIdentity_precise_get(_ _self: UnsafeMutableRawPointer) -> Float64 { + #if arch(wasm32) + let ret = SimpleClassIdentity.bridgeJSLiftParameter(_self).precise + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassIdentity_precise_set") +@_cdecl("bjs_SimpleClassIdentity_precise_set") +public func _bjs_SimpleClassIdentity_precise_set(_ _self: UnsafeMutableRawPointer, _ value: Float64) -> Void { + #if arch(wasm32) + SimpleClassIdentity.bridgeJSLiftParameter(_self).precise = Double.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassIdentity_deinit") +@_cdecl("bjs_SimpleClassIdentity_deinit") +public func _bjs_SimpleClassIdentity_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension SimpleClassIdentity: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_SimpleClassIdentity_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_SimpleClassIdentity_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "Benchmarks", name: "bjs_SimpleClassIdentity_wrap") +fileprivate func _bjs_SimpleClassIdentity_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_SimpleClassIdentity_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_SimpleClassIdentity_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_SimpleClassIdentity_wrap_extern(pointer) +} + +@_expose(wasm, "bjs_ClassRoundtripIdentity_init") +@_cdecl("bjs_ClassRoundtripIdentity_init") +public func _bjs_ClassRoundtripIdentity_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ClassRoundtripIdentity() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassRoundtripIdentity_roundtripSimpleClassIdentity") +@_cdecl("bjs_ClassRoundtripIdentity_roundtripSimpleClassIdentity") +public func _bjs_ClassRoundtripIdentity_roundtripSimpleClassIdentity(_ _self: UnsafeMutableRawPointer, _ obj: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ClassRoundtripIdentity.bridgeJSLiftParameter(_self).roundtripSimpleClassIdentity(_: SimpleClassIdentity.bridgeJSLiftParameter(obj)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassRoundtripIdentity_makeSimpleClassIdentity") +@_cdecl("bjs_ClassRoundtripIdentity_makeSimpleClassIdentity") +public func _bjs_ClassRoundtripIdentity_makeSimpleClassIdentity(_ _self: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ClassRoundtripIdentity.bridgeJSLiftParameter(_self).makeSimpleClassIdentity() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassRoundtripIdentity_takeSimpleClassIdentity") +@_cdecl("bjs_ClassRoundtripIdentity_takeSimpleClassIdentity") +public func _bjs_ClassRoundtripIdentity_takeSimpleClassIdentity(_ _self: UnsafeMutableRawPointer, _ obj: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + ClassRoundtripIdentity.bridgeJSLiftParameter(_self).takeSimpleClassIdentity(_: SimpleClassIdentity.bridgeJSLiftParameter(obj)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassRoundtripIdentity_deinit") +@_cdecl("bjs_ClassRoundtripIdentity_deinit") +public func _bjs_ClassRoundtripIdentity_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension ClassRoundtripIdentity: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_ClassRoundtripIdentity_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_ClassRoundtripIdentity_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "Benchmarks", name: "bjs_ClassRoundtripIdentity_wrap") +fileprivate func _bjs_ClassRoundtripIdentity_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_ClassRoundtripIdentity_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_ClassRoundtripIdentity_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_ClassRoundtripIdentity_wrap_extern(pointer) +} + +@_expose(wasm, "bjs_IdentityCacheBenchmarkIdentity_init") +@_cdecl("bjs_IdentityCacheBenchmarkIdentity_init") +public func _bjs_IdentityCacheBenchmarkIdentity_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = IdentityCacheBenchmarkIdentity() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_IdentityCacheBenchmarkIdentity_setupPool") +@_cdecl("bjs_IdentityCacheBenchmarkIdentity_setupPool") +public func _bjs_IdentityCacheBenchmarkIdentity_setupPool(_ _self: UnsafeMutableRawPointer, _ count: Int32) -> Void { + #if arch(wasm32) + IdentityCacheBenchmarkIdentity.bridgeJSLiftParameter(_self).setupPool(_: Int.bridgeJSLiftParameter(count)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_IdentityCacheBenchmarkIdentity_getPoolRepeated") +@_cdecl("bjs_IdentityCacheBenchmarkIdentity_getPoolRepeated") +public func _bjs_IdentityCacheBenchmarkIdentity_getPoolRepeated(_ _self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = IdentityCacheBenchmarkIdentity.bridgeJSLiftParameter(_self).getPoolRepeated() + ret.bridgeJSStackPush() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_IdentityCacheBenchmarkIdentity_deinit") +@_cdecl("bjs_IdentityCacheBenchmarkIdentity_deinit") +public func _bjs_IdentityCacheBenchmarkIdentity_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension IdentityCacheBenchmarkIdentity: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_IdentityCacheBenchmarkIdentity_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_IdentityCacheBenchmarkIdentity_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "Benchmarks", name: "bjs_IdentityCacheBenchmarkIdentity_wrap") +fileprivate func _bjs_IdentityCacheBenchmarkIdentity_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_IdentityCacheBenchmarkIdentity_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_IdentityCacheBenchmarkIdentity_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_IdentityCacheBenchmarkIdentity_wrap_extern(pointer) +} + +nonisolated(unsafe) var _SimpleClassSwiftIdentity_identityTable: Set = [] + +@_expose(wasm, "bjs_SimpleClassSwiftIdentity_init") +@_cdecl("bjs_SimpleClassSwiftIdentity_init") +public func _bjs_SimpleClassSwiftIdentity_init(_ nameBytes: Int32, _ nameLength: Int32, _ count: Int32, _ flag: Int32, _ rate: Float32, _ precise: Float64) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = SimpleClassSwiftIdentity(name: String.bridgeJSLiftParameter(nameBytes, nameLength), count: Int.bridgeJSLiftParameter(count), flag: Bool.bridgeJSLiftParameter(flag), rate: Float.bridgeJSLiftParameter(rate), precise: Double.bridgeJSLiftParameter(precise)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassSwiftIdentity_name_get") +@_cdecl("bjs_SimpleClassSwiftIdentity_name_get") +public func _bjs_SimpleClassSwiftIdentity_name_get(_ _self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = SimpleClassSwiftIdentity.bridgeJSLiftParameter(_self).name + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassSwiftIdentity_name_set") +@_cdecl("bjs_SimpleClassSwiftIdentity_name_set") +public func _bjs_SimpleClassSwiftIdentity_name_set(_ _self: UnsafeMutableRawPointer, _ valueBytes: Int32, _ valueLength: Int32) -> Void { + #if arch(wasm32) + SimpleClassSwiftIdentity.bridgeJSLiftParameter(_self).name = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassSwiftIdentity_count_get") +@_cdecl("bjs_SimpleClassSwiftIdentity_count_get") +public func _bjs_SimpleClassSwiftIdentity_count_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = SimpleClassSwiftIdentity.bridgeJSLiftParameter(_self).count + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassSwiftIdentity_count_set") +@_cdecl("bjs_SimpleClassSwiftIdentity_count_set") +public func _bjs_SimpleClassSwiftIdentity_count_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + SimpleClassSwiftIdentity.bridgeJSLiftParameter(_self).count = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassSwiftIdentity_flag_get") +@_cdecl("bjs_SimpleClassSwiftIdentity_flag_get") +public func _bjs_SimpleClassSwiftIdentity_flag_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = SimpleClassSwiftIdentity.bridgeJSLiftParameter(_self).flag + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassSwiftIdentity_flag_set") +@_cdecl("bjs_SimpleClassSwiftIdentity_flag_set") +public func _bjs_SimpleClassSwiftIdentity_flag_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + SimpleClassSwiftIdentity.bridgeJSLiftParameter(_self).flag = Bool.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassSwiftIdentity_rate_get") +@_cdecl("bjs_SimpleClassSwiftIdentity_rate_get") +public func _bjs_SimpleClassSwiftIdentity_rate_get(_ _self: UnsafeMutableRawPointer) -> Float32 { + #if arch(wasm32) + let ret = SimpleClassSwiftIdentity.bridgeJSLiftParameter(_self).rate + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassSwiftIdentity_rate_set") +@_cdecl("bjs_SimpleClassSwiftIdentity_rate_set") +public func _bjs_SimpleClassSwiftIdentity_rate_set(_ _self: UnsafeMutableRawPointer, _ value: Float32) -> Void { + #if arch(wasm32) + SimpleClassSwiftIdentity.bridgeJSLiftParameter(_self).rate = Float.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassSwiftIdentity_precise_get") +@_cdecl("bjs_SimpleClassSwiftIdentity_precise_get") +public func _bjs_SimpleClassSwiftIdentity_precise_get(_ _self: UnsafeMutableRawPointer) -> Float64 { + #if arch(wasm32) + let ret = SimpleClassSwiftIdentity.bridgeJSLiftParameter(_self).precise + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassSwiftIdentity_precise_set") +@_cdecl("bjs_SimpleClassSwiftIdentity_precise_set") +public func _bjs_SimpleClassSwiftIdentity_precise_set(_ _self: UnsafeMutableRawPointer, _ value: Float64) -> Void { + #if arch(wasm32) + SimpleClassSwiftIdentity.bridgeJSLiftParameter(_self).precise = Double.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassSwiftIdentity_deinit") +@_cdecl("bjs_SimpleClassSwiftIdentity_deinit") +public func _bjs_SimpleClassSwiftIdentity_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClassSwiftIdentity_release_wrapper") +@_cdecl("bjs_SimpleClassSwiftIdentity_release_wrapper") +public func _bjs_SimpleClassSwiftIdentity_release_wrapper(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + guard _SimpleClassSwiftIdentity_identityTable.remove(pointer) != nil else { return } + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension SimpleClassSwiftIdentity { + @_spi(BridgeJS) @_transparent + public consuming func bridgeJSLowerReturn() -> UnsafeMutableRawPointer { + return withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _SimpleClassSwiftIdentity_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + } + @_spi(BridgeJS) public consuming func bridgeJSStackPush() { + let ptr: UnsafeMutableRawPointer = withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _SimpleClassSwiftIdentity_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + _swift_js_push_pointer(ptr) + } +} + +extension SimpleClassSwiftIdentity: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_SimpleClassSwiftIdentity_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_SimpleClassSwiftIdentity_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "Benchmarks", name: "bjs_SimpleClassSwiftIdentity_wrap") +fileprivate func _bjs_SimpleClassSwiftIdentity_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_SimpleClassSwiftIdentity_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_SimpleClassSwiftIdentity_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_SimpleClassSwiftIdentity_wrap_extern(pointer) +} + +nonisolated(unsafe) var _ClassRoundtripSwiftIdentity_identityTable: Set = [] + +@_expose(wasm, "bjs_ClassRoundtripSwiftIdentity_init") +@_cdecl("bjs_ClassRoundtripSwiftIdentity_init") +public func _bjs_ClassRoundtripSwiftIdentity_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ClassRoundtripSwiftIdentity() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassRoundtripSwiftIdentity_roundtripSimpleClassSwiftIdentity") +@_cdecl("bjs_ClassRoundtripSwiftIdentity_roundtripSimpleClassSwiftIdentity") +public func _bjs_ClassRoundtripSwiftIdentity_roundtripSimpleClassSwiftIdentity(_ _self: UnsafeMutableRawPointer, _ obj: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ClassRoundtripSwiftIdentity.bridgeJSLiftParameter(_self).roundtripSimpleClassSwiftIdentity(_: SimpleClassSwiftIdentity.bridgeJSLiftParameter(obj)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassRoundtripSwiftIdentity_makeSimpleClassSwiftIdentity") +@_cdecl("bjs_ClassRoundtripSwiftIdentity_makeSimpleClassSwiftIdentity") +public func _bjs_ClassRoundtripSwiftIdentity_makeSimpleClassSwiftIdentity(_ _self: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ClassRoundtripSwiftIdentity.bridgeJSLiftParameter(_self).makeSimpleClassSwiftIdentity() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassRoundtripSwiftIdentity_takeSimpleClassSwiftIdentity") +@_cdecl("bjs_ClassRoundtripSwiftIdentity_takeSimpleClassSwiftIdentity") +public func _bjs_ClassRoundtripSwiftIdentity_takeSimpleClassSwiftIdentity(_ _self: UnsafeMutableRawPointer, _ obj: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + ClassRoundtripSwiftIdentity.bridgeJSLiftParameter(_self).takeSimpleClassSwiftIdentity(_: SimpleClassSwiftIdentity.bridgeJSLiftParameter(obj)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassRoundtripSwiftIdentity_deinit") +@_cdecl("bjs_ClassRoundtripSwiftIdentity_deinit") +public func _bjs_ClassRoundtripSwiftIdentity_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassRoundtripSwiftIdentity_release_wrapper") +@_cdecl("bjs_ClassRoundtripSwiftIdentity_release_wrapper") +public func _bjs_ClassRoundtripSwiftIdentity_release_wrapper(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + guard _ClassRoundtripSwiftIdentity_identityTable.remove(pointer) != nil else { return } + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension ClassRoundtripSwiftIdentity { + @_spi(BridgeJS) @_transparent + public consuming func bridgeJSLowerReturn() -> UnsafeMutableRawPointer { + return withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _ClassRoundtripSwiftIdentity_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + } + @_spi(BridgeJS) public consuming func bridgeJSStackPush() { + let ptr: UnsafeMutableRawPointer = withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _ClassRoundtripSwiftIdentity_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + _swift_js_push_pointer(ptr) + } +} + +extension ClassRoundtripSwiftIdentity: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_ClassRoundtripSwiftIdentity_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_ClassRoundtripSwiftIdentity_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "Benchmarks", name: "bjs_ClassRoundtripSwiftIdentity_wrap") +fileprivate func _bjs_ClassRoundtripSwiftIdentity_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_ClassRoundtripSwiftIdentity_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_ClassRoundtripSwiftIdentity_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_ClassRoundtripSwiftIdentity_wrap_extern(pointer) +} + +nonisolated(unsafe) var _IdentityCacheBenchmarkSwiftIdentity_identityTable: Set = [] + +@_expose(wasm, "bjs_IdentityCacheBenchmarkSwiftIdentity_init") +@_cdecl("bjs_IdentityCacheBenchmarkSwiftIdentity_init") +public func _bjs_IdentityCacheBenchmarkSwiftIdentity_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = IdentityCacheBenchmarkSwiftIdentity() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_IdentityCacheBenchmarkSwiftIdentity_setupPool") +@_cdecl("bjs_IdentityCacheBenchmarkSwiftIdentity_setupPool") +public func _bjs_IdentityCacheBenchmarkSwiftIdentity_setupPool(_ _self: UnsafeMutableRawPointer, _ count: Int32) -> Void { + #if arch(wasm32) + IdentityCacheBenchmarkSwiftIdentity.bridgeJSLiftParameter(_self).setupPool(_: Int.bridgeJSLiftParameter(count)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_IdentityCacheBenchmarkSwiftIdentity_getPoolRepeated") +@_cdecl("bjs_IdentityCacheBenchmarkSwiftIdentity_getPoolRepeated") +public func _bjs_IdentityCacheBenchmarkSwiftIdentity_getPoolRepeated(_ _self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = IdentityCacheBenchmarkSwiftIdentity.bridgeJSLiftParameter(_self).getPoolRepeated() + ret.bridgeJSStackPush() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_IdentityCacheBenchmarkSwiftIdentity_deinit") +@_cdecl("bjs_IdentityCacheBenchmarkSwiftIdentity_deinit") +public func _bjs_IdentityCacheBenchmarkSwiftIdentity_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_IdentityCacheBenchmarkSwiftIdentity_release_wrapper") +@_cdecl("bjs_IdentityCacheBenchmarkSwiftIdentity_release_wrapper") +public func _bjs_IdentityCacheBenchmarkSwiftIdentity_release_wrapper(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + guard _IdentityCacheBenchmarkSwiftIdentity_identityTable.remove(pointer) != nil else { return } + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension IdentityCacheBenchmarkSwiftIdentity { + @_spi(BridgeJS) @_transparent + public consuming func bridgeJSLowerReturn() -> UnsafeMutableRawPointer { + return withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _IdentityCacheBenchmarkSwiftIdentity_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + } + @_spi(BridgeJS) public consuming func bridgeJSStackPush() { + let ptr: UnsafeMutableRawPointer = withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _IdentityCacheBenchmarkSwiftIdentity_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + _swift_js_push_pointer(ptr) + } +} + +extension IdentityCacheBenchmarkSwiftIdentity: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_IdentityCacheBenchmarkSwiftIdentity_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_IdentityCacheBenchmarkSwiftIdentity_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "Benchmarks", name: "bjs_IdentityCacheBenchmarkSwiftIdentity_wrap") +fileprivate func _bjs_IdentityCacheBenchmarkSwiftIdentity_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_IdentityCacheBenchmarkSwiftIdentity_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_IdentityCacheBenchmarkSwiftIdentity_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_IdentityCacheBenchmarkSwiftIdentity_wrap_extern(pointer) +} + @_expose(wasm, "bjs_ArrayRoundtrip_init") @_cdecl("bjs_ArrayRoundtrip_init") public func _bjs_ArrayRoundtrip_init() -> UnsafeMutableRawPointer { diff --git a/Benchmarks/Sources/Generated/JavaScript/BridgeJS.json b/Benchmarks/Sources/Generated/JavaScript/BridgeJS.json index b2c33ac01..8f89c720f 100644 --- a/Benchmarks/Sources/Generated/JavaScript/BridgeJS.json +++ b/Benchmarks/Sources/Generated/JavaScript/BridgeJS.json @@ -1270,6 +1270,785 @@ ], "swiftCallName" : "ClassRoundtrip" }, + { + "constructor" : { + "abiName" : "bjs_ClassArrayRoundtrip_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_ClassArrayRoundtrip_setupPool", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "setupPool", + "parameters" : [ + { + "label" : "_", + "name" : "count", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_ClassArrayRoundtrip_getPool", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getPool", + "parameters" : [ + + ], + "returnType" : { + "array" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "SimpleClass" + } + } + } + } + }, + { + "abiName" : "bjs_ClassArrayRoundtrip_makeClassArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "makeClassArray", + "parameters" : [ + + ], + "returnType" : { + "array" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "SimpleClass" + } + } + } + } + }, + { + "abiName" : "bjs_ClassArrayRoundtrip_takeClassArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "takeClassArray", + "parameters" : [ + { + "label" : "_", + "name" : "values", + "type" : { + "array" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "SimpleClass" + } + } + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_ClassArrayRoundtrip_roundtripClassArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundtripClassArray", + "parameters" : [ + { + "label" : "_", + "name" : "values", + "type" : { + "array" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "SimpleClass" + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "SimpleClass" + } + } + } + } + } + ], + "name" : "ClassArrayRoundtrip", + "properties" : [ + + ], + "swiftCallName" : "ClassArrayRoundtrip" + }, + { + "constructor" : { + "abiName" : "bjs_IdentityCacheBenchmark_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_IdentityCacheBenchmark_setupPool", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "setupPool", + "parameters" : [ + { + "label" : "_", + "name" : "count", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_IdentityCacheBenchmark_getPoolRepeated", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getPoolRepeated", + "parameters" : [ + + ], + "returnType" : { + "array" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "SimpleClass" + } + } + } + } + } + ], + "name" : "IdentityCacheBenchmark", + "properties" : [ + + ], + "swiftCallName" : "IdentityCacheBenchmark" + }, + { + "constructor" : { + "abiName" : "bjs_SimpleClassIdentity_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "name", + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "label" : "count", + "name" : "count", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "label" : "flag", + "name" : "flag", + "type" : { + "bool" : { + + } + } + }, + { + "label" : "rate", + "name" : "rate", + "type" : { + "float" : { + + } + } + }, + { + "label" : "precise", + "name" : "precise", + "type" : { + "double" : { + + } + } + } + ] + }, + "identityMode" : "pointer", + "methods" : [ + + ], + "name" : "SimpleClassIdentity", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : false, + "name" : "count", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "isReadonly" : false, + "isStatic" : false, + "name" : "flag", + "type" : { + "bool" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : false, + "name" : "rate", + "type" : { + "float" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : false, + "name" : "precise", + "type" : { + "double" : { + + } + } + } + ], + "swiftCallName" : "SimpleClassIdentity" + }, + { + "constructor" : { + "abiName" : "bjs_ClassRoundtripIdentity_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "identityMode" : "pointer", + "methods" : [ + { + "abiName" : "bjs_ClassRoundtripIdentity_roundtripSimpleClassIdentity", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundtripSimpleClassIdentity", + "parameters" : [ + { + "label" : "_", + "name" : "obj", + "type" : { + "swiftHeapObject" : { + "_0" : "SimpleClassIdentity" + } + } + } + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "SimpleClassIdentity" + } + } + }, + { + "abiName" : "bjs_ClassRoundtripIdentity_makeSimpleClassIdentity", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "makeSimpleClassIdentity", + "parameters" : [ + + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "SimpleClassIdentity" + } + } + }, + { + "abiName" : "bjs_ClassRoundtripIdentity_takeSimpleClassIdentity", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "takeSimpleClassIdentity", + "parameters" : [ + { + "label" : "_", + "name" : "obj", + "type" : { + "swiftHeapObject" : { + "_0" : "SimpleClassIdentity" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + } + ], + "name" : "ClassRoundtripIdentity", + "properties" : [ + + ], + "swiftCallName" : "ClassRoundtripIdentity" + }, + { + "constructor" : { + "abiName" : "bjs_IdentityCacheBenchmarkIdentity_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "identityMode" : "pointer", + "methods" : [ + { + "abiName" : "bjs_IdentityCacheBenchmarkIdentity_setupPool", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "setupPool", + "parameters" : [ + { + "label" : "_", + "name" : "count", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_IdentityCacheBenchmarkIdentity_getPoolRepeated", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getPoolRepeated", + "parameters" : [ + + ], + "returnType" : { + "array" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "SimpleClassIdentity" + } + } + } + } + } + ], + "name" : "IdentityCacheBenchmarkIdentity", + "properties" : [ + + ], + "swiftCallName" : "IdentityCacheBenchmarkIdentity" + }, + { + "constructor" : { + "abiName" : "bjs_SimpleClassSwiftIdentity_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "name", + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "label" : "count", + "name" : "count", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "label" : "flag", + "name" : "flag", + "type" : { + "bool" : { + + } + } + }, + { + "label" : "rate", + "name" : "rate", + "type" : { + "float" : { + + } + } + }, + { + "label" : "precise", + "name" : "precise", + "type" : { + "double" : { + + } + } + } + ] + }, + "identityMode" : "swift", + "methods" : [ + + ], + "name" : "SimpleClassSwiftIdentity", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : false, + "name" : "count", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "isReadonly" : false, + "isStatic" : false, + "name" : "flag", + "type" : { + "bool" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : false, + "name" : "rate", + "type" : { + "float" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : false, + "name" : "precise", + "type" : { + "double" : { + + } + } + } + ], + "swiftCallName" : "SimpleClassSwiftIdentity" + }, + { + "constructor" : { + "abiName" : "bjs_ClassRoundtripSwiftIdentity_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "identityMode" : "swift", + "methods" : [ + { + "abiName" : "bjs_ClassRoundtripSwiftIdentity_roundtripSimpleClassSwiftIdentity", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundtripSimpleClassSwiftIdentity", + "parameters" : [ + { + "label" : "_", + "name" : "obj", + "type" : { + "swiftHeapObject" : { + "_0" : "SimpleClassSwiftIdentity" + } + } + } + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "SimpleClassSwiftIdentity" + } + } + }, + { + "abiName" : "bjs_ClassRoundtripSwiftIdentity_makeSimpleClassSwiftIdentity", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "makeSimpleClassSwiftIdentity", + "parameters" : [ + + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "SimpleClassSwiftIdentity" + } + } + }, + { + "abiName" : "bjs_ClassRoundtripSwiftIdentity_takeSimpleClassSwiftIdentity", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "takeSimpleClassSwiftIdentity", + "parameters" : [ + { + "label" : "_", + "name" : "obj", + "type" : { + "swiftHeapObject" : { + "_0" : "SimpleClassSwiftIdentity" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + } + ], + "name" : "ClassRoundtripSwiftIdentity", + "properties" : [ + + ], + "swiftCallName" : "ClassRoundtripSwiftIdentity" + }, + { + "constructor" : { + "abiName" : "bjs_IdentityCacheBenchmarkSwiftIdentity_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "identityMode" : "swift", + "methods" : [ + { + "abiName" : "bjs_IdentityCacheBenchmarkSwiftIdentity_setupPool", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "setupPool", + "parameters" : [ + { + "label" : "_", + "name" : "count", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_IdentityCacheBenchmarkSwiftIdentity_getPoolRepeated", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getPoolRepeated", + "parameters" : [ + + ], + "returnType" : { + "array" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "SimpleClassSwiftIdentity" + } + } + } + } + } + ], + "name" : "IdentityCacheBenchmarkSwiftIdentity", + "properties" : [ + + ], + "swiftCallName" : "IdentityCacheBenchmarkSwiftIdentity" + }, { "constructor" : { "abiName" : "bjs_ArrayRoundtrip_init", diff --git a/Benchmarks/Sources/bridge-js.config.json b/Benchmarks/Sources/bridge-js.config.json deleted file mode 100644 index 9e26dfeeb..000000000 --- a/Benchmarks/Sources/bridge-js.config.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/Benchmarks/lib/identity-benchmarks.js b/Benchmarks/lib/identity-benchmarks.js new file mode 100644 index 000000000..b193332c5 --- /dev/null +++ b/Benchmarks/lib/identity-benchmarks.js @@ -0,0 +1,273 @@ +/** + * Force a garbage collection cycle if available + */ +function forceGC() { + if (typeof globalThis.gc === 'function') { + globalThis.gc() + } +} + +/** + * Format a byte count as a human-readable string + * @param {number} bytes - Byte count to format + * @returns {string} Formatted string (B, KiB, or MiB) + */ +function formatBytes(bytes) { + if (Math.abs(bytes) < 1024) return `${bytes} B` + if (Math.abs(bytes) < 1024 * 1024) + return `${(bytes / 1024).toFixed(2)} KiB` + return `${(bytes / (1024 * 1024)).toFixed(2)} MiB` +} + +/** + * Parse the --identity-mode CLI argument into mode labels. + * + * Class variants for all three modes are compiled into the same WASM build + * via per-class `@JS(identityMode: .pointer)` and `@JS(identityMode: .swift)` + * annotations, so `both` and `both3` work in a single run. + * + * @param {string} modeArg - Mode argument: off, none, pointer, swift, both (none+pointer), or both3 (none+pointer+swift) + * @returns {string[]} Array of mode labels, or empty for "off" + */ +function parseIdentityModes(modeArg) { + if (!modeArg || modeArg === 'off') return [] + if (modeArg === 'none') return ['none'] + if (modeArg === 'pointer') return ['pointer'] + if (modeArg === 'swift') return ['swift'] + if (modeArg === 'both') return ['none', 'pointer'] + if (modeArg === 'both3') return ['none', 'pointer', 'swift'] + console.error( + `Invalid --identity-mode value: ${modeArg}. Expected off, none, pointer, swift, both, or both3.` + ) + process.exit(1) +} + +/** + * Parse the --identity-reuse-pools CLI argument into pool sizes + * @param {string} value - Comma-separated pool sizes (default: "1") + * @returns {number[]} List of pool sizes + */ +function parseIdentityReusePools(value) { + const raw = (value || '1') + .split(',') + .map((part) => part.trim()) + .filter(Boolean) + const parsed = [] + for (const item of raw) { + const poolSize = Number.parseInt(item, 10) + if (!Number.isInteger(poolSize) || poolSize <= 0) { + console.error( + `Invalid --identity-reuse-pools value: ${value}. Expected comma-separated positive integers.` + ) + process.exit(1) + } + if (!parsed.includes(poolSize)) { + parsed.push(poolSize) + } + } + if (parsed.length === 0) { + console.error( + 'Invalid --identity-reuse-pools value: expected at least one pool size.' + ) + process.exit(1) + } + return parsed +} + +/** + * Capture a memory profile sample for identity mode benchmarks. + * Measures heap cost of holding roundtrip results in an array. + * @param {function} roundtrip - Function that roundtrips an object + * @param {object} baseObject - Base object instance + * @param {number} iterations - Number of roundtrip iterations + * @param {number} sampleInterval - How often to sample heap usage + * @returns {Promise} Memory profile sample + */ +async function captureIdentityMemorySample(roundtrip, baseObject, iterations, sampleInterval) { + const v8 = await import('v8') + forceGC() + const before = process.memoryUsage() + const beforeHeapStats = v8.getHeapStatistics() + let peakHeapUsed = before.heapUsed + + const retained = [] + let current = baseObject + const startedAt = performance.now() + for (let i = 0; i < iterations; i++) { + current = roundtrip(current) + retained.push(current) + if ((i + 1) % sampleInterval === 0) { + peakHeapUsed = Math.max(peakHeapUsed, process.memoryUsage().heapUsed) + } + } + const durationMs = performance.now() - startedAt + + const afterRetain = process.memoryUsage() + peakHeapUsed = Math.max(peakHeapUsed, afterRetain.heapUsed) + retained.length = 0 + + forceGC() + const afterGC = process.memoryUsage() + const afterHeapStats = v8.getHeapStatistics() + + return { + iterations, + durationMs, + heapUsedBefore: before.heapUsed, + heapUsedPeak: peakHeapUsed, + heapUsedAfterRetain: afterRetain.heapUsed, + heapUsedAfterGC: afterGC.heapUsed, + heapSizeLimit: beforeHeapStats.heap_size_limit, + totalHeapSizeBefore: beforeHeapStats.total_heap_size, + totalHeapSizeAfter: afterHeapStats.total_heap_size, + retainedHeapDelta: afterRetain.heapUsed - before.heapUsed, + peakHeapDelta: peakHeapUsed - before.heapUsed, + postGCDelta: afterGC.heapUsed - before.heapUsed, + } +} + +/** + * Run identity mode benchmarks using dual-class strategy. + * + * Both class variants live in the same WASM build: + * - "none" mode uses: SimpleClass, ClassRoundtrip, IdentityCacheBenchmark + * - "pointer" mode uses: SimpleClassIdentity, ClassRoundtripIdentity, IdentityCacheBenchmarkIdentity + * + * @param {object} results - Results object to accumulate benchmark data + * @param {function|null} nameFilter - Optional filter for benchmark names + * @param {object|null} identityConfig - Identity benchmark configuration + * @param {function} benchmarkRunner - Benchmark runner function from singleRun + * @param {object} exports - Exports from the main WASM instance + */ +async function runIdentityModeBenchmarks(results, nameFilter, identityConfig, benchmarkRunner, exports) { + if (!identityConfig || identityConfig.modes.length === 0) return + + for (const mode of identityConfig.modes) { + // Select the right class set based on mode + let classRoundtrip, baseObject, roundtrip, makeSimple, takeSimple + let SimpleClassCtor, IdentityCacheCtor + + if (mode === 'pointer') { + classRoundtrip = new exports.ClassRoundtripIdentity() + baseObject = new exports.SimpleClassIdentity('Hello', 42, true, 0.5, 3.14159) + SimpleClassCtor = exports.SimpleClassIdentity + IdentityCacheCtor = exports.IdentityCacheBenchmarkIdentity + roundtrip = (obj) => classRoundtrip.roundtripSimpleClassIdentity(obj) + makeSimple = () => classRoundtrip.makeSimpleClassIdentity() + takeSimple = (obj) => classRoundtrip.takeSimpleClassIdentity(obj) + } else if (mode === 'swift') { + classRoundtrip = new exports.ClassRoundtripSwiftIdentity() + baseObject = new exports.SimpleClassSwiftIdentity('Hello', 42, true, 0.5, 3.14159) + SimpleClassCtor = exports.SimpleClassSwiftIdentity + IdentityCacheCtor = exports.IdentityCacheBenchmarkSwiftIdentity + roundtrip = (obj) => classRoundtrip.roundtripSimpleClassSwiftIdentity(obj) + makeSimple = () => classRoundtrip.makeSimpleClassSwiftIdentity() + takeSimple = (obj) => classRoundtrip.takeSimpleClassSwiftIdentity(obj) + } else { + classRoundtrip = new exports.ClassRoundtrip() + baseObject = new exports.SimpleClass('Hello', 42, true, 0.5, 3.14159) + SimpleClassCtor = exports.SimpleClass + IdentityCacheCtor = exports.IdentityCacheBenchmark + roundtrip = (obj) => classRoundtrip.roundtripSimpleClass(obj) + makeSimple = () => classRoundtrip.makeSimpleClass() + takeSimple = (obj) => classRoundtrip.takeSimpleClass(obj) + } + + for (const poolSize of identityConfig.reusePools) { + const pool = Array.from({ length: poolSize }, (_, i) => { + return new SimpleClassCtor(`Hello ${i}`, i, true, 0.5, 3.14159) + }) + const testName = poolSize === 1 + ? `IdentityMode/${mode}/passBothWaysRoundtrip` + : `IdentityMode/${mode}/passBothWaysPool${poolSize}` + benchmarkRunner(testName, () => { + for (let i = 0; i < identityConfig.iterations; i++) { + const index = i % poolSize + pool[index] = roundtrip(pool[index]) + } + }) + for (const object of pool) { + object.release() + } + } + + benchmarkRunner(`IdentityMode/${mode}/swiftConsumesSameObject`, () => { + for (let i = 0; i < identityConfig.iterations; i++) { + takeSimple(baseObject) + } + }) + + // Churn scenario: create, roundtrip, release in a tight loop. + benchmarkRunner(`IdentityMode/${mode}/churnObjects`, () => { + for (let i = 0; i < identityConfig.iterations; i++) { + const obj = new SimpleClassCtor(`temp ${i}`, i, true, 0.5, 3.14159) + roundtrip(obj) + obj.release() + } + }) + + // Bulk array return: Swift returns same cached array of 100 objects. + const identityCacheBench = new IdentityCacheCtor() + identityCacheBench.setupPool(100) + identityCacheBench.getPoolRepeated() // warm the cache + benchmarkRunner(`IdentityMode/${mode}/getPoolRepeated_100`, () => { + for (let i = 0; i < Math.floor(identityConfig.iterations / 100); i++) { + identityCacheBench.getPoolRepeated() + } + }) + identityCacheBench.release() + + benchmarkRunner(`IdentityMode/${mode}/swiftCreatesObject`, () => { + for (let i = 0; i < identityConfig.iterations; i++) { + makeSimple() + } + }) + + if (identityConfig.memory) { + const sample = await captureIdentityMemorySample( + roundtrip, baseObject, + identityConfig.iterations, identityConfig.sampleInterval + ) + if (!identityConfig.memorySamples[mode]) { + identityConfig.memorySamples[mode] = [] + } + identityConfig.memorySamples[mode].push(sample) + } + + baseObject.release() + classRoundtrip.release() + } +} + +/** + * Summarize memory profiling results across all identity modes + * @param {object} identityConfig - Identity benchmark configuration with memorySamples + * @returns {object[]} Table rows for console.table display + */ +function summarizeIdentityMemory(identityConfig) { + const rows = [] + for (const mode of identityConfig.modes) { + const samples = identityConfig.memorySamples[mode] || [] + if (samples.length === 0) continue + const avg = (selector) => + samples.reduce((acc, sample) => acc + selector(sample), 0) / samples.length + rows.push({ + Mode: mode, + Samples: samples.length, + Iterations: samples[0].iterations, + 'Avg duration (ms)': avg((s) => s.durationMs).toFixed(2), + 'Avg peak delta': formatBytes(Math.round(avg((s) => s.peakHeapDelta))), + 'Avg retained delta': formatBytes(Math.round(avg((s) => s.retainedHeapDelta))), + 'Avg post-GC delta': formatBytes(Math.round(avg((s) => s.postGCDelta))), + 'Heap limit': formatBytes(samples[0].heapSizeLimit), + }) + } + return rows +} + +export { + parseIdentityModes, + parseIdentityReusePools, + runIdentityModeBenchmarks, + summarizeIdentityMemory, +} diff --git a/Benchmarks/run.js b/Benchmarks/run.js index 5a1ae61e6..f48fa8acf 100644 --- a/Benchmarks/run.js +++ b/Benchmarks/run.js @@ -3,6 +3,7 @@ import { defaultNodeSetup } from "./.build/plugins/PackageToJS/outputs/Package/p import fs from 'fs'; import path from 'path'; import { parseArgs } from "util"; +import { parseIdentityModes, parseIdentityReusePools, runIdentityModeBenchmarks, summarizeIdentityMemory } from "./lib/identity-benchmarks.js" import { APIResultValues as APIResult, ComplexResultValues as ComplexResult } from "./.build/plugins/PackageToJS/outputs/Package/bridge-js.js"; /** @@ -62,17 +63,44 @@ function createNameFilter(arg) { * @returns {number} Coefficient of variation as a percentage */ function calculateCV(values) { - if (values.length < 2) return 0; - - const sum = values.reduce((a, b) => a + b, 0); - const mean = sum / values.length; - - if (mean === 0) return 0; + if (values.length < 2) return 0 + const filtered = removeOutliers(values) + const sum = filtered.reduce((a, b) => a + b, 0) + const mean = sum / filtered.length + if (mean === 0) return 0 + const variance = filtered.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / filtered.length + const stdDev = Math.sqrt(variance) + return (stdDev / mean) * 100 +} - const variance = values.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / values.length; - const stdDev = Math.sqrt(variance); +/** + * Remove outliers using the IQR (interquartile range) method. + * Discards values below Q1-1.5*IQR or above Q3+1.5*IQR. + * Returns the filtered array (or the original if too few samples). + * @param {Array} values - Array of measurement values + * @returns {Array} Values with outliers removed + */ +function removeOutliers(values) { + if (values.length < 4) return values + const sorted = [...values].sort((a, b) => a - b) + const q1 = sorted[Math.floor(sorted.length * 0.25)] + const q3 = sorted[Math.floor(sorted.length * 0.75)] + const iqr = q3 - q1 + const lower = q1 - 1.5 * iqr + const upper = q3 + 1.5 * iqr + const filtered = values.filter(v => v >= lower && v <= upper) + return filtered.length > 0 ? filtered : values +} - return (stdDev / mean) * 100; // Return as percentage +/** + * Calculate the median of an array of numbers + * @param {Array} values - Array of measurement values + * @returns {number} Median value + */ +function median(values) { + const sorted = [...values].sort((a, b) => a - b) + const mid = Math.floor(sorted.length / 2) + return sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2 } /** @@ -85,33 +113,38 @@ function calculateStatistics(results) { const consoleTable = []; for (const [name, times] of Object.entries(results)) { - const sum = times.reduce((a, b) => a + b, 0); - const avg = sum / times.length; - const min = Math.min(...times); - const max = Math.max(...times); - const variance = times.reduce((a, b) => a + Math.pow(b - avg, 2), 0) / times.length; - const stdDev = Math.sqrt(variance); - const cv = (stdDev / avg) * 100; // Coefficient of variation as percentage + const filtered = removeOutliers(times) + const sum = filtered.reduce((a, b) => a + b, 0) + const avg = sum / filtered.length + const med = median(filtered) + const min = Math.min(...filtered) + const max = Math.max(...filtered) + const variance = filtered.reduce((a, b) => a + Math.pow(b - avg, 2), 0) / filtered.length + const stdDev = Math.sqrt(variance) + const cv = (stdDev / avg) * 100 + const outliers = times.length - filtered.length formattedResults[name] = { "avg_ms": parseFloat(avg.toFixed(2)), + "median_ms": parseFloat(med.toFixed(2)), "min_ms": parseFloat(min.toFixed(2)), "max_ms": parseFloat(max.toFixed(2)), "stdDev_ms": parseFloat(stdDev.toFixed(2)), "cv_percent": parseFloat(cv.toFixed(2)), - "samples": times.length, + "samples": filtered.length, + "outliers_removed": outliers, "rawTimes_ms": times.map(t => parseFloat(t.toFixed(2))) - }; + } consoleTable.push({ Test: name, + 'Median (ms)': med.toFixed(2), 'Avg (ms)': avg.toFixed(2), 'Min (ms)': min.toFixed(2), 'Max (ms)': max.toFixed(2), - 'StdDev (ms)': stdDev.toFixed(2), 'CV (%)': cv.toFixed(2), - 'Samples': times.length - }); + 'Samples': filtered.length + (outliers > 0 ? ` (-${outliers})` : '') + }) } return { formattedResults, consoleTable }; @@ -275,14 +308,14 @@ function saveJsonResults(filePath, data) { * @param {number} iterations - Loop iterations per JS benchmark * @returns {Promise} */ -async function singleRun(results, nameFilter, iterations) { +async function singleRun(results, nameFilter, iterations, identityConfig) { const options = await defaultNodeSetup({}) const benchmarkRunner = (name, body) => { if (nameFilter && !nameFilter(name)) { return; } // Warmup to reduce JIT/IC noise. - body(); + body() if (typeof globalThis.gc === "function") { globalThis.gc(); } @@ -869,6 +902,8 @@ async function singleRun(results, nameFilter, iterations) { arrayRoundtrip.roundtripOptionalArray(null) } }) + + await runIdentityModeBenchmarks(results, nameFilter, identityConfig, benchmarkRunner, exports) } /** @@ -878,7 +913,7 @@ async function singleRun(results, nameFilter, iterations) { * @param {number} iterations - Loop iterations per JS benchmark * @returns {Promise} */ -async function runUntilStable(results, options, width, nameFilter, filterArg, iterations) { +async function runUntilStable(results, options, width, nameFilter, filterArg, iterations, identityConfig) { const { minRuns = 5, maxRuns = 50, @@ -897,7 +932,7 @@ async function runUntilStable(results, options, width, nameFilter, filterArg, it // Update progress with estimated completion updateProgress(runs, maxRuns, "Benchmark Progress:", width); - await singleRun(results, nameFilter, iterations); + await singleRun(results, nameFilter, iterations, identityConfig); runs++; if (runs === 1 && Object.keys(results).length === 0) { @@ -968,6 +1003,12 @@ Options: --max-runs=NUMBER Maximum runs for adaptive sampling (default: 50) --target-cv=NUMBER Target coefficient of variation % (default: 5) --filter=PATTERN Filter benchmarks by name (substring or /regex/flags) + --identity-mode=MODE Identity benchmarks: off, none, pointer, both (default: off) + Both class variants are in the same build via per-class + @JS(identityMode: true). Use 'both' to compare side-by-side. + --identity-iterations=N Iterations for identity benchmarks (default: 1000000) + --identity-reuse-pools=N,N Pool sizes for reuse scenarios (default: 1,8,64) + --identity-memory Enable memory profiling for identity benchmarks --help Show this help message `); } @@ -984,7 +1025,11 @@ async function main() { 'min-runs': { type: 'string', default: '5' }, 'max-runs': { type: 'string', default: '50' }, 'target-cv': { type: 'string', default: '5' }, - filter: { type: 'string' } + filter: { type: 'string' }, + 'identity-mode': { type: 'string', default: 'off' }, + 'identity-iterations': { type: 'string', default: '1000000' }, + 'identity-reuse-pools': { type: 'string' }, + 'identity-memory': { type: 'boolean', default: false } } }); @@ -998,6 +1043,17 @@ async function main() { const filterArg = args.values.filter; const nameFilter = createNameFilter(filterArg); + const identityModes = parseIdentityModes(args.values['identity-mode']) + const identityIterations = parseInt(args.values['identity-iterations'], 10) + const identityConfig = identityModes.length > 0 ? { + modes: identityModes, + iterations: identityIterations, + reusePools: parseIdentityReusePools(args.values['identity-reuse-pools']), + memory: args.values['identity-memory'], + memorySamples: {}, + sampleInterval: 10000, + } : null + const iterations = parseInt(args.values.iterations, 10); if (isNaN(iterations) || iterations <= 0) { console.error('Invalid --iterations value:', args.values.iterations); @@ -1017,7 +1073,7 @@ async function main() { console.log(`Results will be saved to: ${args.values.output}`); } - await runUntilStable(results, options, width, nameFilter, filterArg, iterations); + await runUntilStable(results, options, width, nameFilter, filterArg, iterations, identityConfig) } else { // Fixed number of runs mode const runs = parseInt(args.values.runs, 10); @@ -1039,7 +1095,7 @@ async function main() { console.log("\nOverall Progress:"); for (let i = 0; i < runs; i++) { updateProgress(i, runs, "Benchmark Runs:", width); - await singleRun(results, nameFilter, iterations); + await singleRun(results, nameFilter, iterations, identityConfig) if (i === 0 && Object.keys(results).length === 0) { process.stdout.write("\n"); console.error(`No benchmarks matched filter: ${filterArg}`); @@ -1071,6 +1127,15 @@ async function main() { } } + // Identity memory summary + if (identityConfig?.memory) { + const memoryRows = summarizeIdentityMemory(identityConfig) + if (memoryRows.length > 0) { + console.log("\n=== Identity Mode Memory Profile ===") + console.table(memoryRows) + } + } + // Save JSON to file if specified if (args.values.output) { saveJsonResults(args.values.output, formattedResults); diff --git a/Package.swift b/Package.swift index 820524177..c7e76a08e 100644 --- a/Package.swift +++ b/Package.swift @@ -217,5 +217,29 @@ let package = Package( ], linkerSettings: testingLinkerFlags ), + .testTarget( + name: "BridgeJSIdentityTests", + dependencies: ["JavaScriptKit", "JavaScriptEventLoop"], + exclude: [ + "bridge-js.config.json", + "Generated/JavaScript", + ], + swiftSettings: [ + .enableExperimentalFeature("Extern") + ], + linkerSettings: testingLinkerFlags + ), + .testTarget( + name: "BridgeJSSwiftIdentityTests", + dependencies: ["JavaScriptKit", "JavaScriptEventLoop"], + exclude: [ + "bridge-js.config.json", + "Generated/JavaScript", + ], + swiftSettings: [ + .enableExperimentalFeature("Extern") + ], + linkerSettings: testingLinkerFlags + ), ] ) diff --git a/Package@swift-6.1.swift b/Package@swift-6.1.swift index 60adb7a5f..fe98ec529 100644 --- a/Package@swift-6.1.swift +++ b/Package@swift-6.1.swift @@ -206,5 +206,17 @@ let package = Package( ], linkerSettings: testingLinkerFlags ), + .testTarget( + name: "BridgeJSIdentityTests", + dependencies: ["JavaScriptKit", "JavaScriptEventLoop"], + exclude: [ + "bridge-js.config.json", + "Generated/JavaScript", + ], + swiftSettings: [ + .enableExperimentalFeature("Extern") + ], + linkerSettings: testingLinkerFlags + ), ] ) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index b649b244d..55574bf93 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -31,6 +31,20 @@ public class ExportSwift { self.skeleton = skeleton } + /// Whether a class resolves to `identityMode: "swift"`. Per-class annotation + /// wins over the skeleton-wide config default. + func isSwiftIdentityMode(_ className: String) -> Bool { + guard + let klass = skeleton.classes.first(where: { + $0.name == className || $0.swiftCallName == className + }) + else { + return false + } + let resolved = klass.identityMode ?? skeleton.identityMode + return resolved == "swift" + } + /// Finalizes the export process and generates the bridge code /// /// - Parameters: @@ -457,7 +471,9 @@ public class ExportSwift { let className = context.className let isStatic = context.isStatic - let getterBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false, isStatic: isStatic)) + let getterBuilder = ExportedThunkBuilder( + effects: Effects(isAsync: false, isThrows: false, isStatic: isStatic) + ) if !isStatic { try getterBuilder.liftParameter( @@ -652,6 +668,13 @@ public class ExportSwift { func renderSingleExportedClass(klass: ExportedClass) throws -> [DeclSyntax] { var decls: [DeclSyntax] = [] + // .swift identity mode: Swift tracks which pointers have been retained. + if isSwiftIdentityMode(klass.name) { + decls.append( + "nonisolated(unsafe) var _\(raw: klass.name)_identityTable: Set = []" + ) + } + if let constructor = klass.constructor { decls.append( try renderSingleExportedConstructor( @@ -691,6 +714,50 @@ public class ExportSwift { decls.append(DeclSyntax(funcDecl)) } + // .swift identity mode: release thunk + overrides that route every + // return shape (scalar, array element, Optional) through the cache. + if isSwiftIdentityMode(klass.name) { + do { + let releaseDecl = SwiftCodePattern.buildExposedFunctionDecl( + abiName: "bjs_\(klass.abiName)_release_wrapper", + signature: SwiftSignatureBuilder.buildABIFunctionSignature( + abiParameters: [("pointer", .pointer)], + returnType: nil + ) + ) { printer in + printer.write("guard _\(klass.name)_identityTable.remove(pointer) != nil else { return }") + printer.write("Unmanaged<\(klass.swiftCallName)>.fromOpaque(pointer).release()") + } + decls.append(DeclSyntax(releaseDecl)) + } + + let swiftIdentityOverridesExt: DeclSyntax = """ + extension \(raw: klass.swiftCallName) { + @_spi(BridgeJS) @_transparent + public consuming func bridgeJSLowerReturn() -> UnsafeMutableRawPointer { + return withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _\(raw: klass.name)_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + } + @_spi(BridgeJS) public consuming func bridgeJSStackPush() { + let ptr: UnsafeMutableRawPointer = withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _\(raw: klass.name)_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + _swift_js_push_pointer(ptr) + } + } + """ + decls.append(swiftIdentityOverridesExt) + } + // Generate ConvertibleToJSValue extension decls.append(contentsOf: renderConvertibleToJSValueExtension(klass: klass)) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/Misc.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/Misc.swift index 06fb422a9..c28cb0d92 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/Misc.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/Misc.swift @@ -342,22 +342,50 @@ public struct BridgeJSConfig: Codable { /// Default: `false` public var exposeToGlobal: Bool - public init(tools: [String: String]? = nil, exposeToGlobal: Bool = false) { + /// The identity mode to use for exported Swift heap objects. + /// + /// Valid values: `"none"` | `"pointer"` | `"swift"`. + /// + /// - `"none"` (or `nil`): no identity tracking; each boundary crossing produces a fresh JS wrapper. + /// - `"pointer"`: JS-side identity cache keyed by the Swift pointer (weak refs + `FinalizationRegistry`). + /// - `"swift"`: Swift-owned strong identity cache; wrappers live until explicit `release()`. + /// + /// A per-class `@JS(identityMode: ...)` annotation overrides this default. + /// + /// Default: `nil` (treated as `"none"`) + public var identityMode: String? + + public init(tools: [String: String]? = nil, exposeToGlobal: Bool = false, identityMode: String? = nil) { self.tools = tools self.exposeToGlobal = exposeToGlobal + self.identityMode = identityMode } enum CodingKeys: String, CodingKey { case tools case exposeToGlobal + case identityMode } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) tools = try container.decodeIfPresent([String: String].self, forKey: .tools) exposeToGlobal = try container.decodeIfPresent(Bool.self, forKey: .exposeToGlobal) ?? false + identityMode = try container.decodeIfPresent(String.self, forKey: .identityMode) + if let mode = identityMode, !BridgeJSConfig.validIdentityModes.contains(mode) { + throw DecodingError.dataCorruptedError( + forKey: .identityMode, + in: container, + debugDescription: + "Invalid identityMode \"\(mode)\". Expected one of: \(BridgeJSConfig.validIdentityModes.sorted().map { "\"\($0)\"" }.joined(separator: ", "))." + ) + } } + /// The set of accepted values for the top-level `identityMode` config field. + /// Keep in sync with `JSIdentityMode` in `Sources/JavaScriptKit/JSIdentityMode.swift`. + static let validIdentityModes: Set = ["none", "pointer", "swift"] + /// Load the configuration file from the SwiftPM package target directory. /// /// Files are loaded **in this order** and merged (later files override earlier ones): @@ -398,7 +426,8 @@ public struct BridgeJSConfig: Codable { func merging(overrides: BridgeJSConfig) -> BridgeJSConfig { return BridgeJSConfig( tools: (tools ?? [:]).merging(overrides.tools ?? [:], uniquingKeysWith: { $1 }), - exposeToGlobal: overrides.exposeToGlobal + exposeToGlobal: overrides.exposeToGlobal, + identityMode: overrides.identityMode ?? identityMode ) } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift index 7b7c6ca30..a4557a305 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift @@ -16,14 +16,16 @@ public final class SwiftToSkeleton { public let progress: ProgressReporting public let moduleName: String public let exposeToGlobal: Bool + public let identityMode: String? private var sourceFiles: [(sourceFile: SourceFileSyntax, inputFilePath: String)] = [] let typeDeclResolver: TypeDeclResolver - public init(progress: ProgressReporting, moduleName: String, exposeToGlobal: Bool) { + public init(progress: ProgressReporting, moduleName: String, exposeToGlobal: Bool, identityMode: String? = nil) { self.progress = progress self.moduleName = moduleName self.exposeToGlobal = exposeToGlobal + self.identityMode = identityMode self.typeDeclResolver = TypeDeclResolver() // Index known types provided by JavaScriptKit @@ -42,7 +44,13 @@ public final class SwiftToSkeleton { public func finalize() throws -> BridgeJSSkeleton { var perSourceErrors: [(inputFilePath: String, errors: [DiagnosticError])] = [] var importedFiles: [ImportedFileSkeleton] = [] - var exported = ExportedSkeleton(functions: [], classes: [], enums: [], exposeToGlobal: exposeToGlobal) + var exported = ExportedSkeleton( + functions: [], + classes: [], + enums: [], + exposeToGlobal: exposeToGlobal, + identityMode: identityMode + ) var exportCollectors: [ExportSwiftAPICollector] = [] for (sourceFile, inputFilePath) in sourceFiles { @@ -1189,6 +1197,21 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { return nil } + private func extractIdentityMode(from jsAttribute: AttributeSyntax) -> String? { + guard let arguments = jsAttribute.arguments?.as(LabeledExprListSyntax.self), + let identityArg = arguments.first(where: { $0.label?.text == "identityMode" }) + else { return nil } + let text = identityArg.expression.trimmedDescription + // Enum member-access (current API). + if text.contains(".swift") { return "swift" } + if text.contains(".pointer") { return "pointer" } + if text.contains(".none") { return "none" } + // Legacy Bool literals, kept for backward compatibility. + if text == "true" { return "pointer" } + if text == "false" { return "none" } + return nil + } + override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { guard let jsAttribute = node.attributes.firstJSAttribute else { return .skipChildren } @@ -1376,6 +1399,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { for: node, message: "Class visibility must be at least internal" ) + let classIdentityMode = extractIdentityMode(from: jsAttribute) let exportedClass = ExportedClass( name: name, swiftCallName: swiftCallName, @@ -1383,7 +1407,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { constructor: nil, methods: [], properties: [], - namespace: namespaceResult.namespace + namespace: namespaceResult.namespace, + identityMode: classIdentityMode ) let uniqueKey = makeKey(name: name, namespace: namespaceResult.namespace) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 60b34ff16..10441833e 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -25,6 +25,29 @@ public struct BridgeJSLink { self.sharedMemory = sharedMemory } + /// Resolves the identity-mode config default for a class by locating its owning + /// skeleton. Per-skeleton so config defaults don't bleed across modules when + /// multiple targets link together. + private func configIdentityMode(for klass: ExportedClass) -> String { + for unified in skeletons { + guard let exported = unified.exported else { continue } + if exported.classes.contains(where: { $0.name == klass.name && $0.namespace == klass.namespace }) { + return exported.identityMode ?? "none" + } + } + return "none" + } + + private func shouldUseIdentityCache(for klass: ExportedClass) -> Bool { + let resolved = klass.identityMode ?? configIdentityMode(for: klass) + return resolved == "pointer" + } + + private func shouldUseSwiftIdentityCache(for klass: ExportedClass) -> Bool { + let resolved = klass.identityMode ?? configIdentityMode(for: klass) + return resolved == "swift" + } + mutating func addSkeletonFile(data: Data) throws { do { let unified = try JSONDecoder().decode(BridgeJSSkeleton.self, from: data) @@ -85,31 +108,52 @@ public struct BridgeJSLink { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; """ if enableLifetimeTracking { - output += " TRACKING.wrap(pointer, deinit, prototype, state);\n" + output += " TRACKING.wrap(pointer, deinit, prototype, state);\n" } output += """ - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { """ if enableLifetimeTracking { - output += " TRACKING.release(this);\n" + output += " TRACKING.release(this);\n" } output += """ const state = this.__swiftHeapObjectState; @@ -118,6 +162,7 @@ public struct BridgeJSLink { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } @@ -141,13 +186,24 @@ public struct BridgeJSLink { private func collectLinkData() throws -> LinkData { var data = LinkData() - // Swift heap object class definitions - if skeletons.contains(where: { $0.exported?.classes.isEmpty == false }) { + // The JS-side SwiftHeapObject base class is only needed for .none and .pointer + // classes; .swift-mode classes are standalone. The .d.ts declaration is still + // emitted whenever any class exists so the interface shape stays stable. + let hasAnyPointerOrNoneClass = skeletons.contains { unified in + guard let exported = unified.exported else { return false } + return exported.classes.contains { klass in + !shouldUseSwiftIdentityCache(for: klass) + } + } + let hasAnyClass = skeletons.contains(where: { $0.exported?.classes.isEmpty == false }) + if hasAnyPointerOrNoneClass { data.classLines.append( contentsOf: swiftHeapObjectClassJs.split(separator: "\n", omittingEmptySubsequences: false).map { String($0) } ) + } + if hasAnyClass { data.dtsClassLines.append( contentsOf: swiftHeapObjectClassDts.split(separator: "\n", omittingEmptySubsequences: false).map { String($0) @@ -1961,20 +2017,87 @@ extension BridgeJSLink { let dtsTypePrinter = CodeFragmentPrinter() let dtsExportEntryPrinter = CodeFragmentPrinter() + let useIdentity = shouldUseIdentityCache(for: klass) + let useSwiftIdentity = shouldUseSwiftIdentityCache(for: klass) + dtsTypePrinter.write("export interface \(klass.name) extends SwiftHeapObject {") dtsExportEntryPrinter.write("\(klass.name): {") - jsPrinter.write("class \(klass.name) extends SwiftHeapObject {") - // Always add __construct and constructor methods for all classes - jsPrinter.indent { - jsPrinter.write("static __construct(ptr) {") + if useSwiftIdentity { + // Standalone wrapper class with a strong pointer-keyed Map. No + // SwiftHeapObject inheritance, no FinalizationRegistry, no WeakRef. + jsPrinter.write("class \(klass.name) {") jsPrinter.indent { - jsPrinter.write( - "return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_\(klass.abiName)_deinit, \(klass.name).prototype);" - ) + jsPrinter.write("static __swiftIdentityWrappers = new Map();") + jsPrinter.nextLine() + jsPrinter.write("static __wrap(pointer) {") + jsPrinter.indent { + jsPrinter.write("const cached = \(klass.name).__swiftIdentityWrappers.get(pointer);") + jsPrinter.write("if (cached !== undefined) return cached;") + jsPrinter.write("const obj = Object.create(\(klass.name).prototype);") + jsPrinter.write("obj.pointer = pointer;") + jsPrinter.write("obj.__swiftIdentityHasReleased = false;") + jsPrinter.write("\(klass.name).__swiftIdentityWrappers.set(pointer, obj);") + jsPrinter.write("return obj;") + } + jsPrinter.write("}") + jsPrinter.nextLine() + jsPrinter.write("static __construct(pointer) {") + jsPrinter.indent { + jsPrinter.write("return \(klass.name).__wrap(pointer);") + } + jsPrinter.write("}") + jsPrinter.nextLine() + jsPrinter.write("release() {") + jsPrinter.indent { + jsPrinter.write("if (this.__swiftIdentityHasReleased) return;") + jsPrinter.write("this.__swiftIdentityHasReleased = true;") + jsPrinter.write("const pointer = this.pointer;") + jsPrinter.write("instance.exports.bjs_\(klass.abiName)_release_wrapper(pointer);") + jsPrinter.write("\(klass.name).__swiftIdentityWrappers.delete(pointer);") + } + jsPrinter.write("}") + } + } else { + jsPrinter.write("class \(klass.name) extends SwiftHeapObject {") + jsPrinter.indent { + if useIdentity { + jsPrinter.write("static __identityCache = new Map();") + jsPrinter.nextLine() + } + jsPrinter.write("static __construct(ptr) {") + jsPrinter.indent { + if useIdentity { + jsPrinter.write( + "return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_\(klass.abiName)_deinit, \(klass.name).prototype, \(klass.name).__identityCache);" + ) + } else { + jsPrinter.write( + "return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_\(klass.abiName)_deinit, \(klass.name).prototype, null);" + ) + } + } + jsPrinter.write("}") + jsPrinter.nextLine() + // __constructFresh skips the identity cache lookup for newly created objects. + // Since the pointer is brand new, it cannot already be in the cache. + if useIdentity { + jsPrinter.write("static __constructFresh(ptr) {") + jsPrinter.indent { + jsPrinter.write("const obj = Object.create(\(klass.name).prototype);") + jsPrinter.write( + "const state = { pointer: ptr, deinit: instance.exports.bjs_\(klass.abiName)_deinit, hasReleased: false, identityMap: \(klass.name).__identityCache };" + ) + jsPrinter.write("obj.pointer = ptr;") + jsPrinter.write("obj.__swiftHeapObjectState = state;") + jsPrinter.write("swiftHeapObjectFinalizationRegistry.register(obj, state, state);") + jsPrinter.write("\(klass.name).__identityCache.set(ptr, new WeakRef(obj));") + jsPrinter.write("return obj;") + } + jsPrinter.write("}") + jsPrinter.nextLine() + } } - jsPrinter.write("}") - jsPrinter.nextLine() } if let constructor: ExportedConstructor = klass.constructor { @@ -1991,10 +2114,20 @@ extension BridgeJSLink { jsPrinter.indent { jsPrinter.write("constructor(\(constructorParamList)) {") let returnExpr = thunkBuilder.callConstructor(abiName: constructor.abiName) + let constructCall: String + if useSwiftIdentity { + // `.swift` mode: the init export itself pushed (id, freshBit). + // `__wrap` pops them and deduplicates. + constructCall = "\(klass.name).__wrap(\(returnExpr))" + } else if useIdentity { + constructCall = "\(klass.name).__constructFresh(\(returnExpr))" + } else { + constructCall = "\(klass.name).__construct(\(returnExpr))" + } jsPrinter.indent { thunkBuilder.renderFunctionBody( into: jsPrinter, - returnExpr: "\(klass.name).__construct(\(returnExpr))" + returnExpr: constructCall ) } jsPrinter.write("}") @@ -2043,6 +2176,10 @@ extension BridgeJSLink { effects: method.effects, intrinsicRegistry: intrinsicRegistry ) + // Instance methods on .swift-mode classes guard against use-after-release. + if useSwiftIdentity { + appendUseAfterReleaseGuard(to: thunkBuilder.body, className: klass.name) + } thunkBuilder.lowerSelf() for param in method.parameters { try thunkBuilder.lowerParameter(param: param) @@ -2073,7 +2210,9 @@ extension BridgeJSLink { try renderClassProperty( property: property, className: klass.abiName, + classJsName: klass.name, isStatic: property.isStatic, + useSwiftIdentity: useSwiftIdentity, jsPrinter: jsPrinter, dtsPrinter: property.isStatic ? dtsExportEntryPrinter : dtsTypePrinter ) @@ -2086,10 +2225,25 @@ extension BridgeJSLink { return (jsPrinter.lines, dtsTypePrinter.lines, dtsExportEntryPrinter.lines) } + /// Writes the use-after-release guard for a `.swift`-mode class to the given printer. + /// Must be called before any other content is written so the guard appears at the top + /// of the generated function body. + private func appendUseAfterReleaseGuard(to printer: CodeFragmentPrinter, className: String) { + printer.write("if (this.__swiftIdentityHasReleased) {") + printer.indent { + printer.write( + "throw new Error(\"Attempted to call a member on a released \(className)\");" + ) + } + printer.write("}") + } + private func renderClassProperty( property: ExportedProperty, className: String, + classJsName: String, isStatic: Bool, + useSwiftIdentity: Bool, jsPrinter: CodeFragmentPrinter, dtsPrinter: CodeFragmentPrinter ) throws { @@ -2097,6 +2251,9 @@ extension BridgeJSLink { effects: Effects(isAsync: false, isThrows: false), intrinsicRegistry: intrinsicRegistry ) + if !isStatic && useSwiftIdentity { + appendUseAfterReleaseGuard(to: getterThunkBuilder.body, className: classJsName) + } if !isStatic { getterThunkBuilder.lowerSelf() } @@ -2122,6 +2279,9 @@ extension BridgeJSLink { effects: Effects(isAsync: false, isThrows: false), intrinsicRegistry: intrinsicRegistry ) + if !isStatic && useSwiftIdentity { + appendUseAfterReleaseGuard(to: setterThunkBuilder.body, className: classJsName) + } if !isStatic { setterThunkBuilder.lowerSelf() } diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index c5672c79c..daaaf0040 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -784,6 +784,9 @@ public struct ExportedClass: Codable, NamespacedExportedType { public var methods: [ExportedFunction] public var properties: [ExportedProperty] public var namespace: [String]? + /// Per-class identity-mode override. `nil` means "use the config default". + /// Valid values: `"none"`, `"pointer"`, `"swift"`. See `JSIdentityMode`. + public var identityMode: String? public init( name: String, @@ -792,7 +795,8 @@ public struct ExportedClass: Codable, NamespacedExportedType { constructor: ExportedConstructor? = nil, methods: [ExportedFunction], properties: [ExportedProperty] = [], - namespace: [String]? = nil + namespace: [String]? = nil, + identityMode: String? = nil ) { self.name = name self.swiftCallName = swiftCallName @@ -801,6 +805,7 @@ public struct ExportedClass: Codable, NamespacedExportedType { self.methods = methods self.properties = properties self.namespace = namespace + self.identityMode = identityMode } } @@ -890,13 +895,20 @@ public struct ExportedSkeleton: Codable { /// through the exports object. public var exposeToGlobal: Bool + /// The identity mode for exported Swift heap objects. + /// + /// When `"pointer"`, Swift heap objects are tracked by pointer identity. + /// When `"none"` or `nil`, no identity tracking is performed. + public var identityMode: String? + public init( functions: [ExportedFunction], classes: [ExportedClass], enums: [ExportedEnum], structs: [ExportedStruct] = [], protocols: [ExportedProtocol] = [], - exposeToGlobal: Bool + exposeToGlobal: Bool, + identityMode: String? = nil ) { self.functions = functions self.classes = classes @@ -904,6 +916,7 @@ public struct ExportedSkeleton: Codable { self.structs = structs self.protocols = protocols self.exposeToGlobal = exposeToGlobal + self.identityMode = identityMode } public mutating func append(_ other: ExportedSkeleton) { @@ -913,6 +926,7 @@ public struct ExportedSkeleton: Codable { self.structs.append(contentsOf: other.structs) self.protocols.append(contentsOf: other.protocols) assert(self.exposeToGlobal == other.exposeToGlobal) + assert(self.identityMode == other.identityMode) } public var isEmpty: Bool { diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift index a71aaee44..3e3f27ea1 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift @@ -165,7 +165,8 @@ import BridgeJSUtilities let swiftToSkeleton = SwiftToSkeleton( progress: progress, moduleName: moduleName, - exposeToGlobal: config.exposeToGlobal + exposeToGlobal: config.exposeToGlobal, + identityMode: config.identityMode ) for inputFile in inputFiles.sorted() { try withSpan("Parsing \(inputFile)") { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift index 711b04512..e75626702 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift @@ -105,4 +105,114 @@ import Testing ) try snapshot(bridgeJSLink: bridgeJSLink, name: "MixedModules") } + + @Test + func perClassIdentityModeFromAnnotation() throws { + let url = Self.inputsDirectory.appendingPathComponent("IdentityModeClass.swift") + let sourceFile = Parser.parse(source: try String(contentsOf: url, encoding: .utf8)) + let swiftAPI = SwiftToSkeleton( + progress: .silent, + moduleName: "TestModule", + exposeToGlobal: false, + identityMode: nil // no config default + ) + swiftAPI.addSourceFile(sourceFile, inputFilePath: "IdentityModeClass.swift") + let outputSkeleton = try swiftAPI.finalize() + + // Verify skeleton has per-class identity mode (not captured by snapshots) + let cachedClass = outputSkeleton.exported!.classes.first { $0.name == "CachedModel" } + let uncachedClass = outputSkeleton.exported!.classes.first { $0.name == "UncachedModel" } + let explicitlyUncachedClass = outputSkeleton.exported!.classes.first { $0.name == "ExplicitlyUncachedModel" } + #expect(cachedClass?.identityMode == "pointer") + #expect(uncachedClass?.identityMode == nil) + #expect(explicitlyUncachedClass?.identityMode == "none") + + // Verify generated JS via snapshot + let bridgeJSLink = BridgeJSLink(skeletons: [outputSkeleton], sharedMemory: false) + try snapshot(bridgeJSLink: bridgeJSLink, name: "IdentityModeClass.PerClass") + } + + @Test + func perClassIdentityModeWithConfigOverride() throws { + let url = Self.inputsDirectory.appendingPathComponent("IdentityModeClass.swift") + let sourceFile = Parser.parse(source: try String(contentsOf: url, encoding: .utf8)) + let swiftAPI = SwiftToSkeleton( + progress: .silent, + moduleName: "TestModule", + exposeToGlobal: false, + identityMode: "pointer" // config says pointer for all classes + ) + swiftAPI.addSourceFile(sourceFile, inputFilePath: "IdentityModeClass.swift") + let outputSkeleton = try swiftAPI.finalize() + + // When config says "pointer", classes without annotation get identity mode from config. + // But @JS(identityMode: .none) should still override to "without identity". + let explicitlyUncachedClass = outputSkeleton.exported!.classes.first { $0.name == "ExplicitlyUncachedModel" } + #expect(explicitlyUncachedClass?.identityMode == "none") + + // Verify generated JS via snapshot + let bridgeJSLink = BridgeJSLink(skeletons: [outputSkeleton], sharedMemory: false) + try snapshot(bridgeJSLink: bridgeJSLink, name: "IdentityModeClass.ConfigPointer") + } + + @Test + func testLinkIdentityModeSwiftClass() throws { + // Per-class `identityMode: .swift` opt-in — no config default. + // Fixture `IdentityModeSwiftClass.swift` has three classes: + // - SwiftCached: @JS(identityMode: .swift) -> "swift" + // - WeakCached: @JS(identityMode: .pointer) -> "pointer" + // - Untouched: @JS -> nil (inherits config, which is "none" here) + let url = Self.inputsDirectory.appendingPathComponent("IdentityModeSwiftClass.swift") + let sourceFile = Parser.parse(source: try String(contentsOf: url, encoding: .utf8)) + let swiftAPI = SwiftToSkeleton( + progress: .silent, + moduleName: "TestModule", + exposeToGlobal: false, + identityMode: nil // no config default + ) + swiftAPI.addSourceFile(sourceFile, inputFilePath: "IdentityModeSwiftClass.swift") + let outputSkeleton = try swiftAPI.finalize() + + // Verify skeleton has per-class identity modes resolved from annotations. + let swiftCached = outputSkeleton.exported!.classes.first { $0.name == "SwiftCached" } + let weakCached = outputSkeleton.exported!.classes.first { $0.name == "WeakCached" } + let untouched = outputSkeleton.exported!.classes.first { $0.name == "Untouched" } + #expect(swiftCached?.identityMode == "swift") + #expect(weakCached?.identityMode == "pointer") + #expect(untouched?.identityMode == nil) + + // Verify generated JS via snapshot + let bridgeJSLink = BridgeJSLink(skeletons: [outputSkeleton], sharedMemory: false) + try snapshot(bridgeJSLink: bridgeJSLink, name: "IdentityModeSwiftClass") + } + + @Test + func testLinkIdentityModeConfigSwift() throws { + // Config-default `identityMode: "swift"` — reuses the existing `IdentityModeClass.swift` fixture. + // - ExplicitlyUncachedModel: @JS(identityMode: false) -> "none" (explicit override wins) + // - UncachedModel: @JS -> nil (inherits config -> "swift" at resolution time) + // - CachedModel: @JS(identityMode: true) -> "pointer" (explicit per-class override stays) + let url = Self.inputsDirectory.appendingPathComponent("IdentityModeClass.swift") + let sourceFile = Parser.parse(source: try String(contentsOf: url, encoding: .utf8)) + let swiftAPI = SwiftToSkeleton( + progress: .silent, + moduleName: "TestModule", + exposeToGlobal: false, + identityMode: "swift" // config default says swift for unannotated classes + ) + swiftAPI.addSourceFile(sourceFile, inputFilePath: "IdentityModeClass.swift") + let outputSkeleton = try swiftAPI.finalize() + + // Per-class annotation still wins over config default. + let explicitlyUncachedClass = outputSkeleton.exported!.classes.first { $0.name == "ExplicitlyUncachedModel" } + let uncachedClass = outputSkeleton.exported!.classes.first { $0.name == "UncachedModel" } + let cachedClass = outputSkeleton.exported!.classes.first { $0.name == "CachedModel" } + #expect(explicitlyUncachedClass?.identityMode == "none") + #expect(uncachedClass?.identityMode == nil) // inherits config at resolution time + #expect(cachedClass?.identityMode == "pointer") + + // Verify generated JS via snapshot + let bridgeJSLink = BridgeJSLink(skeletons: [outputSkeleton], sharedMemory: false) + try snapshot(bridgeJSLink: bridgeJSLink, name: "IdentityModeClass.ConfigSwift") + } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/IdentityModeClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/IdentityModeClass.swift new file mode 100644 index 000000000..4d50b6c76 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/IdentityModeClass.swift @@ -0,0 +1,28 @@ +import JavaScriptKit + +@JS(identityMode: true) +class CachedModel { + @JS var name: String + + @JS init(name: String) { + self.name = name + } +} + +@JS +class UncachedModel { + @JS var value: Int + + @JS init(value: Int) { + self.value = value + } +} + +@JS(identityMode: false) +class ExplicitlyUncachedModel { + @JS var count: Int + + @JS init(count: Int) { + self.count = count + } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/IdentityModeSwiftClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/IdentityModeSwiftClass.swift new file mode 100644 index 000000000..de3175e81 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/IdentityModeSwiftClass.swift @@ -0,0 +1,18 @@ +import JavaScriptKit + +@JS(identityMode: .swift) +class SwiftCached { + @JS var name: String + @JS init(name: String) { self.name = name } +} + +@JS(identityMode: .pointer) +class WeakCached { + @JS var value: Int + @JS init(value: Int) { self.value = value } +} + +@JS class Untouched { + @JS var v: Int + @JS init(v: Int) { self.v = v } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/IdentityModeClass.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/IdentityModeClass.json new file mode 100644 index 000000000..1d6d747bc --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/IdentityModeClass.json @@ -0,0 +1,148 @@ +{ + "exported" : { + "classes" : [ + { + "constructor" : { + "abiName" : "bjs_CachedModel_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "name", + "name" : "name", + "type" : { + "string" : { + + } + } + } + ] + }, + "identityMode" : "pointer", + "methods" : [ + + ], + "name" : "CachedModel", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + } + ], + "swiftCallName" : "CachedModel" + }, + { + "constructor" : { + "abiName" : "bjs_UncachedModel_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "UncachedModel", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "UncachedModel" + }, + { + "constructor" : { + "abiName" : "bjs_ExplicitlyUncachedModel_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "count", + "name" : "count", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "identityMode" : "none", + "methods" : [ + + ], + "name" : "ExplicitlyUncachedModel", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "count", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "ExplicitlyUncachedModel" + } + ], + "enums" : [ + + ], + "exposeToGlobal" : false, + "functions" : [ + + ], + "protocols" : [ + + ], + "structs" : [ + + ] + }, + "moduleName" : "TestModule" +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/IdentityModeClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/IdentityModeClass.swift new file mode 100644 index 000000000..a79b91d56 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/IdentityModeClass.swift @@ -0,0 +1,188 @@ +@_expose(wasm, "bjs_CachedModel_init") +@_cdecl("bjs_CachedModel_init") +public func _bjs_CachedModel_init(_ nameBytes: Int32, _ nameLength: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = CachedModel(name: String.bridgeJSLiftParameter(nameBytes, nameLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_CachedModel_name_get") +@_cdecl("bjs_CachedModel_name_get") +public func _bjs_CachedModel_name_get(_ _self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = CachedModel.bridgeJSLiftParameter(_self).name + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_CachedModel_name_set") +@_cdecl("bjs_CachedModel_name_set") +public func _bjs_CachedModel_name_set(_ _self: UnsafeMutableRawPointer, _ valueBytes: Int32, _ valueLength: Int32) -> Void { + #if arch(wasm32) + CachedModel.bridgeJSLiftParameter(_self).name = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_CachedModel_deinit") +@_cdecl("bjs_CachedModel_deinit") +public func _bjs_CachedModel_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension CachedModel: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_CachedModel_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_CachedModel_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_CachedModel_wrap") +fileprivate func _bjs_CachedModel_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_CachedModel_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_CachedModel_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_CachedModel_wrap_extern(pointer) +} + +@_expose(wasm, "bjs_UncachedModel_init") +@_cdecl("bjs_UncachedModel_init") +public func _bjs_UncachedModel_init(_ value: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = UncachedModel(value: Int.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_UncachedModel_value_get") +@_cdecl("bjs_UncachedModel_value_get") +public func _bjs_UncachedModel_value_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = UncachedModel.bridgeJSLiftParameter(_self).value + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_UncachedModel_value_set") +@_cdecl("bjs_UncachedModel_value_set") +public func _bjs_UncachedModel_value_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + UncachedModel.bridgeJSLiftParameter(_self).value = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_UncachedModel_deinit") +@_cdecl("bjs_UncachedModel_deinit") +public func _bjs_UncachedModel_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension UncachedModel: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_UncachedModel_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_UncachedModel_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_UncachedModel_wrap") +fileprivate func _bjs_UncachedModel_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_UncachedModel_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_UncachedModel_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_UncachedModel_wrap_extern(pointer) +} + +@_expose(wasm, "bjs_ExplicitlyUncachedModel_init") +@_cdecl("bjs_ExplicitlyUncachedModel_init") +public func _bjs_ExplicitlyUncachedModel_init(_ count: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ExplicitlyUncachedModel(count: Int.bridgeJSLiftParameter(count)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ExplicitlyUncachedModel_count_get") +@_cdecl("bjs_ExplicitlyUncachedModel_count_get") +public func _bjs_ExplicitlyUncachedModel_count_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = ExplicitlyUncachedModel.bridgeJSLiftParameter(_self).count + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ExplicitlyUncachedModel_count_set") +@_cdecl("bjs_ExplicitlyUncachedModel_count_set") +public func _bjs_ExplicitlyUncachedModel_count_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + ExplicitlyUncachedModel.bridgeJSLiftParameter(_self).count = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ExplicitlyUncachedModel_deinit") +@_cdecl("bjs_ExplicitlyUncachedModel_deinit") +public func _bjs_ExplicitlyUncachedModel_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension ExplicitlyUncachedModel: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_ExplicitlyUncachedModel_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_ExplicitlyUncachedModel_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_ExplicitlyUncachedModel_wrap") +fileprivate func _bjs_ExplicitlyUncachedModel_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_ExplicitlyUncachedModel_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_ExplicitlyUncachedModel_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_ExplicitlyUncachedModel_wrap_extern(pointer) +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/IdentityModeSwiftClass.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/IdentityModeSwiftClass.json new file mode 100644 index 000000000..459491051 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/IdentityModeSwiftClass.json @@ -0,0 +1,148 @@ +{ + "exported" : { + "classes" : [ + { + "constructor" : { + "abiName" : "bjs_SwiftCached_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "name", + "name" : "name", + "type" : { + "string" : { + + } + } + } + ] + }, + "identityMode" : "swift", + "methods" : [ + + ], + "name" : "SwiftCached", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + } + ], + "swiftCallName" : "SwiftCached" + }, + { + "constructor" : { + "abiName" : "bjs_WeakCached_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "identityMode" : "pointer", + "methods" : [ + + ], + "name" : "WeakCached", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "WeakCached" + }, + { + "constructor" : { + "abiName" : "bjs_Untouched_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "Untouched", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "v", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "Untouched" + } + ], + "enums" : [ + + ], + "exposeToGlobal" : false, + "functions" : [ + + ], + "protocols" : [ + + ], + "structs" : [ + + ] + }, + "moduleName" : "TestModule" +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/IdentityModeSwiftClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/IdentityModeSwiftClass.swift new file mode 100644 index 000000000..63cf4a685 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/IdentityModeSwiftClass.swift @@ -0,0 +1,224 @@ +nonisolated(unsafe) var _SwiftCached_identityTable: Set = [] + +@_expose(wasm, "bjs_SwiftCached_init") +@_cdecl("bjs_SwiftCached_init") +public func _bjs_SwiftCached_init(_ nameBytes: Int32, _ nameLength: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = SwiftCached(name: String.bridgeJSLiftParameter(nameBytes, nameLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftCached_name_get") +@_cdecl("bjs_SwiftCached_name_get") +public func _bjs_SwiftCached_name_get(_ _self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = SwiftCached.bridgeJSLiftParameter(_self).name + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftCached_name_set") +@_cdecl("bjs_SwiftCached_name_set") +public func _bjs_SwiftCached_name_set(_ _self: UnsafeMutableRawPointer, _ valueBytes: Int32, _ valueLength: Int32) -> Void { + #if arch(wasm32) + SwiftCached.bridgeJSLiftParameter(_self).name = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftCached_deinit") +@_cdecl("bjs_SwiftCached_deinit") +public func _bjs_SwiftCached_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftCached_release_wrapper") +@_cdecl("bjs_SwiftCached_release_wrapper") +public func _bjs_SwiftCached_release_wrapper(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + guard _SwiftCached_identityTable.remove(pointer) != nil else { return } + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension SwiftCached { + @_spi(BridgeJS) @_transparent + public consuming func bridgeJSLowerReturn() -> UnsafeMutableRawPointer { + return withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _SwiftCached_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + } + @_spi(BridgeJS) public consuming func bridgeJSStackPush() { + let ptr: UnsafeMutableRawPointer = withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _SwiftCached_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + _swift_js_push_pointer(ptr) + } +} + +extension SwiftCached: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_SwiftCached_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_SwiftCached_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_SwiftCached_wrap") +fileprivate func _bjs_SwiftCached_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_SwiftCached_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_SwiftCached_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_SwiftCached_wrap_extern(pointer) +} + +@_expose(wasm, "bjs_WeakCached_init") +@_cdecl("bjs_WeakCached_init") +public func _bjs_WeakCached_init(_ value: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = WeakCached(value: Int.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_WeakCached_value_get") +@_cdecl("bjs_WeakCached_value_get") +public func _bjs_WeakCached_value_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = WeakCached.bridgeJSLiftParameter(_self).value + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_WeakCached_value_set") +@_cdecl("bjs_WeakCached_value_set") +public func _bjs_WeakCached_value_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + WeakCached.bridgeJSLiftParameter(_self).value = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_WeakCached_deinit") +@_cdecl("bjs_WeakCached_deinit") +public func _bjs_WeakCached_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension WeakCached: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_WeakCached_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_WeakCached_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_WeakCached_wrap") +fileprivate func _bjs_WeakCached_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_WeakCached_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_WeakCached_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_WeakCached_wrap_extern(pointer) +} + +@_expose(wasm, "bjs_Untouched_init") +@_cdecl("bjs_Untouched_init") +public func _bjs_Untouched_init(_ v: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Untouched(v: Int.bridgeJSLiftParameter(v)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Untouched_v_get") +@_cdecl("bjs_Untouched_v_get") +public func _bjs_Untouched_v_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = Untouched.bridgeJSLiftParameter(_self).v + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Untouched_v_set") +@_cdecl("bjs_Untouched_v_set") +public func _bjs_Untouched_v_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + Untouched.bridgeJSLiftParameter(_self).v = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Untouched_deinit") +@_cdecl("bjs_Untouched_deinit") +public func _bjs_Untouched_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension Untouched: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_Untouched_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_Untouched_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_Untouched_wrap") +fileprivate func _bjs_Untouched_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_Untouched_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_Untouched_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_Untouched_wrap_extern(pointer) +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js index 85e9c749a..8359220c9 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js @@ -348,18 +348,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -369,18 +390,19 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class Item extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Item_deinit, Item.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Item_deinit, Item.prototype, null); } } class MultiArrayContainer extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_MultiArrayContainer_deinit, MultiArrayContainer.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_MultiArrayContainer_deinit, MultiArrayContainer.prototype, null); } constructor(nums, strs) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js index 004320e4b..cafd250b0 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js @@ -279,18 +279,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -300,12 +321,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class DefaultGreeter extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_DefaultGreeter_deinit, DefaultGreeter.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_DefaultGreeter_deinit, DefaultGreeter.prototype, null); } constructor(name) { @@ -328,7 +350,7 @@ export async function createInstantiator(options, swift) { } class EmptyGreeter extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_EmptyGreeter_deinit, EmptyGreeter.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_EmptyGreeter_deinit, EmptyGreeter.prototype, null); } constructor() { @@ -338,7 +360,7 @@ export async function createInstantiator(options, swift) { } class ConstructorDefaults extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_ConstructorDefaults_deinit, ConstructorDefaults.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_ConstructorDefaults_deinit, ConstructorDefaults.prototype, null); } constructor(name = "Default", count = 42, enabled = true, status = StatusValues.Active, tag = null) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js index b6cf2c253..920017972 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js @@ -288,18 +288,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -309,12 +330,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class Box extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Box_deinit, Box.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Box_deinit, Box.prototype, null); } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.js index eb474d3b0..3d2230c6c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.js @@ -955,18 +955,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -976,12 +997,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class User extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_User_deinit, User.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_User_deinit, User.prototype, null); } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.js index 948039cf9..10fe31f64 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.js @@ -271,18 +271,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -292,12 +313,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class Converter extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Utils_Converter_deinit, Converter.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Utils_Converter_deinit, Converter.prototype, null); } constructor() { @@ -320,7 +342,7 @@ export async function createInstantiator(options, swift) { } class HTTPServer extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Networking_API_HTTPServer_deinit, HTTPServer.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Networking_API_HTTPServer_deinit, HTTPServer.prototype, null); } constructor() { @@ -333,7 +355,7 @@ export async function createInstantiator(options, swift) { } class TestServer extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Networking_APIV2_Internal_TestServer_deinit, TestServer.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Networking_APIV2_Internal_TestServer_deinit, TestServer.prototype, null); } constructor() { @@ -346,7 +368,7 @@ export async function createInstantiator(options, swift) { } class Converter extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Formatting_Converter_deinit, Converter.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Formatting_Converter_deinit, Converter.prototype, null); } constructor() { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.js index 5201350b2..a6aeee4b0 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.js @@ -252,18 +252,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -273,12 +294,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class Converter extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Utils_Converter_deinit, Converter.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Utils_Converter_deinit, Converter.prototype, null); } constructor() { @@ -301,7 +323,7 @@ export async function createInstantiator(options, swift) { } class HTTPServer extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Networking_API_HTTPServer_deinit, HTTPServer.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Networking_API_HTTPServer_deinit, HTTPServer.prototype, null); } constructor() { @@ -314,7 +336,7 @@ export async function createInstantiator(options, swift) { } class TestServer extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Networking_APIV2_Internal_TestServer_deinit, TestServer.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Networking_APIV2_Internal_TestServer_deinit, TestServer.prototype, null); } constructor() { @@ -327,7 +349,7 @@ export async function createInstantiator(options, swift) { } class Converter extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Formatting_Converter_deinit, Converter.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Formatting_Converter_deinit, Converter.prototype, null); } constructor() { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigPointer.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigPointer.d.ts new file mode 100644 index 000000000..e5e2a3a84 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigPointer.d.ts @@ -0,0 +1,42 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +/// Represents a Swift heap object like a class instance or an actor instance. +export interface SwiftHeapObject { + /// Release the heap object. + /// + /// Note: Calling this method will release the heap object and it will no longer be accessible. + release(): void; +} +export interface CachedModel extends SwiftHeapObject { + name: string; +} +export interface UncachedModel extends SwiftHeapObject { + value: number; +} +export interface ExplicitlyUncachedModel extends SwiftHeapObject { + count: number; +} +export type Exports = { + CachedModel: { + new(name: string): CachedModel; + } + UncachedModel: { + new(value: number): UncachedModel; + } + ExplicitlyUncachedModel: { + new(count: number): ExplicitlyUncachedModel; + } +} +export type Imports = { +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigPointer.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigPointer.js new file mode 100644 index 000000000..7970a905d --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigPointer.js @@ -0,0 +1,362 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + let decodeString; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + let tmpRetOptionalBool; + let tmpRetOptionalInt; + let tmpRetOptionalFloat; + let tmpRetOptionalDouble; + let tmpRetOptionalHeapObject; + let strStack = []; + let i32Stack = []; + let i64Stack = []; + let f32Stack = []; + let f64Stack = []; + let ptrStack = []; + const enumHelpers = {}; + const structHelpers = {}; + + let _exports = null; + let bjs = null; + + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + bjs = {}; + importObject["bjs"] = bjs; + bjs["swift_js_return_string"] = function(ptr, len) { + tmpRetString = decodeString(ptr, len); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + swift.memory.release(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + return swift.memory.retain(decodeString(ptr, len)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + bjs["swift_js_push_i32"] = function(v) { + i32Stack.push(v | 0); + } + bjs["swift_js_push_f32"] = function(v) { + f32Stack.push(Math.fround(v)); + } + bjs["swift_js_push_f64"] = function(v) { + f64Stack.push(v); + } + bjs["swift_js_push_string"] = function(ptr, len) { + const value = decodeString(ptr, len); + strStack.push(value); + } + bjs["swift_js_pop_i32"] = function() { + return i32Stack.pop(); + } + bjs["swift_js_pop_f32"] = function() { + return f32Stack.pop(); + } + bjs["swift_js_pop_f64"] = function() { + return f64Stack.pop(); + } + bjs["swift_js_push_pointer"] = function(pointer) { + ptrStack.push(pointer); + } + bjs["swift_js_pop_pointer"] = function() { + return ptrStack.pop(); + } + bjs["swift_js_push_i64"] = function(v) { + i64Stack.push(v); + } + bjs["swift_js_pop_i64"] = function() { + return i64Stack.pop(); + } + bjs["swift_js_return_optional_bool"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalBool = null; + } else { + tmpRetOptionalBool = value !== 0; + } + } + bjs["swift_js_return_optional_int"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalInt = null; + } else { + tmpRetOptionalInt = value | 0; + } + } + bjs["swift_js_return_optional_float"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalFloat = null; + } else { + tmpRetOptionalFloat = Math.fround(value); + } + } + bjs["swift_js_return_optional_double"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalDouble = null; + } else { + tmpRetOptionalDouble = value; + } + } + bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + if (isSome === 0) { + tmpRetString = null; + } else { + tmpRetString = decodeString(ptr, len); + } + } + bjs["swift_js_return_optional_object"] = function(isSome, objectId) { + if (isSome === 0) { + tmpRetString = null; + } else { + tmpRetString = swift.memory.getObject(objectId); + } + } + bjs["swift_js_return_optional_heap_object"] = function(isSome, pointer) { + if (isSome === 0) { + tmpRetOptionalHeapObject = null; + } else { + tmpRetOptionalHeapObject = pointer; + } + } + bjs["swift_js_get_optional_int_presence"] = function() { + return tmpRetOptionalInt != null ? 1 : 0; + } + bjs["swift_js_get_optional_int_value"] = function() { + const value = tmpRetOptionalInt; + tmpRetOptionalInt = undefined; + return value; + } + bjs["swift_js_get_optional_string"] = function() { + const str = tmpRetString; + tmpRetString = undefined; + if (str == null) { + return -1; + } else { + const bytes = textEncoder.encode(str); + tmpRetBytes = bytes; + return bytes.length; + } + } + bjs["swift_js_get_optional_float_presence"] = function() { + return tmpRetOptionalFloat != null ? 1 : 0; + } + bjs["swift_js_get_optional_float_value"] = function() { + const value = tmpRetOptionalFloat; + tmpRetOptionalFloat = undefined; + return value; + } + bjs["swift_js_get_optional_double_presence"] = function() { + return tmpRetOptionalDouble != null ? 1 : 0; + } + bjs["swift_js_get_optional_double_value"] = function() { + const value = tmpRetOptionalDouble; + tmpRetOptionalDouble = undefined; + return value; + } + bjs["swift_js_get_optional_heap_object_pointer"] = function() { + const pointer = tmpRetOptionalHeapObject; + tmpRetOptionalHeapObject = undefined; + return pointer || 0; + } + bjs["swift_js_closure_unregister"] = function(funcRef) {} + // Wrapper functions for module: TestModule + if (!importObject["TestModule"]) { + importObject["TestModule"] = {}; + } + importObject["TestModule"]["bjs_CachedModel_wrap"] = function(pointer) { + const obj = _exports['CachedModel'].__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_ExplicitlyUncachedModel_wrap"] = function(pointer) { + const obj = _exports['ExplicitlyUncachedModel'].__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_UncachedModel_wrap"] = function(pointer) { + const obj = _exports['UncachedModel'].__construct(pointer); + return swift.memory.retain(obj); + }; + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + + decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + const swiftHeapObjectFinalizationRegistry = (typeof FinalizationRegistry === "undefined") ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry((state) => { + if (state.hasReleased) { + return; + } + state.hasReleased = true; + state.identityMap?.delete(state.pointer); + state.deinit(state.pointer); + }); + + /// Represents a Swift heap object like a class instance or an actor instance. + class SwiftHeapObject { + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); + } + + release() { + const state = this.__swiftHeapObjectState; + if (state.hasReleased) { + return; + } + state.hasReleased = true; + swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); + state.deinit(state.pointer); + } + } + class CachedModel extends SwiftHeapObject { + static __identityCache = new Map(); + + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_CachedModel_deinit, CachedModel.prototype, CachedModel.__identityCache); + } + + static __constructFresh(ptr) { + const obj = Object.create(CachedModel.prototype); + const state = { pointer: ptr, deinit: instance.exports.bjs_CachedModel_deinit, hasReleased: false, identityMap: CachedModel.__identityCache }; + obj.pointer = ptr; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + CachedModel.__identityCache.set(ptr, new WeakRef(obj)); + return obj; + } + + constructor(name) { + const nameBytes = textEncoder.encode(name); + const nameId = swift.memory.retain(nameBytes); + const ret = instance.exports.bjs_CachedModel_init(nameId, nameBytes.length); + return CachedModel.__constructFresh(ret); + } + get name() { + instance.exports.bjs_CachedModel_name_get(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + set name(value) { + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + instance.exports.bjs_CachedModel_name_set(this.pointer, valueId, valueBytes.length); + } + } + class UncachedModel extends SwiftHeapObject { + static __identityCache = new Map(); + + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_UncachedModel_deinit, UncachedModel.prototype, UncachedModel.__identityCache); + } + + static __constructFresh(ptr) { + const obj = Object.create(UncachedModel.prototype); + const state = { pointer: ptr, deinit: instance.exports.bjs_UncachedModel_deinit, hasReleased: false, identityMap: UncachedModel.__identityCache }; + obj.pointer = ptr; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + UncachedModel.__identityCache.set(ptr, new WeakRef(obj)); + return obj; + } + + constructor(value) { + const ret = instance.exports.bjs_UncachedModel_init(value); + return UncachedModel.__constructFresh(ret); + } + get value() { + const ret = instance.exports.bjs_UncachedModel_value_get(this.pointer); + return ret; + } + set value(value) { + instance.exports.bjs_UncachedModel_value_set(this.pointer, value); + } + } + class ExplicitlyUncachedModel extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_ExplicitlyUncachedModel_deinit, ExplicitlyUncachedModel.prototype, null); + } + + constructor(count) { + const ret = instance.exports.bjs_ExplicitlyUncachedModel_init(count); + return ExplicitlyUncachedModel.__construct(ret); + } + get count() { + const ret = instance.exports.bjs_ExplicitlyUncachedModel_count_get(this.pointer); + return ret; + } + set count(value) { + instance.exports.bjs_ExplicitlyUncachedModel_count_set(this.pointer, value); + } + } + const exports = { + CachedModel, + UncachedModel, + ExplicitlyUncachedModel, + }; + _exports = exports; + return exports; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigSwift.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigSwift.d.ts new file mode 100644 index 000000000..e5e2a3a84 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigSwift.d.ts @@ -0,0 +1,42 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +/// Represents a Swift heap object like a class instance or an actor instance. +export interface SwiftHeapObject { + /// Release the heap object. + /// + /// Note: Calling this method will release the heap object and it will no longer be accessible. + release(): void; +} +export interface CachedModel extends SwiftHeapObject { + name: string; +} +export interface UncachedModel extends SwiftHeapObject { + value: number; +} +export interface ExplicitlyUncachedModel extends SwiftHeapObject { + count: number; +} +export type Exports = { + CachedModel: { + new(name: string): CachedModel; + } + UncachedModel: { + new(value: number): UncachedModel; + } + ExplicitlyUncachedModel: { + new(count: number): ExplicitlyUncachedModel; + } +} +export type Imports = { +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigSwift.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigSwift.js new file mode 100644 index 000000000..5275d1042 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigSwift.js @@ -0,0 +1,375 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + let decodeString; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + let tmpRetOptionalBool; + let tmpRetOptionalInt; + let tmpRetOptionalFloat; + let tmpRetOptionalDouble; + let tmpRetOptionalHeapObject; + let strStack = []; + let i32Stack = []; + let i64Stack = []; + let f32Stack = []; + let f64Stack = []; + let ptrStack = []; + const enumHelpers = {}; + const structHelpers = {}; + + let _exports = null; + let bjs = null; + + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + bjs = {}; + importObject["bjs"] = bjs; + bjs["swift_js_return_string"] = function(ptr, len) { + tmpRetString = decodeString(ptr, len); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + swift.memory.release(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + return swift.memory.retain(decodeString(ptr, len)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + bjs["swift_js_push_i32"] = function(v) { + i32Stack.push(v | 0); + } + bjs["swift_js_push_f32"] = function(v) { + f32Stack.push(Math.fround(v)); + } + bjs["swift_js_push_f64"] = function(v) { + f64Stack.push(v); + } + bjs["swift_js_push_string"] = function(ptr, len) { + const value = decodeString(ptr, len); + strStack.push(value); + } + bjs["swift_js_pop_i32"] = function() { + return i32Stack.pop(); + } + bjs["swift_js_pop_f32"] = function() { + return f32Stack.pop(); + } + bjs["swift_js_pop_f64"] = function() { + return f64Stack.pop(); + } + bjs["swift_js_push_pointer"] = function(pointer) { + ptrStack.push(pointer); + } + bjs["swift_js_pop_pointer"] = function() { + return ptrStack.pop(); + } + bjs["swift_js_push_i64"] = function(v) { + i64Stack.push(v); + } + bjs["swift_js_pop_i64"] = function() { + return i64Stack.pop(); + } + bjs["swift_js_return_optional_bool"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalBool = null; + } else { + tmpRetOptionalBool = value !== 0; + } + } + bjs["swift_js_return_optional_int"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalInt = null; + } else { + tmpRetOptionalInt = value | 0; + } + } + bjs["swift_js_return_optional_float"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalFloat = null; + } else { + tmpRetOptionalFloat = Math.fround(value); + } + } + bjs["swift_js_return_optional_double"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalDouble = null; + } else { + tmpRetOptionalDouble = value; + } + } + bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + if (isSome === 0) { + tmpRetString = null; + } else { + tmpRetString = decodeString(ptr, len); + } + } + bjs["swift_js_return_optional_object"] = function(isSome, objectId) { + if (isSome === 0) { + tmpRetString = null; + } else { + tmpRetString = swift.memory.getObject(objectId); + } + } + bjs["swift_js_return_optional_heap_object"] = function(isSome, pointer) { + if (isSome === 0) { + tmpRetOptionalHeapObject = null; + } else { + tmpRetOptionalHeapObject = pointer; + } + } + bjs["swift_js_get_optional_int_presence"] = function() { + return tmpRetOptionalInt != null ? 1 : 0; + } + bjs["swift_js_get_optional_int_value"] = function() { + const value = tmpRetOptionalInt; + tmpRetOptionalInt = undefined; + return value; + } + bjs["swift_js_get_optional_string"] = function() { + const str = tmpRetString; + tmpRetString = undefined; + if (str == null) { + return -1; + } else { + const bytes = textEncoder.encode(str); + tmpRetBytes = bytes; + return bytes.length; + } + } + bjs["swift_js_get_optional_float_presence"] = function() { + return tmpRetOptionalFloat != null ? 1 : 0; + } + bjs["swift_js_get_optional_float_value"] = function() { + const value = tmpRetOptionalFloat; + tmpRetOptionalFloat = undefined; + return value; + } + bjs["swift_js_get_optional_double_presence"] = function() { + return tmpRetOptionalDouble != null ? 1 : 0; + } + bjs["swift_js_get_optional_double_value"] = function() { + const value = tmpRetOptionalDouble; + tmpRetOptionalDouble = undefined; + return value; + } + bjs["swift_js_get_optional_heap_object_pointer"] = function() { + const pointer = tmpRetOptionalHeapObject; + tmpRetOptionalHeapObject = undefined; + return pointer || 0; + } + bjs["swift_js_closure_unregister"] = function(funcRef) {} + // Wrapper functions for module: TestModule + if (!importObject["TestModule"]) { + importObject["TestModule"] = {}; + } + importObject["TestModule"]["bjs_CachedModel_wrap"] = function(pointer) { + const obj = _exports['CachedModel'].__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_ExplicitlyUncachedModel_wrap"] = function(pointer) { + const obj = _exports['ExplicitlyUncachedModel'].__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_UncachedModel_wrap"] = function(pointer) { + const obj = _exports['UncachedModel'].__construct(pointer); + return swift.memory.retain(obj); + }; + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + + decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + const swiftHeapObjectFinalizationRegistry = (typeof FinalizationRegistry === "undefined") ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry((state) => { + if (state.hasReleased) { + return; + } + state.hasReleased = true; + state.identityMap?.delete(state.pointer); + state.deinit(state.pointer); + }); + + /// Represents a Swift heap object like a class instance or an actor instance. + class SwiftHeapObject { + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); + } + + release() { + const state = this.__swiftHeapObjectState; + if (state.hasReleased) { + return; + } + state.hasReleased = true; + swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); + state.deinit(state.pointer); + } + } + class CachedModel extends SwiftHeapObject { + static __identityCache = new Map(); + + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_CachedModel_deinit, CachedModel.prototype, CachedModel.__identityCache); + } + + static __constructFresh(ptr) { + const obj = Object.create(CachedModel.prototype); + const state = { pointer: ptr, deinit: instance.exports.bjs_CachedModel_deinit, hasReleased: false, identityMap: CachedModel.__identityCache }; + obj.pointer = ptr; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + CachedModel.__identityCache.set(ptr, new WeakRef(obj)); + return obj; + } + + constructor(name) { + const nameBytes = textEncoder.encode(name); + const nameId = swift.memory.retain(nameBytes); + const ret = instance.exports.bjs_CachedModel_init(nameId, nameBytes.length); + return CachedModel.__constructFresh(ret); + } + get name() { + instance.exports.bjs_CachedModel_name_get(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + set name(value) { + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + instance.exports.bjs_CachedModel_name_set(this.pointer, valueId, valueBytes.length); + } + } + class UncachedModel { + static __swiftIdentityWrappers = new Map(); + + static __wrap(pointer) { + const cached = UncachedModel.__swiftIdentityWrappers.get(pointer); + if (cached !== undefined) return cached; + const obj = Object.create(UncachedModel.prototype); + obj.pointer = pointer; + obj.__swiftIdentityHasReleased = false; + UncachedModel.__swiftIdentityWrappers.set(pointer, obj); + return obj; + } + + static __construct(pointer) { + return UncachedModel.__wrap(pointer); + } + + release() { + if (this.__swiftIdentityHasReleased) return; + this.__swiftIdentityHasReleased = true; + const pointer = this.pointer; + instance.exports.bjs_UncachedModel_release_wrapper(pointer); + UncachedModel.__swiftIdentityWrappers.delete(pointer); + } + constructor(value) { + const ret = instance.exports.bjs_UncachedModel_init(value); + return UncachedModel.__wrap(ret); + } + get value() { + if (this.__swiftIdentityHasReleased) { + throw new Error("Attempted to call a member on a released UncachedModel"); + } + const ret = instance.exports.bjs_UncachedModel_value_get(this.pointer); + return ret; + } + set value(value) { + if (this.__swiftIdentityHasReleased) { + throw new Error("Attempted to call a member on a released UncachedModel"); + } + instance.exports.bjs_UncachedModel_value_set(this.pointer, value); + } + } + class ExplicitlyUncachedModel extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_ExplicitlyUncachedModel_deinit, ExplicitlyUncachedModel.prototype, null); + } + + constructor(count) { + const ret = instance.exports.bjs_ExplicitlyUncachedModel_init(count); + return ExplicitlyUncachedModel.__construct(ret); + } + get count() { + const ret = instance.exports.bjs_ExplicitlyUncachedModel_count_get(this.pointer); + return ret; + } + set count(value) { + instance.exports.bjs_ExplicitlyUncachedModel_count_set(this.pointer, value); + } + } + const exports = { + CachedModel, + UncachedModel, + ExplicitlyUncachedModel, + }; + _exports = exports; + return exports; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.PerClass.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.PerClass.d.ts new file mode 100644 index 000000000..e5e2a3a84 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.PerClass.d.ts @@ -0,0 +1,42 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +/// Represents a Swift heap object like a class instance or an actor instance. +export interface SwiftHeapObject { + /// Release the heap object. + /// + /// Note: Calling this method will release the heap object and it will no longer be accessible. + release(): void; +} +export interface CachedModel extends SwiftHeapObject { + name: string; +} +export interface UncachedModel extends SwiftHeapObject { + value: number; +} +export interface ExplicitlyUncachedModel extends SwiftHeapObject { + count: number; +} +export type Exports = { + CachedModel: { + new(name: string): CachedModel; + } + UncachedModel: { + new(value: number): UncachedModel; + } + ExplicitlyUncachedModel: { + new(count: number): ExplicitlyUncachedModel; + } +} +export type Imports = { +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.PerClass.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.PerClass.js new file mode 100644 index 000000000..9025bf986 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.PerClass.js @@ -0,0 +1,350 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + let decodeString; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + let tmpRetOptionalBool; + let tmpRetOptionalInt; + let tmpRetOptionalFloat; + let tmpRetOptionalDouble; + let tmpRetOptionalHeapObject; + let strStack = []; + let i32Stack = []; + let i64Stack = []; + let f32Stack = []; + let f64Stack = []; + let ptrStack = []; + const enumHelpers = {}; + const structHelpers = {}; + + let _exports = null; + let bjs = null; + + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + bjs = {}; + importObject["bjs"] = bjs; + bjs["swift_js_return_string"] = function(ptr, len) { + tmpRetString = decodeString(ptr, len); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + swift.memory.release(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + return swift.memory.retain(decodeString(ptr, len)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + bjs["swift_js_push_i32"] = function(v) { + i32Stack.push(v | 0); + } + bjs["swift_js_push_f32"] = function(v) { + f32Stack.push(Math.fround(v)); + } + bjs["swift_js_push_f64"] = function(v) { + f64Stack.push(v); + } + bjs["swift_js_push_string"] = function(ptr, len) { + const value = decodeString(ptr, len); + strStack.push(value); + } + bjs["swift_js_pop_i32"] = function() { + return i32Stack.pop(); + } + bjs["swift_js_pop_f32"] = function() { + return f32Stack.pop(); + } + bjs["swift_js_pop_f64"] = function() { + return f64Stack.pop(); + } + bjs["swift_js_push_pointer"] = function(pointer) { + ptrStack.push(pointer); + } + bjs["swift_js_pop_pointer"] = function() { + return ptrStack.pop(); + } + bjs["swift_js_push_i64"] = function(v) { + i64Stack.push(v); + } + bjs["swift_js_pop_i64"] = function() { + return i64Stack.pop(); + } + bjs["swift_js_return_optional_bool"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalBool = null; + } else { + tmpRetOptionalBool = value !== 0; + } + } + bjs["swift_js_return_optional_int"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalInt = null; + } else { + tmpRetOptionalInt = value | 0; + } + } + bjs["swift_js_return_optional_float"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalFloat = null; + } else { + tmpRetOptionalFloat = Math.fround(value); + } + } + bjs["swift_js_return_optional_double"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalDouble = null; + } else { + tmpRetOptionalDouble = value; + } + } + bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + if (isSome === 0) { + tmpRetString = null; + } else { + tmpRetString = decodeString(ptr, len); + } + } + bjs["swift_js_return_optional_object"] = function(isSome, objectId) { + if (isSome === 0) { + tmpRetString = null; + } else { + tmpRetString = swift.memory.getObject(objectId); + } + } + bjs["swift_js_return_optional_heap_object"] = function(isSome, pointer) { + if (isSome === 0) { + tmpRetOptionalHeapObject = null; + } else { + tmpRetOptionalHeapObject = pointer; + } + } + bjs["swift_js_get_optional_int_presence"] = function() { + return tmpRetOptionalInt != null ? 1 : 0; + } + bjs["swift_js_get_optional_int_value"] = function() { + const value = tmpRetOptionalInt; + tmpRetOptionalInt = undefined; + return value; + } + bjs["swift_js_get_optional_string"] = function() { + const str = tmpRetString; + tmpRetString = undefined; + if (str == null) { + return -1; + } else { + const bytes = textEncoder.encode(str); + tmpRetBytes = bytes; + return bytes.length; + } + } + bjs["swift_js_get_optional_float_presence"] = function() { + return tmpRetOptionalFloat != null ? 1 : 0; + } + bjs["swift_js_get_optional_float_value"] = function() { + const value = tmpRetOptionalFloat; + tmpRetOptionalFloat = undefined; + return value; + } + bjs["swift_js_get_optional_double_presence"] = function() { + return tmpRetOptionalDouble != null ? 1 : 0; + } + bjs["swift_js_get_optional_double_value"] = function() { + const value = tmpRetOptionalDouble; + tmpRetOptionalDouble = undefined; + return value; + } + bjs["swift_js_get_optional_heap_object_pointer"] = function() { + const pointer = tmpRetOptionalHeapObject; + tmpRetOptionalHeapObject = undefined; + return pointer || 0; + } + bjs["swift_js_closure_unregister"] = function(funcRef) {} + // Wrapper functions for module: TestModule + if (!importObject["TestModule"]) { + importObject["TestModule"] = {}; + } + importObject["TestModule"]["bjs_CachedModel_wrap"] = function(pointer) { + const obj = _exports['CachedModel'].__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_ExplicitlyUncachedModel_wrap"] = function(pointer) { + const obj = _exports['ExplicitlyUncachedModel'].__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_UncachedModel_wrap"] = function(pointer) { + const obj = _exports['UncachedModel'].__construct(pointer); + return swift.memory.retain(obj); + }; + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + + decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + const swiftHeapObjectFinalizationRegistry = (typeof FinalizationRegistry === "undefined") ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry((state) => { + if (state.hasReleased) { + return; + } + state.hasReleased = true; + state.identityMap?.delete(state.pointer); + state.deinit(state.pointer); + }); + + /// Represents a Swift heap object like a class instance or an actor instance. + class SwiftHeapObject { + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); + } + + release() { + const state = this.__swiftHeapObjectState; + if (state.hasReleased) { + return; + } + state.hasReleased = true; + swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); + state.deinit(state.pointer); + } + } + class CachedModel extends SwiftHeapObject { + static __identityCache = new Map(); + + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_CachedModel_deinit, CachedModel.prototype, CachedModel.__identityCache); + } + + static __constructFresh(ptr) { + const obj = Object.create(CachedModel.prototype); + const state = { pointer: ptr, deinit: instance.exports.bjs_CachedModel_deinit, hasReleased: false, identityMap: CachedModel.__identityCache }; + obj.pointer = ptr; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + CachedModel.__identityCache.set(ptr, new WeakRef(obj)); + return obj; + } + + constructor(name) { + const nameBytes = textEncoder.encode(name); + const nameId = swift.memory.retain(nameBytes); + const ret = instance.exports.bjs_CachedModel_init(nameId, nameBytes.length); + return CachedModel.__constructFresh(ret); + } + get name() { + instance.exports.bjs_CachedModel_name_get(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + set name(value) { + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + instance.exports.bjs_CachedModel_name_set(this.pointer, valueId, valueBytes.length); + } + } + class UncachedModel extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_UncachedModel_deinit, UncachedModel.prototype, null); + } + + constructor(value) { + const ret = instance.exports.bjs_UncachedModel_init(value); + return UncachedModel.__construct(ret); + } + get value() { + const ret = instance.exports.bjs_UncachedModel_value_get(this.pointer); + return ret; + } + set value(value) { + instance.exports.bjs_UncachedModel_value_set(this.pointer, value); + } + } + class ExplicitlyUncachedModel extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_ExplicitlyUncachedModel_deinit, ExplicitlyUncachedModel.prototype, null); + } + + constructor(count) { + const ret = instance.exports.bjs_ExplicitlyUncachedModel_init(count); + return ExplicitlyUncachedModel.__construct(ret); + } + get count() { + const ret = instance.exports.bjs_ExplicitlyUncachedModel_count_get(this.pointer); + return ret; + } + set count(value) { + instance.exports.bjs_ExplicitlyUncachedModel_count_set(this.pointer, value); + } + } + const exports = { + CachedModel, + UncachedModel, + ExplicitlyUncachedModel, + }; + _exports = exports; + return exports; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.d.ts new file mode 100644 index 000000000..e5e2a3a84 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.d.ts @@ -0,0 +1,42 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +/// Represents a Swift heap object like a class instance or an actor instance. +export interface SwiftHeapObject { + /// Release the heap object. + /// + /// Note: Calling this method will release the heap object and it will no longer be accessible. + release(): void; +} +export interface CachedModel extends SwiftHeapObject { + name: string; +} +export interface UncachedModel extends SwiftHeapObject { + value: number; +} +export interface ExplicitlyUncachedModel extends SwiftHeapObject { + count: number; +} +export type Exports = { + CachedModel: { + new(name: string): CachedModel; + } + UncachedModel: { + new(value: number): UncachedModel; + } + ExplicitlyUncachedModel: { + new(count: number): ExplicitlyUncachedModel; + } +} +export type Imports = { +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.js new file mode 100644 index 000000000..9025bf986 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.js @@ -0,0 +1,350 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + let decodeString; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + let tmpRetOptionalBool; + let tmpRetOptionalInt; + let tmpRetOptionalFloat; + let tmpRetOptionalDouble; + let tmpRetOptionalHeapObject; + let strStack = []; + let i32Stack = []; + let i64Stack = []; + let f32Stack = []; + let f64Stack = []; + let ptrStack = []; + const enumHelpers = {}; + const structHelpers = {}; + + let _exports = null; + let bjs = null; + + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + bjs = {}; + importObject["bjs"] = bjs; + bjs["swift_js_return_string"] = function(ptr, len) { + tmpRetString = decodeString(ptr, len); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + swift.memory.release(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + return swift.memory.retain(decodeString(ptr, len)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + bjs["swift_js_push_i32"] = function(v) { + i32Stack.push(v | 0); + } + bjs["swift_js_push_f32"] = function(v) { + f32Stack.push(Math.fround(v)); + } + bjs["swift_js_push_f64"] = function(v) { + f64Stack.push(v); + } + bjs["swift_js_push_string"] = function(ptr, len) { + const value = decodeString(ptr, len); + strStack.push(value); + } + bjs["swift_js_pop_i32"] = function() { + return i32Stack.pop(); + } + bjs["swift_js_pop_f32"] = function() { + return f32Stack.pop(); + } + bjs["swift_js_pop_f64"] = function() { + return f64Stack.pop(); + } + bjs["swift_js_push_pointer"] = function(pointer) { + ptrStack.push(pointer); + } + bjs["swift_js_pop_pointer"] = function() { + return ptrStack.pop(); + } + bjs["swift_js_push_i64"] = function(v) { + i64Stack.push(v); + } + bjs["swift_js_pop_i64"] = function() { + return i64Stack.pop(); + } + bjs["swift_js_return_optional_bool"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalBool = null; + } else { + tmpRetOptionalBool = value !== 0; + } + } + bjs["swift_js_return_optional_int"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalInt = null; + } else { + tmpRetOptionalInt = value | 0; + } + } + bjs["swift_js_return_optional_float"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalFloat = null; + } else { + tmpRetOptionalFloat = Math.fround(value); + } + } + bjs["swift_js_return_optional_double"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalDouble = null; + } else { + tmpRetOptionalDouble = value; + } + } + bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + if (isSome === 0) { + tmpRetString = null; + } else { + tmpRetString = decodeString(ptr, len); + } + } + bjs["swift_js_return_optional_object"] = function(isSome, objectId) { + if (isSome === 0) { + tmpRetString = null; + } else { + tmpRetString = swift.memory.getObject(objectId); + } + } + bjs["swift_js_return_optional_heap_object"] = function(isSome, pointer) { + if (isSome === 0) { + tmpRetOptionalHeapObject = null; + } else { + tmpRetOptionalHeapObject = pointer; + } + } + bjs["swift_js_get_optional_int_presence"] = function() { + return tmpRetOptionalInt != null ? 1 : 0; + } + bjs["swift_js_get_optional_int_value"] = function() { + const value = tmpRetOptionalInt; + tmpRetOptionalInt = undefined; + return value; + } + bjs["swift_js_get_optional_string"] = function() { + const str = tmpRetString; + tmpRetString = undefined; + if (str == null) { + return -1; + } else { + const bytes = textEncoder.encode(str); + tmpRetBytes = bytes; + return bytes.length; + } + } + bjs["swift_js_get_optional_float_presence"] = function() { + return tmpRetOptionalFloat != null ? 1 : 0; + } + bjs["swift_js_get_optional_float_value"] = function() { + const value = tmpRetOptionalFloat; + tmpRetOptionalFloat = undefined; + return value; + } + bjs["swift_js_get_optional_double_presence"] = function() { + return tmpRetOptionalDouble != null ? 1 : 0; + } + bjs["swift_js_get_optional_double_value"] = function() { + const value = tmpRetOptionalDouble; + tmpRetOptionalDouble = undefined; + return value; + } + bjs["swift_js_get_optional_heap_object_pointer"] = function() { + const pointer = tmpRetOptionalHeapObject; + tmpRetOptionalHeapObject = undefined; + return pointer || 0; + } + bjs["swift_js_closure_unregister"] = function(funcRef) {} + // Wrapper functions for module: TestModule + if (!importObject["TestModule"]) { + importObject["TestModule"] = {}; + } + importObject["TestModule"]["bjs_CachedModel_wrap"] = function(pointer) { + const obj = _exports['CachedModel'].__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_ExplicitlyUncachedModel_wrap"] = function(pointer) { + const obj = _exports['ExplicitlyUncachedModel'].__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_UncachedModel_wrap"] = function(pointer) { + const obj = _exports['UncachedModel'].__construct(pointer); + return swift.memory.retain(obj); + }; + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + + decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + const swiftHeapObjectFinalizationRegistry = (typeof FinalizationRegistry === "undefined") ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry((state) => { + if (state.hasReleased) { + return; + } + state.hasReleased = true; + state.identityMap?.delete(state.pointer); + state.deinit(state.pointer); + }); + + /// Represents a Swift heap object like a class instance or an actor instance. + class SwiftHeapObject { + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); + } + + release() { + const state = this.__swiftHeapObjectState; + if (state.hasReleased) { + return; + } + state.hasReleased = true; + swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); + state.deinit(state.pointer); + } + } + class CachedModel extends SwiftHeapObject { + static __identityCache = new Map(); + + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_CachedModel_deinit, CachedModel.prototype, CachedModel.__identityCache); + } + + static __constructFresh(ptr) { + const obj = Object.create(CachedModel.prototype); + const state = { pointer: ptr, deinit: instance.exports.bjs_CachedModel_deinit, hasReleased: false, identityMap: CachedModel.__identityCache }; + obj.pointer = ptr; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + CachedModel.__identityCache.set(ptr, new WeakRef(obj)); + return obj; + } + + constructor(name) { + const nameBytes = textEncoder.encode(name); + const nameId = swift.memory.retain(nameBytes); + const ret = instance.exports.bjs_CachedModel_init(nameId, nameBytes.length); + return CachedModel.__constructFresh(ret); + } + get name() { + instance.exports.bjs_CachedModel_name_get(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + set name(value) { + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + instance.exports.bjs_CachedModel_name_set(this.pointer, valueId, valueBytes.length); + } + } + class UncachedModel extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_UncachedModel_deinit, UncachedModel.prototype, null); + } + + constructor(value) { + const ret = instance.exports.bjs_UncachedModel_init(value); + return UncachedModel.__construct(ret); + } + get value() { + const ret = instance.exports.bjs_UncachedModel_value_get(this.pointer); + return ret; + } + set value(value) { + instance.exports.bjs_UncachedModel_value_set(this.pointer, value); + } + } + class ExplicitlyUncachedModel extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_ExplicitlyUncachedModel_deinit, ExplicitlyUncachedModel.prototype, null); + } + + constructor(count) { + const ret = instance.exports.bjs_ExplicitlyUncachedModel_init(count); + return ExplicitlyUncachedModel.__construct(ret); + } + get count() { + const ret = instance.exports.bjs_ExplicitlyUncachedModel_count_get(this.pointer); + return ret; + } + set count(value) { + instance.exports.bjs_ExplicitlyUncachedModel_count_set(this.pointer, value); + } + } + const exports = { + CachedModel, + UncachedModel, + ExplicitlyUncachedModel, + }; + _exports = exports; + return exports; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeSwiftClass.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeSwiftClass.d.ts new file mode 100644 index 000000000..63047ee5a --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeSwiftClass.d.ts @@ -0,0 +1,42 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +/// Represents a Swift heap object like a class instance or an actor instance. +export interface SwiftHeapObject { + /// Release the heap object. + /// + /// Note: Calling this method will release the heap object and it will no longer be accessible. + release(): void; +} +export interface SwiftCached extends SwiftHeapObject { + name: string; +} +export interface WeakCached extends SwiftHeapObject { + value: number; +} +export interface Untouched extends SwiftHeapObject { + v: number; +} +export type Exports = { + SwiftCached: { + new(name: string): SwiftCached; + } + WeakCached: { + new(value: number): WeakCached; + } + Untouched: { + new(v: number): Untouched; + } +} +export type Imports = { +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeSwiftClass.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeSwiftClass.js new file mode 100644 index 000000000..773c8283f --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeSwiftClass.js @@ -0,0 +1,375 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + let decodeString; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + let tmpRetOptionalBool; + let tmpRetOptionalInt; + let tmpRetOptionalFloat; + let tmpRetOptionalDouble; + let tmpRetOptionalHeapObject; + let strStack = []; + let i32Stack = []; + let i64Stack = []; + let f32Stack = []; + let f64Stack = []; + let ptrStack = []; + const enumHelpers = {}; + const structHelpers = {}; + + let _exports = null; + let bjs = null; + + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + bjs = {}; + importObject["bjs"] = bjs; + bjs["swift_js_return_string"] = function(ptr, len) { + tmpRetString = decodeString(ptr, len); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + swift.memory.release(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + return swift.memory.retain(decodeString(ptr, len)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + bjs["swift_js_push_i32"] = function(v) { + i32Stack.push(v | 0); + } + bjs["swift_js_push_f32"] = function(v) { + f32Stack.push(Math.fround(v)); + } + bjs["swift_js_push_f64"] = function(v) { + f64Stack.push(v); + } + bjs["swift_js_push_string"] = function(ptr, len) { + const value = decodeString(ptr, len); + strStack.push(value); + } + bjs["swift_js_pop_i32"] = function() { + return i32Stack.pop(); + } + bjs["swift_js_pop_f32"] = function() { + return f32Stack.pop(); + } + bjs["swift_js_pop_f64"] = function() { + return f64Stack.pop(); + } + bjs["swift_js_push_pointer"] = function(pointer) { + ptrStack.push(pointer); + } + bjs["swift_js_pop_pointer"] = function() { + return ptrStack.pop(); + } + bjs["swift_js_push_i64"] = function(v) { + i64Stack.push(v); + } + bjs["swift_js_pop_i64"] = function() { + return i64Stack.pop(); + } + bjs["swift_js_return_optional_bool"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalBool = null; + } else { + tmpRetOptionalBool = value !== 0; + } + } + bjs["swift_js_return_optional_int"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalInt = null; + } else { + tmpRetOptionalInt = value | 0; + } + } + bjs["swift_js_return_optional_float"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalFloat = null; + } else { + tmpRetOptionalFloat = Math.fround(value); + } + } + bjs["swift_js_return_optional_double"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalDouble = null; + } else { + tmpRetOptionalDouble = value; + } + } + bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + if (isSome === 0) { + tmpRetString = null; + } else { + tmpRetString = decodeString(ptr, len); + } + } + bjs["swift_js_return_optional_object"] = function(isSome, objectId) { + if (isSome === 0) { + tmpRetString = null; + } else { + tmpRetString = swift.memory.getObject(objectId); + } + } + bjs["swift_js_return_optional_heap_object"] = function(isSome, pointer) { + if (isSome === 0) { + tmpRetOptionalHeapObject = null; + } else { + tmpRetOptionalHeapObject = pointer; + } + } + bjs["swift_js_get_optional_int_presence"] = function() { + return tmpRetOptionalInt != null ? 1 : 0; + } + bjs["swift_js_get_optional_int_value"] = function() { + const value = tmpRetOptionalInt; + tmpRetOptionalInt = undefined; + return value; + } + bjs["swift_js_get_optional_string"] = function() { + const str = tmpRetString; + tmpRetString = undefined; + if (str == null) { + return -1; + } else { + const bytes = textEncoder.encode(str); + tmpRetBytes = bytes; + return bytes.length; + } + } + bjs["swift_js_get_optional_float_presence"] = function() { + return tmpRetOptionalFloat != null ? 1 : 0; + } + bjs["swift_js_get_optional_float_value"] = function() { + const value = tmpRetOptionalFloat; + tmpRetOptionalFloat = undefined; + return value; + } + bjs["swift_js_get_optional_double_presence"] = function() { + return tmpRetOptionalDouble != null ? 1 : 0; + } + bjs["swift_js_get_optional_double_value"] = function() { + const value = tmpRetOptionalDouble; + tmpRetOptionalDouble = undefined; + return value; + } + bjs["swift_js_get_optional_heap_object_pointer"] = function() { + const pointer = tmpRetOptionalHeapObject; + tmpRetOptionalHeapObject = undefined; + return pointer || 0; + } + bjs["swift_js_closure_unregister"] = function(funcRef) {} + // Wrapper functions for module: TestModule + if (!importObject["TestModule"]) { + importObject["TestModule"] = {}; + } + importObject["TestModule"]["bjs_SwiftCached_wrap"] = function(pointer) { + const obj = _exports['SwiftCached'].__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_Untouched_wrap"] = function(pointer) { + const obj = _exports['Untouched'].__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_WeakCached_wrap"] = function(pointer) { + const obj = _exports['WeakCached'].__construct(pointer); + return swift.memory.retain(obj); + }; + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + + decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + const swiftHeapObjectFinalizationRegistry = (typeof FinalizationRegistry === "undefined") ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry((state) => { + if (state.hasReleased) { + return; + } + state.hasReleased = true; + state.identityMap?.delete(state.pointer); + state.deinit(state.pointer); + }); + + /// Represents a Swift heap object like a class instance or an actor instance. + class SwiftHeapObject { + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); + } + + release() { + const state = this.__swiftHeapObjectState; + if (state.hasReleased) { + return; + } + state.hasReleased = true; + swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); + state.deinit(state.pointer); + } + } + class SwiftCached { + static __swiftIdentityWrappers = new Map(); + + static __wrap(pointer) { + const cached = SwiftCached.__swiftIdentityWrappers.get(pointer); + if (cached !== undefined) return cached; + const obj = Object.create(SwiftCached.prototype); + obj.pointer = pointer; + obj.__swiftIdentityHasReleased = false; + SwiftCached.__swiftIdentityWrappers.set(pointer, obj); + return obj; + } + + static __construct(pointer) { + return SwiftCached.__wrap(pointer); + } + + release() { + if (this.__swiftIdentityHasReleased) return; + this.__swiftIdentityHasReleased = true; + const pointer = this.pointer; + instance.exports.bjs_SwiftCached_release_wrapper(pointer); + SwiftCached.__swiftIdentityWrappers.delete(pointer); + } + constructor(name) { + const nameBytes = textEncoder.encode(name); + const nameId = swift.memory.retain(nameBytes); + const ret = instance.exports.bjs_SwiftCached_init(nameId, nameBytes.length); + return SwiftCached.__wrap(ret); + } + get name() { + if (this.__swiftIdentityHasReleased) { + throw new Error("Attempted to call a member on a released SwiftCached"); + } + instance.exports.bjs_SwiftCached_name_get(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + set name(value) { + if (this.__swiftIdentityHasReleased) { + throw new Error("Attempted to call a member on a released SwiftCached"); + } + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + instance.exports.bjs_SwiftCached_name_set(this.pointer, valueId, valueBytes.length); + } + } + class WeakCached extends SwiftHeapObject { + static __identityCache = new Map(); + + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_WeakCached_deinit, WeakCached.prototype, WeakCached.__identityCache); + } + + static __constructFresh(ptr) { + const obj = Object.create(WeakCached.prototype); + const state = { pointer: ptr, deinit: instance.exports.bjs_WeakCached_deinit, hasReleased: false, identityMap: WeakCached.__identityCache }; + obj.pointer = ptr; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + WeakCached.__identityCache.set(ptr, new WeakRef(obj)); + return obj; + } + + constructor(value) { + const ret = instance.exports.bjs_WeakCached_init(value); + return WeakCached.__constructFresh(ret); + } + get value() { + const ret = instance.exports.bjs_WeakCached_value_get(this.pointer); + return ret; + } + set value(value) { + instance.exports.bjs_WeakCached_value_set(this.pointer, value); + } + } + class Untouched extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Untouched_deinit, Untouched.prototype, null); + } + + constructor(v) { + const ret = instance.exports.bjs_Untouched_init(v); + return Untouched.__construct(ret); + } + get v() { + const ret = instance.exports.bjs_Untouched_v_get(this.pointer); + return ret; + } + set v(value) { + instance.exports.bjs_Untouched_v_set(this.pointer, value); + } + } + const exports = { + SwiftCached, + WeakCached, + Untouched, + }; + _exports = exports; + return exports; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js index 08675da6a..0258b63b6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js @@ -342,18 +342,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -363,12 +384,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class JSValueHolder extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_JSValueHolder_deinit, JSValueHolder.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_JSValueHolder_deinit, JSValueHolder.prototype, null); } constructor(value, optionalValue) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.js index f4fe4dd61..195eef468 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.js @@ -215,18 +215,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -236,12 +257,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class GlobalClass extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_GlobalAPI_GlobalClass_deinit, GlobalClass.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_GlobalAPI_GlobalClass_deinit, GlobalClass.prototype, null); } constructor() { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.js index 4ce318f40..ca54493f6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.js @@ -223,18 +223,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -244,12 +265,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class GlobalClass extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_GlobalAPI_GlobalClass_deinit, GlobalClass.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_GlobalAPI_GlobalClass_deinit, GlobalClass.prototype, null); } constructor() { @@ -265,7 +287,7 @@ export async function createInstantiator(options, swift) { } class PrivateClass extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PrivateAPI_PrivateClass_deinit, PrivateClass.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PrivateAPI_PrivateClass_deinit, PrivateClass.prototype, null); } constructor() { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.js index 025a6fc8a..3551caab2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.js @@ -215,18 +215,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -236,12 +257,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class PrivateClass extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PrivateAPI_PrivateClass_deinit, PrivateClass.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PrivateAPI_PrivateClass_deinit, PrivateClass.prototype, null); } constructor() { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.js index f4596dba7..a63df44be 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.js @@ -227,18 +227,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -248,12 +269,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class Greeter extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs___Swift_Foundation_Greeter_deinit, Greeter.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs___Swift_Foundation_Greeter_deinit, Greeter.prototype, null); } constructor(name) { @@ -281,7 +303,7 @@ export async function createInstantiator(options, swift) { } class Converter extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Utils_Converters_Converter_deinit, Converter.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Utils_Converters_Converter_deinit, Converter.prototype, null); } constructor() { @@ -297,7 +319,7 @@ export async function createInstantiator(options, swift) { } class UUID extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs___Swift_Foundation_UUID_deinit, UUID.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs___Swift_Foundation_UUID_deinit, UUID.prototype, null); } uuidString() { @@ -309,7 +331,7 @@ export async function createInstantiator(options, swift) { } class Container extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Collections_Container_deinit, Container.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Collections_Container_deinit, Container.prototype, null); } constructor() { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.js index 92ce69cbb..32a325bda 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.js @@ -227,18 +227,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -248,12 +269,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class Greeter extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs___Swift_Foundation_Greeter_deinit, Greeter.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs___Swift_Foundation_Greeter_deinit, Greeter.prototype, null); } constructor(name) { @@ -281,7 +303,7 @@ export async function createInstantiator(options, swift) { } class Converter extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Utils_Converters_Converter_deinit, Converter.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Utils_Converters_Converter_deinit, Converter.prototype, null); } constructor() { @@ -297,7 +319,7 @@ export async function createInstantiator(options, swift) { } class UUID extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs___Swift_Foundation_UUID_deinit, UUID.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs___Swift_Foundation_UUID_deinit, UUID.prototype, null); } uuidString() { @@ -309,7 +331,7 @@ export async function createInstantiator(options, swift) { } class Container extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Collections_Container_deinit, Container.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Collections_Container_deinit, Container.prototype, null); } constructor() { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.js index 37408a42d..5971c2fa8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.js @@ -471,18 +471,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -492,12 +513,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class Greeter extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Greeter_deinit, Greeter.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Greeter_deinit, Greeter.prototype, null); } constructor(name) { @@ -558,7 +580,7 @@ export async function createInstantiator(options, swift) { } class OptionalPropertyHolder extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_OptionalPropertyHolder_deinit, OptionalPropertyHolder.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_OptionalPropertyHolder_deinit, OptionalPropertyHolder.prototype, null); } constructor() { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.js index 658702a39..7f840708e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.js @@ -215,18 +215,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -236,12 +257,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class PropertyHolder extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PropertyHolder_deinit, PropertyHolder.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PropertyHolder_deinit, PropertyHolder.prototype, null); } constructor(intValue, floatValue, doubleValue, boolValue, stringValue, jsObject) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js index f82e41703..9fb9b172b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js @@ -575,18 +575,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -596,12 +617,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class Helper extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Helper_deinit, Helper.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Helper_deinit, Helper.prototype, null); } constructor(value) { @@ -621,7 +643,7 @@ export async function createInstantiator(options, swift) { } class MyViewController extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_MyViewController_deinit, MyViewController.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_MyViewController_deinit, MyViewController.prototype, null); } constructor(delegate) { @@ -682,7 +704,7 @@ export async function createInstantiator(options, swift) { } class DelegateManager extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_DelegateManager_deinit, DelegateManager.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_DelegateManager_deinit, DelegateManager.prototype, null); } constructor(delegates) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ProtocolInClosure.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ProtocolInClosure.js index 13070a3cc..aefdb5679 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ProtocolInClosure.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ProtocolInClosure.js @@ -361,18 +361,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -382,12 +403,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class Widget extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Widget_deinit, Widget.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Widget_deinit, Widget.prototype, null); } constructor(name) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.js index 42e25545e..ef685b8a4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.js @@ -259,18 +259,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -280,12 +301,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class MathUtils extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_MathUtils_deinit, MathUtils.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_MathUtils_deinit, MathUtils.prototype, null); } constructor() { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.js index 4cf9615fb..1fd066076 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.js @@ -259,18 +259,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -280,12 +301,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class MathUtils extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_MathUtils_deinit, MathUtils.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_MathUtils_deinit, MathUtils.prototype, null); } constructor() { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.js index 9928804eb..5800fcb56 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.js @@ -220,18 +220,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -241,12 +262,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class PropertyClass extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PropertyClass_deinit, PropertyClass.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PropertyClass_deinit, PropertyClass.prototype, null); } constructor() { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.js index f82ac20df..b81255810 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.js @@ -220,18 +220,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -241,12 +262,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class PropertyClass extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PropertyClass_deinit, PropertyClass.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PropertyClass_deinit, PropertyClass.prototype, null); } constructor() { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js index cf9faa707..9ee57d692 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js @@ -243,18 +243,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -264,12 +285,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class Greeter extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Greeter_deinit, Greeter.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Greeter_deinit, Greeter.prototype, null); } constructor(name) { @@ -325,13 +347,13 @@ export async function createInstantiator(options, swift) { } class PublicGreeter extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PublicGreeter_deinit, PublicGreeter.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PublicGreeter_deinit, PublicGreeter.prototype, null); } } class PackageGreeter extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PackageGreeter_deinit, PackageGreeter.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PackageGreeter_deinit, PackageGreeter.prototype, null); } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js index 7e90c9415..03a5504e4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js @@ -902,18 +902,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -923,12 +944,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class Person extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Person_deinit, Person.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Person_deinit, Person.prototype, null); } constructor(name) { @@ -940,7 +962,7 @@ export async function createInstantiator(options, swift) { } class TestProcessor extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_TestProcessor_deinit, TestProcessor.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_TestProcessor_deinit, TestProcessor.prototype, null); } constructor(transform) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js index a60615686..abfb24d48 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js @@ -484,18 +484,39 @@ export async function createInstantiator(options, swift) { return; } state.hasReleased = true; + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); }); /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __wrap(pointer, deinit, prototype) { - const obj = Object.create(prototype); - const state = { pointer, deinit, hasReleased: false }; - obj.pointer = pointer; - obj.__swiftHeapObjectState = state; - swiftHeapObjectFinalizationRegistry.register(obj, state, state); - return obj; + static __wrap(pointer, deinit, prototype, identityCache) { + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); } release() { @@ -505,12 +526,13 @@ export async function createInstantiator(options, swift) { } state.hasReleased = true; swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); state.deinit(state.pointer); } } class Greeter extends SwiftHeapObject { static __construct(ptr) { - return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Greeter_deinit, Greeter.prototype); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Greeter_deinit, Greeter.prototype, null); } constructor(name) { diff --git a/Plugins/PackageToJS/Templates/index.d.ts b/Plugins/PackageToJS/Templates/index.d.ts index fb063f0ab..f3491b4e3 100644 --- a/Plugins/PackageToJS/Templates/index.d.ts +++ b/Plugins/PackageToJS/Templates/index.d.ts @@ -1,21 +1,21 @@ -import type { Exports, Imports, ModuleSource } from './instantiate.js' +import type { Exports, Imports, ModuleSource } from "./instantiate.js"; export type Options = { -/* #if TARGET_DEFAULT_PLATFORM_BROWSER */ + /* #if TARGET_DEFAULT_PLATFORM_BROWSER */ /** * The WebAssembly module to instantiate * * If not provided, the module will be fetched from the default path. */ - module?: ModuleSource -/* #endif */ -/* #if HAS_IMPORTS */ + module?: ModuleSource; + /* #endif */ + /* #if HAS_IMPORTS */ /** * The imports to use for the module */ - getImports: () => Imports -/* #endif */ -} + getImports: () => Imports; + /* #endif */ +}; /** * Instantiate and initialize the module @@ -24,6 +24,6 @@ export type Options = { * If you need a more flexible API, see `instantiate`. */ export declare function init(options?: Options): Promise<{ - instance: WebAssembly.Instance, - exports: Exports -}> + instance: WebAssembly.Instance; + exports: Exports; +}>; diff --git a/Plugins/PackageToJS/Templates/instantiate.d.ts b/Plugins/PackageToJS/Templates/instantiate.d.ts index 9074d8d2b..8f6170beb 100644 --- a/Plugins/PackageToJS/Templates/instantiate.d.ts +++ b/Plugins/PackageToJS/Templates/instantiate.d.ts @@ -129,6 +129,7 @@ export type InstantiateOptions = { _swift: SwiftRuntime } ) => WebAssembly.Instance + } /** diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Configuration.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Configuration.md index 604017aad..7b79b8877 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Configuration.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Configuration.md @@ -73,6 +73,26 @@ const greeter = new exports.MyModule.Greeter("World"); // globalThis.MyModule is undefined ``` +### `identityMode` + +Default wrapper-identity policy for `@JS class` returns in this module. Controls whether two Swift→JS returns of the same Swift object produce the same JS wrapper (`===`), and where the identity cache lives. + +| Value | Effect | +|-------|--------| +| `"none"` (default) | No cache. Every return allocates a fresh wrapper. | +| `"pointer"` | JS-side `WeakRef` cache. `===` holds while reachable; wrapper may be GC'd. | +| `"swift"` | Swift-owned strong cache. `===` holds until explicit `release()`. Best for create-heavy workloads. | + +Example: + +```json +{ + "identityMode": "swift" +} +``` + +A per-class `@JS(identityMode: .none | .pointer | .swift)` annotation overrides the config default for a single class. See for the full comparison, tradeoffs, and benchmark results. + ### `tools` Specify custom paths for external executables. This is particularly useful when working in environments like Xcode where the system PATH may not be inherited, or when you need to use a specific version of tools for your project. diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md index b14d2eebe..1155c327b 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md @@ -16,6 +16,7 @@ Configure your package and build for JavaScript as described in - +- - - - diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Class.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Class.md index a16c81286..8ee8eddba 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Class.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Class.md @@ -153,3 +153,7 @@ This differs from structs, which use copy semantics and transfer data by value. | Extension methods/properties | ✅ | | Subscripts: `subscript()` | ❌ | | Generics | ❌ | + +## Wrapper identity (`===` on re-export) + +By default, Swift→JS returns allocate a fresh JS wrapper every time, so `===` between two returns of the same Swift object is `false`. Opt into stable identity per class with `@JS(identityMode: .pointer)` (weak, JS-side cache) or `@JS(identityMode: .swift)` (strong, Swift-side cache — best for create-heavy workloads). See . diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Identity-Modes-For-Exported-Classes.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Identity-Modes-For-Exported-Classes.md new file mode 100644 index 000000000..2606b8c59 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Identity-Modes-For-Exported-Classes.md @@ -0,0 +1,114 @@ +# Identity Modes for Exported Classes + +Control whether a Swift class returned to JavaScript produces a stable JS wrapper on re-export, and where the identity cache lives. + +## Overview + +Every time an `@JS class` instance crosses the Swift→JS boundary, BridgeJS has to give JavaScript *some* value that represents the underlying Swift object. By default, it builds a fresh JS wrapper each time — so two calls that return the same Swift object produce two different JS objects: + +```javascript +const a = exports.getCurrentUser(); +const b = exports.getCurrentUser(); +a === b; // false — two different wrappers around the same Swift object +``` + +For many apps that's fine. But if your JS code uses `===` to compare, or stores wrappers as keys in a `Map`/`Set`, or keeps mutable state on the wrapper, you probably want stable identity. BridgeJS offers three policies via `identityMode`: + +| Mode | Meaning | When to use | +|------|---------|-------------| +| `.none` | No cache. Fresh wrapper on every crossing. **Default.** | You don't need `===` identity and you want zero bookkeeping overhead. | +| `.pointer` | JS-side `WeakRef` cache. `===` holds while the wrapper is reachable. | Hit-heavy workloads with many long-lived wrappers. You want weak semantics (wrapper can be GC'd if JS code drops its reference). | +| `.swift` | Swift-owned strong cache. `===` holds until explicit `release()`. | Create-heavy or churn-heavy workloads. You want the lowest per-miss cost and don't mind retaining wrappers until you explicitly release. | + +## Per-class opt-in + +```swift +import JavaScriptKit + +@JS(identityMode: .swift) +class Building { + @JS var name: String + @JS init(name: String) { self.name = name } +} +``` + +Classes without an `identityMode:` argument inherit the project default (see below). A per-class annotation always wins over the config default. + +## Project-wide default + +Set a default in `bridge-js.config.json`: + +```json +{ "identityMode": "swift" } +``` + +This makes every `@JS class` in the project use swift mode unless it explicitly overrides with its own annotation. The three valid string values are `"none"`, `"pointer"`, and `"swift"`. + +## What each mode does at runtime + +### `.none` (default) + +Every Swift→JS return allocates a new wrapper. Wrappers release via a `FinalizationRegistry` when JS GCs them. No identity guarantees; lowest peak memory for short-lived crossings. + +### `.pointer` + +BridgeJS keeps a `Map>` on the JS side, keyed by the raw Swift pointer. On re-export, if the weakref still deref'es to a live wrapper, the same wrapper is returned. Wrappers can still be GC'd if JS drops all references — and on the next re-export a fresh wrapper is built. `===` is "best-effort" in the face of GC. + +### `.swift` + +The Swift side keeps an authoritative `pointer → id` table per class, plus an `id → JavaScriptObjectRef` table holding a strong reference to the JS wrapper. On re-export, Swift returns the known id, and JS fetches the cached wrapper from a dense array by integer index — no `WeakRef.deref`, no `Map.get`. On a fresh object, Swift tells JS to build a new wrapper and JS registers its retained ref back with Swift. + +The key differences vs `.pointer`: + +- **Stronger identity.** The wrapper is strongly retained by Swift until JS calls `release()`. GC will NOT collect it. +- **Faster miss path.** No `FinalizationRegistry.register`, no `WeakRef` allocation per miss. Benchmarks show a 1.7–2.3× improvement on create-heavy and churn-heavy workloads vs `.pointer`. +- **Higher peak memory.** A wrapper that's created and forgotten without `release()` lives for the lifetime of its Swift heap object. Use `.pointer` or `.none` for streaming / short-lived workloads. + +## Lifecycle + +```javascript +const b = exports.getBuilding(); // allocates a wrapper (if fresh) +b.name; // stable === with any future getBuilding() that returns the same Swift object +b.release(); // frees the Swift heap object and the wrapper slot +// after release(): b.name throws "Attempted to call a member on a released Building" +``` + +Double-release is a safe no-op. Static members (class-level methods, constructors) are not affected by release. + +### Why `release()` is required + +`.pointer` mode uses a `WeakRef` and a `FinalizationRegistry` to let the JS garbage collector reclaim wrappers whose users have dropped all references. That safety net is exactly what makes `.pointer` mode's miss path expensive — `FinalizationRegistry.register` and `new WeakRef` allocations account for ~88% of the per-miss cost. `.swift` mode removes both, which is where its performance comes from. The tradeoff is that the JS garbage collector no longer has any hook to trigger cleanup; Swift holds a strong retain until you explicitly release. + +In practice this is the same discipline as any manually-managed resource (file handles, network sockets, native-backed buffers). Use `try { … } finally { x.release() }` for scopes that can throw, or wrap long-lived objects in application code that owns their release. + +If you can't live with explicit release, stay on `.pointer` mode. + +## Benchmarks (summary) + +Full results in [`Benchmarks/results/swift-side-cache/Benchmarks.md`](../../../../../Benchmarks/results/swift-side-cache/Benchmarks.md). Baseline arm64-macOS, Swift 6.3, Node 22, release build, 500k iters per scenario, median ms: + +| Scenario | `.none` | `.pointer` | `.swift` | +|---|---:|---:|---:| +| `passBothWaysRoundtrip` (hit) | 160 | 31 | **26** | +| `getPoolRepeated_100` (hit) | 183 | 46 | **32** | +| `swiftCreatesObject` (miss) | 514 | 2021 | **593** | +| `churnObjects` (create/release) | — | 793 | **317** | +| `swiftConsumesSameObject` | 17 | 10 | **10** | + +`.swift` mode is a strict Pareto improvement over `.pointer` mode: faster on hit, 3.4× faster on miss, 2.5× faster on churn, parity on one-way. + +## Known limitations + +- **No GC-driven cleanup.** Wrappers live until `release()` is called. Dropping all JS references to a wrapper without releasing leaks the Swift heap object. See "Why `release()` is required" above. +- **Wasm-only.** Like all of BridgeJS, identity modes only activate on `wasm32`. On the host, the macro expands to no-op code paths so your test harness can still compile. + +## Choosing a mode + +- **Most apps:** leave as `.none`. You don't need identity, don't pay the cost. +- **Long-lived object graph you cache on the JS side and you want weak GC-safe semantics:** `.pointer`. +- **Create-heavy, churn-heavy, or hit-heavy workloads where explicit `release()` is acceptable:** `.swift`. Strictly faster than `.pointer` on every benchmark scenario; only difference is strong retention until release. + +## See also + +- +- diff --git a/Sources/JavaScriptKit/JSIdentityMode.swift b/Sources/JavaScriptKit/JSIdentityMode.swift new file mode 100644 index 000000000..1bc7a66de --- /dev/null +++ b/Sources/JavaScriptKit/JSIdentityMode.swift @@ -0,0 +1,10 @@ +/// Per-class identity caching policy for `@JS`-exported classes. +/// +/// - `.none`: no identity caching; each boundary crossing produces a fresh JS wrapper. +/// - `.pointer`: JS-side weak cache keyed by the Swift pointer. +/// - `.swift`: Swift-owned strong cache; wrappers live until explicit `release()`. +public enum JSIdentityMode: String, Sendable { + case none + case pointer + case swift +} diff --git a/Sources/JavaScriptKit/Macros.swift b/Sources/JavaScriptKit/Macros.swift index 67b3488bf..8666aed17 100644 --- a/Sources/JavaScriptKit/Macros.swift +++ b/Sources/JavaScriptKit/Macros.swift @@ -113,7 +113,8 @@ public enum JSImportFrom: String { /// /// - Important: This feature is still experimental. No API stability is guaranteed, and the API may change in future releases. @attached(peer) -public macro JS(namespace: String? = nil, enumStyle: JSEnumStyle = .const) = Builtin.ExternalMacro +public macro JS(namespace: String? = nil, enumStyle: JSEnumStyle = .const, identityMode: JSIdentityMode = .none) = + Builtin.ExternalMacro /// A macro that generates a Swift getter that reads a value from JavaScript. /// diff --git a/Tests/BridgeJSIdentityTests/Generated/BridgeJS.swift b/Tests/BridgeJSIdentityTests/Generated/BridgeJS.swift new file mode 100644 index 000000000..ca2fb3688 --- /dev/null +++ b/Tests/BridgeJSIdentityTests/Generated/BridgeJS.swift @@ -0,0 +1,779 @@ +// bridge-js: skip +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit + +@_expose(wasm, "bjs_getSharedSubject") +@_cdecl("bjs_getSharedSubject") +public func _bjs_getSharedSubject() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = getSharedSubject() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_resetSharedSubject") +@_cdecl("bjs_resetSharedSubject") +public func _bjs_resetSharedSubject() -> Void { + #if arch(wasm32) + resetSharedSubject() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getRetainLeakSubject") +@_cdecl("bjs_getRetainLeakSubject") +public func _bjs_getRetainLeakSubject() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = getRetainLeakSubject() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_resetRetainLeakSubject") +@_cdecl("bjs_resetRetainLeakSubject") +public func _bjs_resetRetainLeakSubject() -> Void { + #if arch(wasm32) + resetRetainLeakSubject() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getRetainLeakDeinits") +@_cdecl("bjs_getRetainLeakDeinits") +public func _bjs_getRetainLeakDeinits() -> Int32 { + #if arch(wasm32) + let ret = getRetainLeakDeinits() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_resetRetainLeakDeinits") +@_cdecl("bjs_resetRetainLeakDeinits") +public func _bjs_resetRetainLeakDeinits() -> Void { + #if arch(wasm32) + resetRetainLeakDeinits() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setupArrayPool") +@_cdecl("bjs_setupArrayPool") +public func _bjs_setupArrayPool(_ count: Int32) -> Void { + #if arch(wasm32) + setupArrayPool(_: Int.bridgeJSLiftParameter(count)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getArrayPool") +@_cdecl("bjs_getArrayPool") +public func _bjs_getArrayPool() -> Void { + #if arch(wasm32) + let ret = getArrayPool() + ret.bridgeJSStackPush() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getArrayPoolElement") +@_cdecl("bjs_getArrayPoolElement") +public func _bjs_getArrayPoolElement(_ index: Int32) -> Void { + #if arch(wasm32) + let ret = getArrayPoolElement(_: Int.bridgeJSLiftParameter(index)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getArrayPoolDeinits") +@_cdecl("bjs_getArrayPoolDeinits") +public func _bjs_getArrayPoolDeinits() -> Int32 { + #if arch(wasm32) + let ret = getArrayPoolDeinits() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_resetArrayPoolDeinits") +@_cdecl("bjs_resetArrayPoolDeinits") +public func _bjs_resetArrayPoolDeinits() -> Void { + #if arch(wasm32) + resetArrayPoolDeinits() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_clearArrayPool") +@_cdecl("bjs_clearArrayPool") +public func _bjs_clearArrayPool() -> Void { + #if arch(wasm32) + clearArrayPool() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getSharedSwiftSubject") +@_cdecl("bjs_getSharedSwiftSubject") +public func _bjs_getSharedSwiftSubject() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = getSharedSwiftSubject() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_resetSharedSwiftSubject") +@_cdecl("bjs_resetSharedSwiftSubject") +public func _bjs_resetSharedSwiftSubject() -> Void { + #if arch(wasm32) + resetSharedSwiftSubject() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getRetainLeakSubjectSwift") +@_cdecl("bjs_getRetainLeakSubjectSwift") +public func _bjs_getRetainLeakSubjectSwift() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = getRetainLeakSubjectSwift() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_resetRetainLeakSubjectSwift") +@_cdecl("bjs_resetRetainLeakSubjectSwift") +public func _bjs_resetRetainLeakSubjectSwift() -> Void { + #if arch(wasm32) + resetRetainLeakSubjectSwift() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getRetainLeakDeinitsSwift") +@_cdecl("bjs_getRetainLeakDeinitsSwift") +public func _bjs_getRetainLeakDeinitsSwift() -> Int32 { + #if arch(wasm32) + let ret = getRetainLeakDeinitsSwift() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_resetRetainLeakDeinitsSwift") +@_cdecl("bjs_resetRetainLeakDeinitsSwift") +public func _bjs_resetRetainLeakDeinitsSwift() -> Void { + #if arch(wasm32) + resetRetainLeakDeinitsSwift() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getSwiftIdentityTableSizeForSharedSubject") +@_cdecl("bjs_getSwiftIdentityTableSizeForSharedSubject") +public func _bjs_getSwiftIdentityTableSizeForSharedSubject() -> Int32 { + #if arch(wasm32) + let ret = getSwiftIdentityTableSizeForSharedSubject() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_makeSwiftIdentityArray") +@_cdecl("bjs_makeSwiftIdentityArray") +public func _bjs_makeSwiftIdentityArray(_ a: UnsafeMutableRawPointer, _ b: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = makeSwiftIdentityArray(_: SwiftIdentityTestSubject.bridgeJSLiftParameter(a), _: SwiftIdentityTestSubject.bridgeJSLiftParameter(b)) + ret.bridgeJSStackPush() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_maybeSwiftSubject") +@_cdecl("bjs_maybeSwiftSubject") +public func _bjs_maybeSwiftSubject(_ present: Int32) -> Void { + #if arch(wasm32) + let ret = maybeSwiftSubject(_: Bool.bridgeJSLiftParameter(present)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getSwiftIdentityTableSizeForChurn") +@_cdecl("bjs_getSwiftIdentityTableSizeForChurn") +public func _bjs_getSwiftIdentityTableSizeForChurn() -> Int32 { + #if arch(wasm32) + let ret = getSwiftIdentityTableSizeForChurn() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_IdentityTestSubject_init") +@_cdecl("bjs_IdentityTestSubject_init") +public func _bjs_IdentityTestSubject_init(_ value: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = IdentityTestSubject(value: Int.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_IdentityTestSubject_value_get") +@_cdecl("bjs_IdentityTestSubject_value_get") +public func _bjs_IdentityTestSubject_value_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = IdentityTestSubject.bridgeJSLiftParameter(_self).value + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_IdentityTestSubject_value_set") +@_cdecl("bjs_IdentityTestSubject_value_set") +public func _bjs_IdentityTestSubject_value_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + IdentityTestSubject.bridgeJSLiftParameter(_self).value = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_IdentityTestSubject_currentValue_get") +@_cdecl("bjs_IdentityTestSubject_currentValue_get") +public func _bjs_IdentityTestSubject_currentValue_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = IdentityTestSubject.bridgeJSLiftParameter(_self).currentValue + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_IdentityTestSubject_deinit") +@_cdecl("bjs_IdentityTestSubject_deinit") +public func _bjs_IdentityTestSubject_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension IdentityTestSubject: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_IdentityTestSubject_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_IdentityTestSubject_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSIdentityTests", name: "bjs_IdentityTestSubject_wrap") +fileprivate func _bjs_IdentityTestSubject_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_IdentityTestSubject_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_IdentityTestSubject_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_IdentityTestSubject_wrap_extern(pointer) +} + +@_expose(wasm, "bjs_RetainLeakSubject_init") +@_cdecl("bjs_RetainLeakSubject_init") +public func _bjs_RetainLeakSubject_init(_ tag: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = RetainLeakSubject(tag: Int.bridgeJSLiftParameter(tag)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_RetainLeakSubject_tag_get") +@_cdecl("bjs_RetainLeakSubject_tag_get") +public func _bjs_RetainLeakSubject_tag_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = RetainLeakSubject.bridgeJSLiftParameter(_self).tag + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_RetainLeakSubject_tag_set") +@_cdecl("bjs_RetainLeakSubject_tag_set") +public func _bjs_RetainLeakSubject_tag_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + RetainLeakSubject.bridgeJSLiftParameter(_self).tag = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_RetainLeakSubject_deinit") +@_cdecl("bjs_RetainLeakSubject_deinit") +public func _bjs_RetainLeakSubject_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension RetainLeakSubject: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_RetainLeakSubject_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_RetainLeakSubject_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSIdentityTests", name: "bjs_RetainLeakSubject_wrap") +fileprivate func _bjs_RetainLeakSubject_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_RetainLeakSubject_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_RetainLeakSubject_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_RetainLeakSubject_wrap_extern(pointer) +} + +@_expose(wasm, "bjs_ArrayIdentityElement_init") +@_cdecl("bjs_ArrayIdentityElement_init") +public func _bjs_ArrayIdentityElement_init(_ tag: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ArrayIdentityElement(tag: Int.bridgeJSLiftParameter(tag)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ArrayIdentityElement_tag_get") +@_cdecl("bjs_ArrayIdentityElement_tag_get") +public func _bjs_ArrayIdentityElement_tag_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = ArrayIdentityElement.bridgeJSLiftParameter(_self).tag + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ArrayIdentityElement_tag_set") +@_cdecl("bjs_ArrayIdentityElement_tag_set") +public func _bjs_ArrayIdentityElement_tag_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + ArrayIdentityElement.bridgeJSLiftParameter(_self).tag = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ArrayIdentityElement_deinit") +@_cdecl("bjs_ArrayIdentityElement_deinit") +public func _bjs_ArrayIdentityElement_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension ArrayIdentityElement: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_ArrayIdentityElement_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_ArrayIdentityElement_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSIdentityTests", name: "bjs_ArrayIdentityElement_wrap") +fileprivate func _bjs_ArrayIdentityElement_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_ArrayIdentityElement_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_ArrayIdentityElement_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_ArrayIdentityElement_wrap_extern(pointer) +} + +nonisolated(unsafe) var _SwiftIdentityTestSubject_identityTable: Set = [] + +@_expose(wasm, "bjs_SwiftIdentityTestSubject_init") +@_cdecl("bjs_SwiftIdentityTestSubject_init") +public func _bjs_SwiftIdentityTestSubject_init(_ value: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = SwiftIdentityTestSubject(value: Int.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftIdentityTestSubject_self_") +@_cdecl("bjs_SwiftIdentityTestSubject_self_") +public func _bjs_SwiftIdentityTestSubject_self_(_ _self: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = SwiftIdentityTestSubject.bridgeJSLiftParameter(_self).self_() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftIdentityTestSubject_value_get") +@_cdecl("bjs_SwiftIdentityTestSubject_value_get") +public func _bjs_SwiftIdentityTestSubject_value_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = SwiftIdentityTestSubject.bridgeJSLiftParameter(_self).value + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftIdentityTestSubject_value_set") +@_cdecl("bjs_SwiftIdentityTestSubject_value_set") +public func _bjs_SwiftIdentityTestSubject_value_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + SwiftIdentityTestSubject.bridgeJSLiftParameter(_self).value = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftIdentityTestSubject_currentValue_get") +@_cdecl("bjs_SwiftIdentityTestSubject_currentValue_get") +public func _bjs_SwiftIdentityTestSubject_currentValue_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = SwiftIdentityTestSubject.bridgeJSLiftParameter(_self).currentValue + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftIdentityTestSubject_deinit") +@_cdecl("bjs_SwiftIdentityTestSubject_deinit") +public func _bjs_SwiftIdentityTestSubject_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftIdentityTestSubject_release_wrapper") +@_cdecl("bjs_SwiftIdentityTestSubject_release_wrapper") +public func _bjs_SwiftIdentityTestSubject_release_wrapper(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + guard _SwiftIdentityTestSubject_identityTable.remove(pointer) != nil else { return } + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension SwiftIdentityTestSubject { + @_spi(BridgeJS) @_transparent + public consuming func bridgeJSLowerReturn() -> UnsafeMutableRawPointer { + return withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _SwiftIdentityTestSubject_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + } + @_spi(BridgeJS) public consuming func bridgeJSStackPush() { + let ptr: UnsafeMutableRawPointer = withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _SwiftIdentityTestSubject_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + _swift_js_push_pointer(ptr) + } +} + +extension SwiftIdentityTestSubject: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_SwiftIdentityTestSubject_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_SwiftIdentityTestSubject_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSIdentityTests", name: "bjs_SwiftIdentityTestSubject_wrap") +fileprivate func _bjs_SwiftIdentityTestSubject_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_SwiftIdentityTestSubject_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_SwiftIdentityTestSubject_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_SwiftIdentityTestSubject_wrap_extern(pointer) +} + +nonisolated(unsafe) var _SwiftRetainLeakSubject_identityTable: Set = [] + +@_expose(wasm, "bjs_SwiftRetainLeakSubject_init") +@_cdecl("bjs_SwiftRetainLeakSubject_init") +public func _bjs_SwiftRetainLeakSubject_init(_ tag: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = SwiftRetainLeakSubject(tag: Int.bridgeJSLiftParameter(tag)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftRetainLeakSubject_tag_get") +@_cdecl("bjs_SwiftRetainLeakSubject_tag_get") +public func _bjs_SwiftRetainLeakSubject_tag_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = SwiftRetainLeakSubject.bridgeJSLiftParameter(_self).tag + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftRetainLeakSubject_tag_set") +@_cdecl("bjs_SwiftRetainLeakSubject_tag_set") +public func _bjs_SwiftRetainLeakSubject_tag_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + SwiftRetainLeakSubject.bridgeJSLiftParameter(_self).tag = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftRetainLeakSubject_deinit") +@_cdecl("bjs_SwiftRetainLeakSubject_deinit") +public func _bjs_SwiftRetainLeakSubject_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftRetainLeakSubject_release_wrapper") +@_cdecl("bjs_SwiftRetainLeakSubject_release_wrapper") +public func _bjs_SwiftRetainLeakSubject_release_wrapper(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + guard _SwiftRetainLeakSubject_identityTable.remove(pointer) != nil else { return } + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension SwiftRetainLeakSubject { + @_spi(BridgeJS) @_transparent + public consuming func bridgeJSLowerReturn() -> UnsafeMutableRawPointer { + return withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _SwiftRetainLeakSubject_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + } + @_spi(BridgeJS) public consuming func bridgeJSStackPush() { + let ptr: UnsafeMutableRawPointer = withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _SwiftRetainLeakSubject_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + _swift_js_push_pointer(ptr) + } +} + +extension SwiftRetainLeakSubject: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_SwiftRetainLeakSubject_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_SwiftRetainLeakSubject_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSIdentityTests", name: "bjs_SwiftRetainLeakSubject_wrap") +fileprivate func _bjs_SwiftRetainLeakSubject_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_SwiftRetainLeakSubject_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_SwiftRetainLeakSubject_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_SwiftRetainLeakSubject_wrap_extern(pointer) +} + +nonisolated(unsafe) var _SwiftChurnSubject_identityTable: Set = [] + +@_expose(wasm, "bjs_SwiftChurnSubject_init") +@_cdecl("bjs_SwiftChurnSubject_init") +public func _bjs_SwiftChurnSubject_init(_ tag: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = SwiftChurnSubject(tag: Int.bridgeJSLiftParameter(tag)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftChurnSubject_tag_get") +@_cdecl("bjs_SwiftChurnSubject_tag_get") +public func _bjs_SwiftChurnSubject_tag_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = SwiftChurnSubject.bridgeJSLiftParameter(_self).tag + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftChurnSubject_tag_set") +@_cdecl("bjs_SwiftChurnSubject_tag_set") +public func _bjs_SwiftChurnSubject_tag_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + SwiftChurnSubject.bridgeJSLiftParameter(_self).tag = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftChurnSubject_deinit") +@_cdecl("bjs_SwiftChurnSubject_deinit") +public func _bjs_SwiftChurnSubject_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftChurnSubject_release_wrapper") +@_cdecl("bjs_SwiftChurnSubject_release_wrapper") +public func _bjs_SwiftChurnSubject_release_wrapper(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + guard _SwiftChurnSubject_identityTable.remove(pointer) != nil else { return } + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension SwiftChurnSubject { + @_spi(BridgeJS) @_transparent + public consuming func bridgeJSLowerReturn() -> UnsafeMutableRawPointer { + return withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _SwiftChurnSubject_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + } + @_spi(BridgeJS) public consuming func bridgeJSStackPush() { + let ptr: UnsafeMutableRawPointer = withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _SwiftChurnSubject_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + _swift_js_push_pointer(ptr) + } +} + +extension SwiftChurnSubject: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_SwiftChurnSubject_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_SwiftChurnSubject_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSIdentityTests", name: "bjs_SwiftChurnSubject_wrap") +fileprivate func _bjs_SwiftChurnSubject_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_SwiftChurnSubject_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_SwiftChurnSubject_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_SwiftChurnSubject_wrap_extern(pointer) +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSIdentityTests", name: "bjs_IdentityModeTestImports_runJsIdentityModeTests_static") +fileprivate func bjs_IdentityModeTestImports_runJsIdentityModeTests_static_extern() -> Void +#else +fileprivate func bjs_IdentityModeTestImports_runJsIdentityModeTests_static_extern() -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_IdentityModeTestImports_runJsIdentityModeTests_static() -> Void { + return bjs_IdentityModeTestImports_runJsIdentityModeTests_static_extern() +} + +func _$IdentityModeTestImports_runJsIdentityModeTests() throws(JSException) -> Void { + bjs_IdentityModeTestImports_runJsIdentityModeTests_static() + if let error = _swift_js_take_exception() { + throw error + } +} \ No newline at end of file diff --git a/Tests/BridgeJSIdentityTests/Generated/JavaScript/BridgeJS.json b/Tests/BridgeJSIdentityTests/Generated/JavaScript/BridgeJS.json new file mode 100644 index 000000000..3a4a55056 --- /dev/null +++ b/Tests/BridgeJSIdentityTests/Generated/JavaScript/BridgeJS.json @@ -0,0 +1,824 @@ +{ + "exported" : { + "classes" : [ + { + "constructor" : { + "abiName" : "bjs_IdentityTestSubject_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "IdentityTestSubject", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "currentValue", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "IdentityTestSubject" + }, + { + "constructor" : { + "abiName" : "bjs_RetainLeakSubject_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "tag", + "name" : "tag", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "RetainLeakSubject", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "tag", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "RetainLeakSubject" + }, + { + "constructor" : { + "abiName" : "bjs_ArrayIdentityElement_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "tag", + "name" : "tag", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "ArrayIdentityElement", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "tag", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "ArrayIdentityElement" + }, + { + "constructor" : { + "abiName" : "bjs_SwiftIdentityTestSubject_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "identityMode" : "swift", + "methods" : [ + { + "abiName" : "bjs_SwiftIdentityTestSubject_self_", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "self_", + "parameters" : [ + + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "SwiftIdentityTestSubject" + } + } + } + ], + "name" : "SwiftIdentityTestSubject", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "currentValue", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "SwiftIdentityTestSubject" + }, + { + "constructor" : { + "abiName" : "bjs_SwiftRetainLeakSubject_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "tag", + "name" : "tag", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "identityMode" : "swift", + "methods" : [ + + ], + "name" : "SwiftRetainLeakSubject", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "tag", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "SwiftRetainLeakSubject" + }, + { + "constructor" : { + "abiName" : "bjs_SwiftChurnSubject_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "tag", + "name" : "tag", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "identityMode" : "swift", + "methods" : [ + + ], + "name" : "SwiftChurnSubject", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "tag", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "SwiftChurnSubject" + } + ], + "enums" : [ + + ], + "exposeToGlobal" : false, + "functions" : [ + { + "abiName" : "bjs_getSharedSubject", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getSharedSubject", + "parameters" : [ + + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "IdentityTestSubject" + } + } + }, + { + "abiName" : "bjs_resetSharedSubject", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "resetSharedSubject", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getRetainLeakSubject", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getRetainLeakSubject", + "parameters" : [ + + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "RetainLeakSubject" + } + } + }, + { + "abiName" : "bjs_resetRetainLeakSubject", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "resetRetainLeakSubject", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getRetainLeakDeinits", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getRetainLeakDeinits", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "abiName" : "bjs_resetRetainLeakDeinits", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "resetRetainLeakDeinits", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_setupArrayPool", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "setupArrayPool", + "parameters" : [ + { + "label" : "_", + "name" : "count", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getArrayPool", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getArrayPool", + "parameters" : [ + + ], + "returnType" : { + "array" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "ArrayIdentityElement" + } + } + } + } + }, + { + "abiName" : "bjs_getArrayPoolElement", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getArrayPoolElement", + "parameters" : [ + { + "label" : "_", + "name" : "index", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "ArrayIdentityElement" + } + }, + "_1" : "null" + } + } + }, + { + "abiName" : "bjs_getArrayPoolDeinits", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getArrayPoolDeinits", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "abiName" : "bjs_resetArrayPoolDeinits", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "resetArrayPoolDeinits", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_clearArrayPool", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "clearArrayPool", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getSharedSwiftSubject", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getSharedSwiftSubject", + "parameters" : [ + + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "SwiftIdentityTestSubject" + } + } + }, + { + "abiName" : "bjs_resetSharedSwiftSubject", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "resetSharedSwiftSubject", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getRetainLeakSubjectSwift", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getRetainLeakSubjectSwift", + "parameters" : [ + + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "SwiftRetainLeakSubject" + } + } + }, + { + "abiName" : "bjs_resetRetainLeakSubjectSwift", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "resetRetainLeakSubjectSwift", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getRetainLeakDeinitsSwift", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getRetainLeakDeinitsSwift", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "abiName" : "bjs_resetRetainLeakDeinitsSwift", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "resetRetainLeakDeinitsSwift", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getSwiftIdentityTableSizeForSharedSubject", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getSwiftIdentityTableSizeForSharedSubject", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "abiName" : "bjs_makeSwiftIdentityArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "makeSwiftIdentityArray", + "parameters" : [ + { + "label" : "_", + "name" : "a", + "type" : { + "swiftHeapObject" : { + "_0" : "SwiftIdentityTestSubject" + } + } + }, + { + "label" : "_", + "name" : "b", + "type" : { + "swiftHeapObject" : { + "_0" : "SwiftIdentityTestSubject" + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "SwiftIdentityTestSubject" + } + } + } + } + }, + { + "abiName" : "bjs_maybeSwiftSubject", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "maybeSwiftSubject", + "parameters" : [ + { + "label" : "_", + "name" : "present", + "type" : { + "bool" : { + + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "SwiftIdentityTestSubject" + } + }, + "_1" : "null" + } + } + }, + { + "abiName" : "bjs_getSwiftIdentityTableSizeForChurn", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getSwiftIdentityTableSizeForChurn", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "identityMode" : "pointer", + "protocols" : [ + + ], + "structs" : [ + + ] + }, + "imported" : { + "children" : [ + { + "functions" : [ + + ], + "types" : [ + { + "getters" : [ + + ], + "methods" : [ + + ], + "name" : "IdentityModeTestImports", + "setters" : [ + + ], + "staticMethods" : [ + { + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "name" : "runJsIdentityModeTests", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + } + ] + } + ] + } + ] + }, + "moduleName" : "BridgeJSIdentityTests" +} \ No newline at end of file diff --git a/Tests/BridgeJSIdentityTests/IdentityModeTests.swift b/Tests/BridgeJSIdentityTests/IdentityModeTests.swift new file mode 100644 index 000000000..76c5d0024 --- /dev/null +++ b/Tests/BridgeJSIdentityTests/IdentityModeTests.swift @@ -0,0 +1,207 @@ +import XCTest +import JavaScriptKit + +@JSClass struct IdentityModeTestImports { + @JSFunction static func runJsIdentityModeTests() throws(JSException) +} + +final class IdentityModeTests: XCTestCase { + func testRunJsIdentityModeTests() throws { + try IdentityModeTestImports.runJsIdentityModeTests() + } +} + +@JS class IdentityTestSubject { + @JS var value: Int + + @JS init(value: Int) { + self.value = value + } + + @JS var currentValue: Int { value } +} + +nonisolated(unsafe) private var _sharedSubject: IdentityTestSubject? + +@JS func getSharedSubject() -> IdentityTestSubject { + if _sharedSubject == nil { + _sharedSubject = IdentityTestSubject(value: 42) + } + return _sharedSubject! +} + +@JS func resetSharedSubject() { + _sharedSubject = nil +} + +@JS class RetainLeakSubject { + nonisolated(unsafe) static var deinits: Int = 0 + + @JS var tag: Int + + @JS init(tag: Int) { + self.tag = tag + } + + deinit { + Self.deinits += 1 + } +} + +nonisolated(unsafe) private var _retainLeakSubject: RetainLeakSubject? + +@JS func getRetainLeakSubject() -> RetainLeakSubject { + if _retainLeakSubject == nil { + _retainLeakSubject = RetainLeakSubject(tag: 1) + } + return _retainLeakSubject! +} + +@JS func resetRetainLeakSubject() { + _retainLeakSubject = nil +} + +@JS func getRetainLeakDeinits() -> Int { + RetainLeakSubject.deinits +} + +@JS func resetRetainLeakDeinits() { + RetainLeakSubject.deinits = 0 +} + +// MARK: - Array identity tests + +@JS class ArrayIdentityElement { + nonisolated(unsafe) static var deinits: Int = 0 + + @JS var tag: Int + + @JS init(tag: Int) { + self.tag = tag + } + + deinit { + Self.deinits += 1 + } +} + +nonisolated(unsafe) private var _arrayPool: [ArrayIdentityElement] = [] + +@JS func setupArrayPool(_ count: Int) { + _arrayPool = (0.. [ArrayIdentityElement] { + return _arrayPool +} + +@JS func getArrayPoolElement(_ index: Int) -> ArrayIdentityElement? { + guard index >= 0, index < _arrayPool.count else { return nil } + return _arrayPool[index] +} + +@JS func getArrayPoolDeinits() -> Int { + ArrayIdentityElement.deinits +} + +@JS func resetArrayPoolDeinits() { + ArrayIdentityElement.deinits = 0 +} + +@JS func clearArrayPool() { + _arrayPool = [] +} + +// MARK: - identityMode: .swift per-class opt-in (coexists with .pointer classes above) + +@JS(identityMode: .swift) class SwiftIdentityTestSubject { + @JS var value: Int + + @JS init(value: Int) { + self.value = value + } + + @JS var currentValue: Int { value } + + @JS func self_() -> SwiftIdentityTestSubject { self } +} + +nonisolated(unsafe) private var _sharedSwiftSubject: SwiftIdentityTestSubject? + +@JS func getSharedSwiftSubject() -> SwiftIdentityTestSubject { + if _sharedSwiftSubject == nil { + _sharedSwiftSubject = SwiftIdentityTestSubject(value: 42) + } + return _sharedSwiftSubject! +} + +@JS func resetSharedSwiftSubject() { + _sharedSwiftSubject = nil +} + +@JS(identityMode: .swift) class SwiftRetainLeakSubject { + nonisolated(unsafe) static var deinits: Int = 0 + + @JS var tag: Int + + @JS init(tag: Int) { + self.tag = tag + } + + deinit { + Self.deinits += 1 + } +} + +nonisolated(unsafe) private var _swiftRetainLeakSubject: SwiftRetainLeakSubject? + +@JS func getRetainLeakSubjectSwift() -> SwiftRetainLeakSubject { + if _swiftRetainLeakSubject == nil { + _swiftRetainLeakSubject = SwiftRetainLeakSubject(tag: 1) + } + return _swiftRetainLeakSubject! +} + +@JS func resetRetainLeakSubjectSwift() { + _swiftRetainLeakSubject = nil +} + +@JS func getRetainLeakDeinitsSwift() -> Int { + SwiftRetainLeakSubject.deinits +} + +@JS func resetRetainLeakDeinitsSwift() { + SwiftRetainLeakSubject.deinits = 0 +} + +@JS func getSwiftIdentityTableSizeForSharedSubject() -> Int { + _SwiftIdentityTestSubject_identityTable.count +} + +@JS func makeSwiftIdentityArray( + _ a: SwiftIdentityTestSubject, + _ b: SwiftIdentityTestSubject +) -> [SwiftIdentityTestSubject] { + return [a, b, a] +} + +@JS func maybeSwiftSubject(_ present: Bool) -> SwiftIdentityTestSubject? { + if _sharedSwiftSubject == nil { + _sharedSwiftSubject = SwiftIdentityTestSubject(value: 99) + } + return present ? _sharedSwiftSubject : nil +} + +// Dedicated churn class so its identity-table assertions aren't perturbed by +// the shared subjects above. +@JS(identityMode: .swift) class SwiftChurnSubject { + @JS var tag: Int + + @JS init(tag: Int) { + self.tag = tag + } +} + +@JS func getSwiftIdentityTableSizeForChurn() -> Int { + _SwiftChurnSubject_identityTable.count +} diff --git a/Tests/BridgeJSIdentityTests/JavaScript/IdentityModeTests.mjs b/Tests/BridgeJSIdentityTests/JavaScript/IdentityModeTests.mjs new file mode 100644 index 000000000..fe3e1539e --- /dev/null +++ b/Tests/BridgeJSIdentityTests/JavaScript/IdentityModeTests.mjs @@ -0,0 +1,495 @@ +// @ts-check + +import assert from "node:assert"; + +/** + * @returns {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Imports["IdentityModeTestImports"]} + */ +export function getImports(importsContext) { + return { + runJsIdentityModeTests: () => { + const exports = importsContext.getExports(); + if (!exports) { + throw new Error("No exports!?"); + } + runIdentityModeTests(exports); + }, + }; +} + +/** + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function runIdentityModeTests(exports) { + testWrapperIdentity(exports); + testCacheInvalidationOnRelease(exports); + testDifferentClassesDontCollide(exports); + testRetainLeakOnCacheHit(exports); + testArrayElementIdentity(exports); + testArrayElementMatchesSingleGetter(exports); + testArrayRetainLeak(exports); + + // identityMode: .swift tests (coexist with the pointer-mode classes above). + testSwiftModeIdentity(exports); + testSwiftModeSelfMethodIdentity(exports); + testSwiftModeReleaseFreesHeapObject(exports); + testSwiftModeDoubleReleaseIdempotent(exports); + testSwiftModeIdentityTableCleanup(exports); + testSwiftModeArrayCrossElementIdentity(exports); + testSwiftModeGcSurvivability(exports); + testSwiftModeOptionalIdentity(exports); + testSwiftModeReleaseGuardsMembers(exports); + testModeCoexistence(exports); +} + +/** + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testWrapperIdentity(exports) { + exports.resetSharedSubject(); + const a = exports.getSharedSubject(); + const b = exports.getSharedSubject(); + + assert.strictEqual( + a, + b, + "Same Swift object should return identical JS wrapper", + ); + assert.equal(a.currentValue, 42); + + a.release(); + exports.resetSharedSubject(); +} + +/** + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testCacheInvalidationOnRelease(exports) { + exports.resetSharedSubject(); + const first = exports.getSharedSubject(); + first.release(); + + exports.resetSharedSubject(); + const second = exports.getSharedSubject(); + + assert.notStrictEqual( + first, + second, + "After release + reset, should get a different wrapper", + ); + assert.equal(second.currentValue, 42); + + second.release(); + exports.resetSharedSubject(); +} + +/** + * Verifies that repeated boundary crossings of the same Swift object don't leak + * retain counts. Each cache hit triggers passRetained on the Swift side. Without + * the balancing deinit(pointer) call on cache hit, each crossing leaks +1 retain + * and the object is never deallocated. + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testRetainLeakOnCacheHit(exports) { + exports.resetRetainLeakDeinits(); + exports.resetRetainLeakSubject(); + + const wrappers = []; + for (let i = 0; i < 10; i++) { + wrappers.push(exports.getRetainLeakSubject()); + } + + for (let i = 1; i < wrappers.length; i++) { + assert.strictEqual( + wrappers[0], + wrappers[i], + "All should be the same cached wrapper", + ); + } + + wrappers[0].release(); + exports.resetRetainLeakSubject(); + + assert.strictEqual( + exports.getRetainLeakDeinits(), + 1, + "Object should be deallocated after release + reset. " + + "If deinits == 0, retain leak from unbalanced passRetained on cache hits.", + ); +} + +/** + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testArrayElementIdentity(exports) { + exports.setupArrayPool(10); + const arr1 = exports.getArrayPool(); + const arr2 = exports.getArrayPool(); + + assert.equal(arr1.length, 10); + assert.equal(arr2.length, 10); + + for (let i = 0; i < 10; i++) { + assert.strictEqual( + arr1[i], + arr2[i], + `Array element at index ${i} should be === across calls`, + ); + assert.equal(arr1[i].tag, i); + } + + for (const elem of arr1) { + elem.release(); + } + exports.clearArrayPool(); +} + +/** + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testArrayElementMatchesSingleGetter(exports) { + exports.setupArrayPool(5); + const arr = exports.getArrayPool(); + const single = exports.getArrayPoolElement(2); + + assert.strictEqual( + arr[2], + single, + "Array element and single getter should return the same wrapper", + ); + assert.equal(single.tag, 2); + + for (const elem of arr) { + elem.release(); + } + exports.clearArrayPool(); +} + +/** + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testArrayRetainLeak(exports) { + exports.resetArrayPoolDeinits(); + exports.setupArrayPool(5); + + for (let round = 0; round < 10; round++) { + exports.getArrayPool(); + } + + const arr = exports.getArrayPool(); + for (const elem of arr) { + elem.release(); + } + + exports.clearArrayPool(); + + assert.strictEqual( + exports.getArrayPoolDeinits(), + 5, + "All 5 pool objects should be deallocated after release + clear. " + + "If deinits < 5, retain leak from unbalanced passRetained in array returns.", + ); +} + +/** + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testDifferentClassesDontCollide(exports) { + const subject1 = new exports.IdentityTestSubject(1); + const subject2 = new exports.IdentityTestSubject(2); + + assert.notStrictEqual( + subject1, + subject2, + "Different instances should not be ===", + ); + assert.equal(subject1.currentValue, 1); + assert.equal(subject2.currentValue, 2); + + subject1.release(); + subject2.release(); +} + +// ---------- identityMode: .swift tests ---------- + +/** + * Identity on re-export. + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testSwiftModeIdentity(exports) { + exports.resetSharedSwiftSubject(); + const a = exports.getSharedSwiftSubject(); + const b = exports.getSharedSwiftSubject(); + const c = exports.getSharedSwiftSubject(); + + assert.strictEqual(a, b, "swift mode: same Swift object returns same wrapper"); + assert.strictEqual(b, c, "swift mode: identity is transitive across re-exports"); + assert.equal(a.currentValue, 42); + assert.equal(typeof a.pointer, "number"); + + a.release(); + exports.resetSharedSwiftSubject(); +} + +/** + * (a') Method returning `self` preserves identity. + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testSwiftModeSelfMethodIdentity(exports) { + exports.resetSharedSwiftSubject(); + const a = exports.getSharedSwiftSubject(); + const viaSelf = a.self_(); + + assert.strictEqual( + a, + viaSelf, + "swift mode: `self_()` must return the same JS wrapper (identity preserved across method boundary)", + ); + + a.release(); + exports.resetSharedSwiftSubject(); +} + +/** + * Explicit release frees the underlying Swift heap object. + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testSwiftModeReleaseFreesHeapObject(exports) { + exports.resetRetainLeakDeinitsSwift(); + exports.resetRetainLeakSubjectSwift(); + + const obj = exports.getRetainLeakSubjectSwift(); + // Drop the only Swift-side strong reference so the sole remaining anchor + // is Swift's retain on the heap object performed during the fresh-wrap path. + exports.resetRetainLeakSubjectSwift(); + obj.release(); + + assert.strictEqual( + exports.getRetainLeakDeinitsSwift(), + 1, + "swift mode: release must invoke Swift deinit exactly once (check bjs__release_wrapper fires Unmanaged.release)", + ); +} + +/** + * Double-release is idempotent. No crash, no over-release. + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testSwiftModeDoubleReleaseIdempotent(exports) { + exports.resetRetainLeakDeinitsSwift(); + exports.resetRetainLeakSubjectSwift(); + + const obj = exports.getRetainLeakSubjectSwift(); + exports.resetRetainLeakSubjectSwift(); + + obj.release(); + // Second release must be a no-op guarded by __swiftIdentityHasReleased. + obj.release(); + obj.release(); + + assert.strictEqual( + exports.getRetainLeakDeinitsSwift(), + 1, + "swift mode: double-release must not deinit twice", + ); +} + +/** + * Identity-table cleanup — the Swift-side Set returns to size 0 + * after allocating N wrappers and releasing all of them, so it can't grow + * unboundedly over a churn loop. + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testSwiftModeIdentityTableCleanup(exports) { + const POOL = 10; + const first = []; + for (let i = 0; i < POOL; i++) { + first.push(new exports.SwiftChurnSubject(i)); + } + const peakAfterFirst = exports.getSwiftIdentityTableSizeForChurn(); + assert.strictEqual(peakAfterFirst, POOL, `swift mode: identity table should hold ${POOL} entries; got ${peakAfterFirst}`); + + for (const obj of first) { + obj.release(); + } + const afterRelease = exports.getSwiftIdentityTableSizeForChurn(); + assert.strictEqual(afterRelease, 0, `swift mode: identity table should empty after release; got ${afterRelease}`); + + const second = []; + for (let i = 0; i < POOL; i++) { + second.push(new exports.SwiftChurnSubject(100 + i)); + } + const peakAfterSecond = exports.getSwiftIdentityTableSizeForChurn(); + assert.strictEqual(peakAfterSecond, POOL, `swift mode: identity table should hold ${POOL} entries again; got ${peakAfterSecond}`); + + for (const obj of second) { + obj.release(); + } +} + +/** + * Array of the same wrapper preserves cross-element identity. + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testSwiftModeArrayCrossElementIdentity(exports) { + const a = new exports.SwiftIdentityTestSubject(1); + const b = new exports.SwiftIdentityTestSubject(2); + + const result = exports.makeSwiftIdentityArray(a, b); + assert.equal(result.length, 3); + + assert.strictEqual(result[0], a, "swift mode: array element 0 === original a"); + assert.strictEqual(result[1], b, "swift mode: array element 1 === original b"); + assert.strictEqual(result[2], a, "swift mode: array element 2 === original a"); + assert.strictEqual(result[0], result[2], "swift mode: cross-element identity"); + + a.release(); + b.release(); +} + +/** + * GC survivability — wrapper survives forced GC because Swift holds a + * strong JS ref via `swift.memory.retain`. + * + * Requires node to be launched with `--expose-gc`. `make unittest` does so + * (see Makefile:22). Guarded so we don't fail in environments that don't. + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testSwiftModeGcSurvivability(exports) { + if (typeof globalThis.gc !== "function") { + console.warn("Skipping swift-mode GC test — run with --expose-gc"); + return; + } + + exports.resetSharedSwiftSubject(); + let obj = exports.getSharedSwiftSubject(); + const pointerBefore = obj.pointer; + const weakProbe = new WeakRef(obj); + + // Drop the local reference and force GC twice with a microtask flush in + // between (V8 sometimes needs two cycles to collect newly-unreferenced + // objects). + obj = null; + globalThis.gc(); + // synchronous setImmediate-equivalent flush + const flush = () => + new Promise((resolve) => { + if (typeof setImmediate === "function") { + setImmediate(resolve); + } else { + setTimeout(resolve, 0); + } + }); + // Note: we can't await here (caller is synchronous). For a sync harness, + // run gc twice back-to-back — that's sufficient under V8's two-phase + // collector for this test's purposes. + globalThis.gc(); + + // Re-fetch from Swift. The wrapper must still be === to whatever deref + // yields (since Swift's retain on the JS ref keeps it alive). + const again = exports.getSharedSwiftSubject(); + assert.strictEqual( + again.pointer, + pointerBefore, + "swift mode: pointer must be stable across GC (Swift still retains the heap object)", + ); + assert.strictEqual( + weakProbe.deref(), + again, + "swift mode: wrapper survived GC — Swift retain kept it alive", + ); + + again.release(); + exports.resetSharedSwiftSubject(); + + // Suppress "unused" warning for the async helper (kept around in case + // future refactors make this test async). + void flush; +} + +/** + * Optional identity — `.some(x)` returns the cached wrapper; `.none` → null. + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testSwiftModeOptionalIdentity(exports) { + exports.resetSharedSwiftSubject(); + const direct = exports.getSharedSwiftSubject(); + const viaOptional = exports.maybeSwiftSubject(true); + + assert.strictEqual( + direct, + viaOptional, + "swift mode: Optional.some returns the same cached wrapper", + ); + + const absent = exports.maybeSwiftSubject(false); + assert.strictEqual(absent, null, "swift mode: Optional.none returns null"); + + direct.release(); + exports.resetSharedSwiftSubject(); +} + +/** + * Released wrappers guard instance members (throws on use-after-release). + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testSwiftModeReleaseGuardsMembers(exports) { + exports.resetSharedSwiftSubject(); + const obj = exports.getSharedSwiftSubject(); + obj.release(); + + assert.throws( + () => obj.currentValue, + /released|Attempted to call a member/, + "swift mode: use-after-release must throw", + ); + + exports.resetSharedSwiftSubject(); +} + +/** + * Mode coexistence — .swift class and .pointer class in the same build, + * disjoint tables. + * + * Swift-mode and pointer-mode classes coexist without cross-talk. + * + * The target's config default is "pointer", so IdentityTestSubject (no + * per-class annotation) is a pointer-mode class. SwiftIdentityTestSubject is + * explicitly .swift. Both must work simultaneously and their identity + * machinery must not leak between classes. + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testModeCoexistence(exports) { + const ptr1 = new exports.IdentityTestSubject(10); // pointer-mode + const ptr2 = new exports.IdentityTestSubject(20); + const swift1 = new exports.SwiftIdentityTestSubject(30); // swift-mode + const swift2 = new exports.SwiftIdentityTestSubject(40); + + assert.notStrictEqual(ptr1, swift1); + assert.notStrictEqual(ptr2, swift2); + assert.equal(ptr1.currentValue, 10); + assert.equal(swift1.currentValue, 30); + + // Pointer-mode wrappers have `__swiftHeapObjectState` from SwiftHeapObject; + // swift-mode standalone wrappers have `__swiftIdentityHasReleased` instead. + assert.equal(typeof swift1.__swiftIdentityHasReleased, "boolean"); + assert.equal(typeof ptr1.__swiftIdentityHasReleased, "undefined"); + assert.equal(typeof ptr1.__swiftHeapObjectState, "object"); + assert.equal(typeof swift1.__swiftHeapObjectState, "undefined"); + + ptr1.release(); + ptr2.release(); + swift1.release(); + swift2.release(); +} diff --git a/Tests/BridgeJSIdentityTests/bridge-js.config.json b/Tests/BridgeJSIdentityTests/bridge-js.config.json new file mode 100644 index 000000000..29884404e --- /dev/null +++ b/Tests/BridgeJSIdentityTests/bridge-js.config.json @@ -0,0 +1,3 @@ +{ + "identityMode": "pointer" +} diff --git a/Tests/BridgeJSSwiftIdentityTests/Generated/BridgeJS.swift b/Tests/BridgeJSSwiftIdentityTests/Generated/BridgeJS.swift new file mode 100644 index 000000000..d7c62dd76 --- /dev/null +++ b/Tests/BridgeJSSwiftIdentityTests/Generated/BridgeJS.swift @@ -0,0 +1,442 @@ +// bridge-js: skip +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit + +@_expose(wasm, "bjs_getConfigSwiftSubject") +@_cdecl("bjs_getConfigSwiftSubject") +public func _bjs_getConfigSwiftSubject() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = getConfigSwiftSubject() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_resetConfigSwiftSubject") +@_cdecl("bjs_resetConfigSwiftSubject") +public func _bjs_resetConfigSwiftSubject() -> Void { + #if arch(wasm32) + resetConfigSwiftSubject() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getConfigSwiftRetainLeakSubject") +@_cdecl("bjs_getConfigSwiftRetainLeakSubject") +public func _bjs_getConfigSwiftRetainLeakSubject() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = getConfigSwiftRetainLeakSubject() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_resetConfigSwiftRetainLeakSubject") +@_cdecl("bjs_resetConfigSwiftRetainLeakSubject") +public func _bjs_resetConfigSwiftRetainLeakSubject() -> Void { + #if arch(wasm32) + resetConfigSwiftRetainLeakSubject() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getConfigSwiftRetainLeakDeinits") +@_cdecl("bjs_getConfigSwiftRetainLeakDeinits") +public func _bjs_getConfigSwiftRetainLeakDeinits() -> Int32 { + #if arch(wasm32) + let ret = getConfigSwiftRetainLeakDeinits() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_resetConfigSwiftRetainLeakDeinits") +@_cdecl("bjs_resetConfigSwiftRetainLeakDeinits") +public func _bjs_resetConfigSwiftRetainLeakDeinits() -> Void { + #if arch(wasm32) + resetConfigSwiftRetainLeakDeinits() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getConfigSwiftIdentityTableSizeForChurn") +@_cdecl("bjs_getConfigSwiftIdentityTableSizeForChurn") +public func _bjs_getConfigSwiftIdentityTableSizeForChurn() -> Int32 { + #if arch(wasm32) + let ret = getConfigSwiftIdentityTableSizeForChurn() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_makeConfigSwiftArray") +@_cdecl("bjs_makeConfigSwiftArray") +public func _bjs_makeConfigSwiftArray(_ a: UnsafeMutableRawPointer, _ b: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = makeConfigSwiftArray(_: ConfigSwiftSubject.bridgeJSLiftParameter(a), _: ConfigSwiftSubject.bridgeJSLiftParameter(b)) + ret.bridgeJSStackPush() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_maybeConfigSwiftSubject") +@_cdecl("bjs_maybeConfigSwiftSubject") +public func _bjs_maybeConfigSwiftSubject(_ present: Int32) -> Void { + #if arch(wasm32) + let ret = maybeConfigSwiftSubject(_: Bool.bridgeJSLiftParameter(present)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +nonisolated(unsafe) var _ConfigSwiftSubject_identityTable: Set = [] + +@_expose(wasm, "bjs_ConfigSwiftSubject_init") +@_cdecl("bjs_ConfigSwiftSubject_init") +public func _bjs_ConfigSwiftSubject_init(_ value: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ConfigSwiftSubject(value: Int.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigSwiftSubject_self_") +@_cdecl("bjs_ConfigSwiftSubject_self_") +public func _bjs_ConfigSwiftSubject_self_(_ _self: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ConfigSwiftSubject.bridgeJSLiftParameter(_self).self_() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigSwiftSubject_value_get") +@_cdecl("bjs_ConfigSwiftSubject_value_get") +public func _bjs_ConfigSwiftSubject_value_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = ConfigSwiftSubject.bridgeJSLiftParameter(_self).value + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigSwiftSubject_value_set") +@_cdecl("bjs_ConfigSwiftSubject_value_set") +public func _bjs_ConfigSwiftSubject_value_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + ConfigSwiftSubject.bridgeJSLiftParameter(_self).value = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigSwiftSubject_currentValue_get") +@_cdecl("bjs_ConfigSwiftSubject_currentValue_get") +public func _bjs_ConfigSwiftSubject_currentValue_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = ConfigSwiftSubject.bridgeJSLiftParameter(_self).currentValue + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigSwiftSubject_deinit") +@_cdecl("bjs_ConfigSwiftSubject_deinit") +public func _bjs_ConfigSwiftSubject_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigSwiftSubject_release_wrapper") +@_cdecl("bjs_ConfigSwiftSubject_release_wrapper") +public func _bjs_ConfigSwiftSubject_release_wrapper(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + guard _ConfigSwiftSubject_identityTable.remove(pointer) != nil else { return } + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension ConfigSwiftSubject { + @_spi(BridgeJS) @_transparent + public consuming func bridgeJSLowerReturn() -> UnsafeMutableRawPointer { + return withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _ConfigSwiftSubject_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + } + @_spi(BridgeJS) public consuming func bridgeJSStackPush() { + let ptr: UnsafeMutableRawPointer = withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _ConfigSwiftSubject_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + _swift_js_push_pointer(ptr) + } +} + +extension ConfigSwiftSubject: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_ConfigSwiftSubject_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_ConfigSwiftSubject_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSSwiftIdentityTests", name: "bjs_ConfigSwiftSubject_wrap") +fileprivate func _bjs_ConfigSwiftSubject_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_ConfigSwiftSubject_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_ConfigSwiftSubject_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_ConfigSwiftSubject_wrap_extern(pointer) +} + +nonisolated(unsafe) var _ConfigSwiftRetainLeakSubject_identityTable: Set = [] + +@_expose(wasm, "bjs_ConfigSwiftRetainLeakSubject_init") +@_cdecl("bjs_ConfigSwiftRetainLeakSubject_init") +public func _bjs_ConfigSwiftRetainLeakSubject_init(_ tag: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ConfigSwiftRetainLeakSubject(tag: Int.bridgeJSLiftParameter(tag)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigSwiftRetainLeakSubject_tag_get") +@_cdecl("bjs_ConfigSwiftRetainLeakSubject_tag_get") +public func _bjs_ConfigSwiftRetainLeakSubject_tag_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = ConfigSwiftRetainLeakSubject.bridgeJSLiftParameter(_self).tag + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigSwiftRetainLeakSubject_tag_set") +@_cdecl("bjs_ConfigSwiftRetainLeakSubject_tag_set") +public func _bjs_ConfigSwiftRetainLeakSubject_tag_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + ConfigSwiftRetainLeakSubject.bridgeJSLiftParameter(_self).tag = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigSwiftRetainLeakSubject_deinit") +@_cdecl("bjs_ConfigSwiftRetainLeakSubject_deinit") +public func _bjs_ConfigSwiftRetainLeakSubject_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigSwiftRetainLeakSubject_release_wrapper") +@_cdecl("bjs_ConfigSwiftRetainLeakSubject_release_wrapper") +public func _bjs_ConfigSwiftRetainLeakSubject_release_wrapper(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + guard _ConfigSwiftRetainLeakSubject_identityTable.remove(pointer) != nil else { return } + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension ConfigSwiftRetainLeakSubject { + @_spi(BridgeJS) @_transparent + public consuming func bridgeJSLowerReturn() -> UnsafeMutableRawPointer { + return withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _ConfigSwiftRetainLeakSubject_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + } + @_spi(BridgeJS) public consuming func bridgeJSStackPush() { + let ptr: UnsafeMutableRawPointer = withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _ConfigSwiftRetainLeakSubject_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + _swift_js_push_pointer(ptr) + } +} + +extension ConfigSwiftRetainLeakSubject: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_ConfigSwiftRetainLeakSubject_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_ConfigSwiftRetainLeakSubject_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSSwiftIdentityTests", name: "bjs_ConfigSwiftRetainLeakSubject_wrap") +fileprivate func _bjs_ConfigSwiftRetainLeakSubject_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_ConfigSwiftRetainLeakSubject_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_ConfigSwiftRetainLeakSubject_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_ConfigSwiftRetainLeakSubject_wrap_extern(pointer) +} + +nonisolated(unsafe) var _ConfigSwiftChurnSubject_identityTable: Set = [] + +@_expose(wasm, "bjs_ConfigSwiftChurnSubject_init") +@_cdecl("bjs_ConfigSwiftChurnSubject_init") +public func _bjs_ConfigSwiftChurnSubject_init(_ tag: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ConfigSwiftChurnSubject(tag: Int.bridgeJSLiftParameter(tag)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigSwiftChurnSubject_tag_get") +@_cdecl("bjs_ConfigSwiftChurnSubject_tag_get") +public func _bjs_ConfigSwiftChurnSubject_tag_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = ConfigSwiftChurnSubject.bridgeJSLiftParameter(_self).tag + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigSwiftChurnSubject_tag_set") +@_cdecl("bjs_ConfigSwiftChurnSubject_tag_set") +public func _bjs_ConfigSwiftChurnSubject_tag_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + ConfigSwiftChurnSubject.bridgeJSLiftParameter(_self).tag = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigSwiftChurnSubject_deinit") +@_cdecl("bjs_ConfigSwiftChurnSubject_deinit") +public func _bjs_ConfigSwiftChurnSubject_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigSwiftChurnSubject_release_wrapper") +@_cdecl("bjs_ConfigSwiftChurnSubject_release_wrapper") +public func _bjs_ConfigSwiftChurnSubject_release_wrapper(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + guard _ConfigSwiftChurnSubject_identityTable.remove(pointer) != nil else { return } + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension ConfigSwiftChurnSubject { + @_spi(BridgeJS) @_transparent + public consuming func bridgeJSLowerReturn() -> UnsafeMutableRawPointer { + return withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _ConfigSwiftChurnSubject_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + } + @_spi(BridgeJS) public consuming func bridgeJSStackPush() { + let ptr: UnsafeMutableRawPointer = withExtendedLifetime(self) { + let ptr = Unmanaged.passUnretained(self).toOpaque() + if _ConfigSwiftChurnSubject_identityTable.insert(ptr).inserted { + _ = Unmanaged.passRetained(self) + } + return ptr + } + _swift_js_push_pointer(ptr) + } +} + +extension ConfigSwiftChurnSubject: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_ConfigSwiftChurnSubject_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_ConfigSwiftChurnSubject_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSSwiftIdentityTests", name: "bjs_ConfigSwiftChurnSubject_wrap") +fileprivate func _bjs_ConfigSwiftChurnSubject_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_ConfigSwiftChurnSubject_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_ConfigSwiftChurnSubject_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_ConfigSwiftChurnSubject_wrap_extern(pointer) +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSSwiftIdentityTests", name: "bjs_SwiftIdentityModeTestImports_runJsSwiftIdentityModeTests_static") +fileprivate func bjs_SwiftIdentityModeTestImports_runJsSwiftIdentityModeTests_static_extern() -> Void +#else +fileprivate func bjs_SwiftIdentityModeTestImports_runJsSwiftIdentityModeTests_static_extern() -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_SwiftIdentityModeTestImports_runJsSwiftIdentityModeTests_static() -> Void { + return bjs_SwiftIdentityModeTestImports_runJsSwiftIdentityModeTests_static_extern() +} + +func _$SwiftIdentityModeTestImports_runJsSwiftIdentityModeTests() throws(JSException) -> Void { + bjs_SwiftIdentityModeTestImports_runJsSwiftIdentityModeTests_static() + if let error = _swift_js_take_exception() { + throw error + } +} \ No newline at end of file diff --git a/Tests/BridgeJSSwiftIdentityTests/Generated/JavaScript/BridgeJS.json b/Tests/BridgeJSSwiftIdentityTests/Generated/JavaScript/BridgeJS.json new file mode 100644 index 000000000..9ae527aa3 --- /dev/null +++ b/Tests/BridgeJSSwiftIdentityTests/Generated/JavaScript/BridgeJS.json @@ -0,0 +1,415 @@ +{ + "exported" : { + "classes" : [ + { + "constructor" : { + "abiName" : "bjs_ConfigSwiftSubject_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "methods" : [ + { + "abiName" : "bjs_ConfigSwiftSubject_self_", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "self_", + "parameters" : [ + + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "ConfigSwiftSubject" + } + } + } + ], + "name" : "ConfigSwiftSubject", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "currentValue", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "ConfigSwiftSubject" + }, + { + "constructor" : { + "abiName" : "bjs_ConfigSwiftRetainLeakSubject_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "tag", + "name" : "tag", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "ConfigSwiftRetainLeakSubject", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "tag", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "ConfigSwiftRetainLeakSubject" + }, + { + "constructor" : { + "abiName" : "bjs_ConfigSwiftChurnSubject_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "tag", + "name" : "tag", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "ConfigSwiftChurnSubject", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "tag", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "ConfigSwiftChurnSubject" + } + ], + "enums" : [ + + ], + "exposeToGlobal" : false, + "functions" : [ + { + "abiName" : "bjs_getConfigSwiftSubject", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getConfigSwiftSubject", + "parameters" : [ + + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "ConfigSwiftSubject" + } + } + }, + { + "abiName" : "bjs_resetConfigSwiftSubject", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "resetConfigSwiftSubject", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getConfigSwiftRetainLeakSubject", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getConfigSwiftRetainLeakSubject", + "parameters" : [ + + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "ConfigSwiftRetainLeakSubject" + } + } + }, + { + "abiName" : "bjs_resetConfigSwiftRetainLeakSubject", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "resetConfigSwiftRetainLeakSubject", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getConfigSwiftRetainLeakDeinits", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getConfigSwiftRetainLeakDeinits", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "abiName" : "bjs_resetConfigSwiftRetainLeakDeinits", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "resetConfigSwiftRetainLeakDeinits", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getConfigSwiftIdentityTableSizeForChurn", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getConfigSwiftIdentityTableSizeForChurn", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "abiName" : "bjs_makeConfigSwiftArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "makeConfigSwiftArray", + "parameters" : [ + { + "label" : "_", + "name" : "a", + "type" : { + "swiftHeapObject" : { + "_0" : "ConfigSwiftSubject" + } + } + }, + { + "label" : "_", + "name" : "b", + "type" : { + "swiftHeapObject" : { + "_0" : "ConfigSwiftSubject" + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "ConfigSwiftSubject" + } + } + } + } + }, + { + "abiName" : "bjs_maybeConfigSwiftSubject", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "maybeConfigSwiftSubject", + "parameters" : [ + { + "label" : "_", + "name" : "present", + "type" : { + "bool" : { + + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "ConfigSwiftSubject" + } + }, + "_1" : "null" + } + } + } + ], + "identityMode" : "swift", + "protocols" : [ + + ], + "structs" : [ + + ] + }, + "imported" : { + "children" : [ + { + "functions" : [ + + ], + "types" : [ + { + "getters" : [ + + ], + "methods" : [ + + ], + "name" : "SwiftIdentityModeTestImports", + "setters" : [ + + ], + "staticMethods" : [ + { + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "name" : "runJsSwiftIdentityModeTests", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + } + ] + } + ] + } + ] + }, + "moduleName" : "BridgeJSSwiftIdentityTests" +} \ No newline at end of file diff --git a/Tests/BridgeJSSwiftIdentityTests/JavaScript/SwiftIdentityModeTests.mjs b/Tests/BridgeJSSwiftIdentityTests/JavaScript/SwiftIdentityModeTests.mjs new file mode 100644 index 000000000..fd71e525d --- /dev/null +++ b/Tests/BridgeJSSwiftIdentityTests/JavaScript/SwiftIdentityModeTests.mjs @@ -0,0 +1,238 @@ +// @ts-check + +// Tests for identityMode: "swift" inherited from the target's +// bridge-js.config.json (no per-class annotations). + +import assert from "node:assert"; + +/** + * @returns {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Imports["SwiftIdentityModeTestImports"]} + */ +export function getImports(importsContext) { + return { + runJsSwiftIdentityModeTests: () => { + const exports = importsContext.getExports(); + if (!exports) { + throw new Error("No exports!?"); + } + runSwiftIdentityModeTests(exports); + }, + }; +} + +/** + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function runSwiftIdentityModeTests(exports) { + testConfigSwiftIdentity(exports); + testConfigSwiftSelfMethodIdentity(exports); + testConfigSwiftReleaseFreesHeapObject(exports); + testConfigSwiftDoubleReleaseIdempotent(exports); + testConfigSwiftIdentityTableCleanup(exports); + testConfigSwiftArrayCrossElementIdentity(exports); + testConfigSwiftGcSurvivability(exports); + testConfigSwiftOptionalIdentity(exports); + testConfigSwiftReleaseGuardsMembers(exports); +} + +/** + * Identity on re-export (config-default swift mode). + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testConfigSwiftIdentity(exports) { + exports.resetConfigSwiftSubject(); + const a = exports.getConfigSwiftSubject(); + const b = exports.getConfigSwiftSubject(); + const c = exports.getConfigSwiftSubject(); + + assert.strictEqual(a, b, "config-swift: re-export identity"); + assert.strictEqual(b, c, "config-swift: re-export identity transitive"); + assert.equal(a.currentValue, 7); + assert.equal( + typeof a.__swiftIdentityHasReleased, + "boolean", + "config-default `swift` mode should emit a standalone wrapper with __swiftIdentityHasReleased — verify BridgeJSLink.shouldUseSwiftIdentityCache picks up the config", + ); + + a.release(); + exports.resetConfigSwiftSubject(); +} + +/** + * (a') Method returning self preserves identity. + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testConfigSwiftSelfMethodIdentity(exports) { + exports.resetConfigSwiftSubject(); + const a = exports.getConfigSwiftSubject(); + const viaSelf = a.self_(); + assert.strictEqual(a, viaSelf, "config-swift: self_() preserves identity"); + + a.release(); + exports.resetConfigSwiftSubject(); +} + +/** + * Explicit release frees underlying Swift heap object. + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testConfigSwiftReleaseFreesHeapObject(exports) { + exports.resetConfigSwiftRetainLeakDeinits(); + exports.resetConfigSwiftRetainLeakSubject(); + + const obj = exports.getConfigSwiftRetainLeakSubject(); + exports.resetConfigSwiftRetainLeakSubject(); + obj.release(); + + assert.strictEqual( + exports.getConfigSwiftRetainLeakDeinits(), + 1, + "config-swift: release must deinit exactly once", + ); +} + +/** + * Double-release is idempotent. + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testConfigSwiftDoubleReleaseIdempotent(exports) { + exports.resetConfigSwiftRetainLeakDeinits(); + exports.resetConfigSwiftRetainLeakSubject(); + + const obj = exports.getConfigSwiftRetainLeakSubject(); + exports.resetConfigSwiftRetainLeakSubject(); + + obj.release(); + obj.release(); + obj.release(); + + assert.strictEqual( + exports.getConfigSwiftRetainLeakDeinits(), + 1, + "config-swift: double-release must not deinit twice", + ); +} + +/** + * Identity-table cleanup — Set returns to empty after release. + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testConfigSwiftIdentityTableCleanup(exports) { + const POOL = 10; + const first = []; + for (let i = 0; i < POOL; i++) { + first.push(new exports.ConfigSwiftChurnSubject(i)); + } + assert.strictEqual(exports.getConfigSwiftIdentityTableSizeForChurn(), POOL); + + for (const o of first) o.release(); + assert.strictEqual(exports.getConfigSwiftIdentityTableSizeForChurn(), 0); + + const second = []; + for (let i = 0; i < POOL; i++) { + second.push(new exports.ConfigSwiftChurnSubject(100 + i)); + } + assert.strictEqual(exports.getConfigSwiftIdentityTableSizeForChurn(), POOL); + + for (const o of second) o.release(); +} + +/** + * Array returns preserve cross-element identity. + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testConfigSwiftArrayCrossElementIdentity(exports) { + const a = new exports.ConfigSwiftSubject(1); + const b = new exports.ConfigSwiftSubject(2); + + const result = exports.makeConfigSwiftArray(a, b); + assert.equal(result.length, 3); + + assert.strictEqual(result[0], a); + assert.strictEqual(result[1], b); + assert.strictEqual(result[2], a); + assert.strictEqual(result[0], result[2], "config-swift: cross-element identity"); + + a.release(); + b.release(); +} + +/** + * GC survivability. + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testConfigSwiftGcSurvivability(exports) { + if (typeof globalThis.gc !== "function") { + console.warn("Skipping config-swift GC test — run with --expose-gc"); + return; + } + + exports.resetConfigSwiftSubject(); + let obj = exports.getConfigSwiftSubject(); + const pointerBefore = obj.pointer; + const weakProbe = new WeakRef(obj); + + obj = null; + globalThis.gc(); + globalThis.gc(); + + const again = exports.getConfigSwiftSubject(); + assert.strictEqual( + again.pointer, + pointerBefore, + "config-swift: pointer stable across GC", + ); + assert.strictEqual( + weakProbe.deref(), + again, + "config-swift: wrapper survived GC — Swift retain kept it alive", + ); + + again.release(); + exports.resetConfigSwiftSubject(); +} + +/** + * Optional identity — `.some(x)` returns the cached wrapper; `.none` → null. + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testConfigSwiftOptionalIdentity(exports) { + exports.resetConfigSwiftSubject(); + const direct = exports.getConfigSwiftSubject(); + const viaOptional = exports.maybeConfigSwiftSubject(true); + + assert.strictEqual(direct, viaOptional, "config-swift: Optional.some preserves identity"); + + const absent = exports.maybeConfigSwiftSubject(false); + assert.strictEqual(absent, null, "config-swift: Optional.none returns null"); + + direct.release(); + exports.resetConfigSwiftSubject(); +} + +/** + * Released wrappers guard instance members. + * + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports + */ +function testConfigSwiftReleaseGuardsMembers(exports) { + exports.resetConfigSwiftSubject(); + const obj = exports.getConfigSwiftSubject(); + obj.release(); + + assert.throws( + () => obj.currentValue, + /released|Attempted to call a member/, + "config-swift: use-after-release must throw", + ); + + exports.resetConfigSwiftSubject(); +} diff --git a/Tests/BridgeJSSwiftIdentityTests/SwiftIdentityModeTests.swift b/Tests/BridgeJSSwiftIdentityTests/SwiftIdentityModeTests.swift new file mode 100644 index 000000000..9fb1c147a --- /dev/null +++ b/Tests/BridgeJSSwiftIdentityTests/SwiftIdentityModeTests.swift @@ -0,0 +1,105 @@ +import XCTest +import JavaScriptKit + +// Tests the config-default identityMode: "swift" resolution path. +// bridge-js.config.json in this target sets the default, and none of the +// @JS class declarations below opt in explicitly. + +@JSClass struct SwiftIdentityModeTestImports { + @JSFunction static func runJsSwiftIdentityModeTests() throws(JSException) +} + +final class SwiftIdentityModeTests: XCTestCase { + func testRunJsSwiftIdentityModeTests() throws { + try SwiftIdentityModeTestImports.runJsSwiftIdentityModeTests() + } +} + +@JS class ConfigSwiftSubject { + @JS var value: Int + + @JS init(value: Int) { + self.value = value + } + + @JS var currentValue: Int { value } + + @JS func self_() -> ConfigSwiftSubject { self } +} + +nonisolated(unsafe) private var _configSwiftSubject: ConfigSwiftSubject? + +@JS func getConfigSwiftSubject() -> ConfigSwiftSubject { + if _configSwiftSubject == nil { + _configSwiftSubject = ConfigSwiftSubject(value: 7) + } + return _configSwiftSubject! +} + +@JS func resetConfigSwiftSubject() { + _configSwiftSubject = nil +} + +// Dedicated class with a deinit counter for release-related tests. +@JS class ConfigSwiftRetainLeakSubject { + nonisolated(unsafe) static var deinits: Int = 0 + + @JS var tag: Int + + @JS init(tag: Int) { + self.tag = tag + } + + deinit { + Self.deinits += 1 + } +} + +nonisolated(unsafe) private var _configSwiftRetainLeakSubject: ConfigSwiftRetainLeakSubject? + +@JS func getConfigSwiftRetainLeakSubject() -> ConfigSwiftRetainLeakSubject { + if _configSwiftRetainLeakSubject == nil { + _configSwiftRetainLeakSubject = ConfigSwiftRetainLeakSubject(tag: 1) + } + return _configSwiftRetainLeakSubject! +} + +@JS func resetConfigSwiftRetainLeakSubject() { + _configSwiftRetainLeakSubject = nil +} + +@JS func getConfigSwiftRetainLeakDeinits() -> Int { + ConfigSwiftRetainLeakSubject.deinits +} + +@JS func resetConfigSwiftRetainLeakDeinits() { + ConfigSwiftRetainLeakSubject.deinits = 0 +} + +// Dedicated churn class so its identity-table assertions aren't perturbed by +// the shared subjects above. +@JS class ConfigSwiftChurnSubject { + @JS var tag: Int + + @JS init(tag: Int) { + self.tag = tag + } +} + +@JS func getConfigSwiftIdentityTableSizeForChurn() -> Int { + _ConfigSwiftChurnSubject_identityTable.count +} + +@JS func makeConfigSwiftArray( + _ a: ConfigSwiftSubject, + _ b: ConfigSwiftSubject +) -> [ConfigSwiftSubject] { + return [a, b, a] +} + +@JS func maybeConfigSwiftSubject(_ present: Bool) -> ConfigSwiftSubject? { + if _configSwiftSubject == nil { + _configSwiftSubject = ConfigSwiftSubject(value: 99) + } + return present ? _configSwiftSubject : nil +} diff --git a/Tests/BridgeJSSwiftIdentityTests/bridge-js.config.json b/Tests/BridgeJSSwiftIdentityTests/bridge-js.config.json new file mode 100644 index 000000000..310d6b0f5 --- /dev/null +++ b/Tests/BridgeJSSwiftIdentityTests/bridge-js.config.json @@ -0,0 +1,3 @@ +{ + "identityMode": "swift" +} diff --git a/Tests/JavaScriptEventLoopTests/JSClosure+AsyncTests.swift b/Tests/JavaScriptEventLoopTests/JSClosure+AsyncTests.swift index db093e549..164ad6727 100644 --- a/Tests/JavaScriptEventLoopTests/JSClosure+AsyncTests.swift +++ b/Tests/JavaScriptEventLoopTests/JSClosure+AsyncTests.swift @@ -62,49 +62,48 @@ class JSClosureAsyncTests: XCTestCase { // - Make JSObject a final-class // - Unify JSFunction and JSObject into JSValue // - Make JS(Oneshot)Closure as a wrapper of JSObject, not a subclass - /* - func testAsyncOneshotClosure() async throws { - let closure = JSOneshotClosure.async { _ in - return (42.0).jsValue - }.jsValue - let result = try await JSPromise( - from: closure.function!() - )!.value() - XCTAssertEqual(result, 42.0) - } - - func testAsyncOneshotClosureWithPriority() async throws { - let priority = UnsafeSendableBox(nil) - let closure = JSOneshotClosure.async(priority: .high) { _ in - priority.value = Task.currentPriority - return (42.0).jsValue - }.jsValue - let result = try await JSPromise(from: closure.function!())!.value() - XCTAssertEqual(result, 42.0) - XCTAssertEqual(priority.value, .high) - } - - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - func testAsyncOneshotClosureWithTaskExecutor() async throws { - let executor = AnyTaskExecutor() - let closure = JSOneshotClosure.async(executorPreference: executor) { _ in - return (42.0).jsValue - }.jsValue - let result = try await JSPromise(from: closure.function!())!.value() - XCTAssertEqual(result, 42.0) - } - - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - func testAsyncOneshotClosureWithTaskExecutorPreference() async throws { - let executor = AnyTaskExecutor() - let priority = UnsafeSendableBox(nil) - let closure = JSOneshotClosure.async(executorPreference: executor, priority: .high) { _ in - priority.value = Task.currentPriority - return (42.0).jsValue - }.jsValue - let result = try await JSPromise(from: closure.function!())!.value() - XCTAssertEqual(result, 42.0) - XCTAssertEqual(priority.value, .high) - } - */ + // + // func testAsyncOneshotClosure() async throws { + // let closure = JSOneshotClosure.async { _ in + // return (42.0).jsValue + // }.jsValue + // let result = try await JSPromise( + // from: closure.function!() + // )!.value() + // XCTAssertEqual(result, 42.0) + // } + // + // func testAsyncOneshotClosureWithPriority() async throws { + // let priority = UnsafeSendableBox(nil) + // let closure = JSOneshotClosure.async(priority: .high) { _ in + // priority.value = Task.currentPriority + // return (42.0).jsValue + // }.jsValue + // let result = try await JSPromise(from: closure.function!())!.value() + // XCTAssertEqual(result, 42.0) + // XCTAssertEqual(priority.value, .high) + // } + // + // @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + // func testAsyncOneshotClosureWithTaskExecutor() async throws { + // let executor = AnyTaskExecutor() + // let closure = JSOneshotClosure.async(executorPreference: executor) { _ in + // return (42.0).jsValue + // }.jsValue + // let result = try await JSPromise(from: closure.function!())!.value() + // XCTAssertEqual(result, 42.0) + // } + // + // @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + // func testAsyncOneshotClosureWithTaskExecutorPreference() async throws { + // let executor = AnyTaskExecutor() + // let priority = UnsafeSendableBox(nil) + // let closure = JSOneshotClosure.async(executorPreference: executor, priority: .high) { _ in + // priority.value = Task.currentPriority + // return (42.0).jsValue + // }.jsValue + // let result = try await JSPromise(from: closure.function!())!.value() + // XCTAssertEqual(result, 42.0) + // XCTAssertEqual(priority.value, .high) + // } } diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 0af033226..714e94638 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -14,12 +14,15 @@ import { getImports as getDefaultArgumentImports } from './BridgeJSRuntimeTests/ import { getImports as getJSClassSupportImports, JSClassWithArrayMembers } from './BridgeJSRuntimeTests/JavaScript/JSClassSupportTests.mjs'; import { getImports as getIntegerTypesSupportImports } from './BridgeJSRuntimeTests/JavaScript/IntegerTypesSupportTests.mjs'; import { getImports as getAsyncImportImports, runAsyncWorksTests } from './BridgeJSRuntimeTests/JavaScript/AsyncImportTests.mjs'; +import { getImports as getIdentityModeTestImports } from './BridgeJSIdentityTests/JavaScript/IdentityModeTests.mjs'; +import { getImports as getSwiftIdentityModeTestImports } from './BridgeJSSwiftIdentityTests/JavaScript/SwiftIdentityModeTests.mjs'; /** @type {import('../.build/plugins/PackageToJS/outputs/PackageTests/test.d.ts').SetupOptionsFn} */ export async function setupOptions(options, context) { Error.stackTraceLimit = 100; setupTestGlobals(globalThis); + class StaticBox { constructor(value) { this._value = value; @@ -155,6 +158,8 @@ export async function setupOptions(options, context) { DefaultArgumentImports: getDefaultArgumentImports(importsContext), JSClassSupportImports: getJSClassSupportImports(importsContext), IntegerTypesSupportImports: getIntegerTypesSupportImports(importsContext), + IdentityModeTestImports: getIdentityModeTestImports(importsContext), + SwiftIdentityModeTestImports: getSwiftIdentityModeTestImports(importsContext), }; }, addToCoreImports(importObject, importsContext) { diff --git a/Utilities/bridge-js-generate.sh b/Utilities/bridge-js-generate.sh index 22182d24b..6dae0e472 100755 --- a/Utilities/bridge-js-generate.sh +++ b/Utilities/bridge-js-generate.sh @@ -6,5 +6,7 @@ swift build --package-path ./Plugins/BridgeJS --product BridgeJSTool ./Plugins/BridgeJS/.build/debug/BridgeJSTool generate --project ./tsconfig.json --module-name BridgeJSRuntimeTests --target-dir ./Tests/BridgeJSRuntimeTests --output-dir ./Tests/BridgeJSRuntimeTests/Generated ./Plugins/BridgeJS/.build/debug/BridgeJSTool generate --project ./tsconfig.json --module-name BridgeJSGlobalTests --target-dir ./Tests/BridgeJSGlobalTests --output-dir ./Tests/BridgeJSGlobalTests/Generated +./Plugins/BridgeJS/.build/debug/BridgeJSTool generate --project ./tsconfig.json --module-name BridgeJSIdentityTests --target-dir ./Tests/BridgeJSIdentityTests --output-dir ./Tests/BridgeJSIdentityTests/Generated +./Plugins/BridgeJS/.build/debug/BridgeJSTool generate --project ./tsconfig.json --module-name BridgeJSSwiftIdentityTests --target-dir ./Tests/BridgeJSSwiftIdentityTests --output-dir ./Tests/BridgeJSSwiftIdentityTests/Generated ./Plugins/BridgeJS/.build/debug/BridgeJSTool generate --project ./tsconfig.json --module-name Benchmarks --target-dir ./Benchmarks/Sources --output-dir ./Benchmarks/Sources/Generated ./Plugins/BridgeJS/.build/debug/BridgeJSTool generate --project ./tsconfig.json --module-name PlayBridgeJS --target-dir ./Examples/PlayBridgeJS/Sources/PlayBridgeJS --output-dir ./Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated