Skip to content

Commit f562fcb

Browse files
authored
Merge pull request #2 from PassiveLogic/feat/pointer-mode-v2
feat: Add opt-in identityMode pointer for SwiftHeapObject wrapper identity caching
2 parents ff90c3e + 2aac6d6 commit f562fcb

114 files changed

Lines changed: 3040 additions & 262 deletions

File tree

Some content is hidden

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

Benchmarks/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,40 @@ node run.js --filter=Call
3535
node run.js --filter=/^Property access\//
3636
node run.js --filter=/string/i
3737
```
38+
39+
## Identity Mode Benchmarks
40+
41+
Compare `identityMode: "pointer"` vs default (`"none"`) for SwiftHeapObject wrapper caching. Requires `--expose-gc` for memory benchmarks.
42+
43+
```bash
44+
# Run identity benchmarks comparing both modes
45+
node --expose-gc run.js --identity-mode=both
46+
47+
# Pointer mode only
48+
node --expose-gc run.js --identity-mode=pointer
49+
50+
# Control iteration count (default: 1000000)
51+
node --expose-gc run.js --identity-mode=both --identity-iterations=500000
52+
53+
# Control pool sizes for reuse scenarios (default: 1)
54+
node --expose-gc run.js --identity-mode=both --identity-reuse-pools=1,16
55+
56+
# Include memory profiling (heap snapshots before/during/after)
57+
node --expose-gc run.js --identity-mode=both --identity-memory
58+
59+
# Combine with adaptive sampling
60+
node --expose-gc run.js --identity-mode=both --adaptive
61+
62+
# Save identity results
63+
node --expose-gc run.js --identity-mode=both --output=results/identity-mode/results.json
64+
```
65+
66+
### Identity Mode Scenarios
67+
68+
| Scenario | What it measures |
69+
|----------|-----------------|
70+
| `passBothWaysRoundtrip` | Same object crossing boundary repeatedly (cache hit path) |
71+
| `getPoolRepeated_100` | Bulk return of 100 cached objects (model collection pattern) |
72+
| `churnObjects` | Create, roundtrip, release cycle (FinalizationRegistry cleanup pressure) |
73+
| `swiftConsumesSameObject` | JS passes same object to Swift repeatedly |
74+
| `swiftCreatesObject` | Fresh object creation overhead (cache miss path) |

Benchmarks/Sources/Benchmarks.swift

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,54 @@ enum ComplexResult {
257257
}
258258
}
259259

260+
// MARK: - Class Array Performance Tests
261+
262+
nonisolated(unsafe) var _classArrayPool: [SimpleClass] = []
263+
264+
@JS class ClassArrayRoundtrip {
265+
@JS init() {}
266+
267+
@JS func setupPool(_ count: Int) {
268+
_classArrayPool = (0..<count).map {
269+
SimpleClass(name: "Item \($0)", count: $0, flag: true, rate: 0.5, precise: 3.14)
270+
}
271+
}
272+
273+
@JS func getPool() -> [SimpleClass] {
274+
return _classArrayPool
275+
}
276+
277+
@JS func makeClassArray() -> [SimpleClass] {
278+
return (0..<100).map {
279+
SimpleClass(name: "Item \($0)", count: $0, flag: true, rate: 0.5, precise: 3.14)
280+
}
281+
}
282+
283+
@JS func takeClassArray(_ values: [SimpleClass]) {}
284+
285+
@JS func roundtripClassArray(_ values: [SimpleClass]) -> [SimpleClass] {
286+
return values
287+
}
288+
}
289+
290+
// MARK: - Identity Cache Benchmark
291+
292+
nonisolated(unsafe) var _cachedPool: [SimpleClass] = []
293+
294+
@JS class IdentityCacheBenchmark {
295+
@JS init() {}
296+
297+
@JS func setupPool(_ count: Int) {
298+
_cachedPool = (0..<count).map {
299+
SimpleClass(name: "Item \($0)", count: $0, flag: true, rate: 0.5, precise: 3.14)
300+
}
301+
}
302+
303+
@JS func getPoolRepeated() -> [SimpleClass] {
304+
return _cachedPool
305+
}
306+
}
307+
260308
// MARK: - Array Performance Tests
261309

262310
@JS struct Point {

Benchmarks/Sources/Generated/BridgeJS.swift

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,6 +1373,164 @@ fileprivate func _bjs_ClassRoundtrip_wrap_extern(_ pointer: UnsafeMutableRawPoin
13731373
return _bjs_ClassRoundtrip_wrap_extern(pointer)
13741374
}
13751375

1376+
@_expose(wasm, "bjs_ClassArrayRoundtrip_init")
1377+
@_cdecl("bjs_ClassArrayRoundtrip_init")
1378+
public func _bjs_ClassArrayRoundtrip_init() -> UnsafeMutableRawPointer {
1379+
#if arch(wasm32)
1380+
let ret = ClassArrayRoundtrip()
1381+
return ret.bridgeJSLowerReturn()
1382+
#else
1383+
fatalError("Only available on WebAssembly")
1384+
#endif
1385+
}
1386+
1387+
@_expose(wasm, "bjs_ClassArrayRoundtrip_setupPool")
1388+
@_cdecl("bjs_ClassArrayRoundtrip_setupPool")
1389+
public func _bjs_ClassArrayRoundtrip_setupPool(_ _self: UnsafeMutableRawPointer, _ count: Int32) -> Void {
1390+
#if arch(wasm32)
1391+
ClassArrayRoundtrip.bridgeJSLiftParameter(_self).setupPool(_: Int.bridgeJSLiftParameter(count))
1392+
#else
1393+
fatalError("Only available on WebAssembly")
1394+
#endif
1395+
}
1396+
1397+
@_expose(wasm, "bjs_ClassArrayRoundtrip_getPool")
1398+
@_cdecl("bjs_ClassArrayRoundtrip_getPool")
1399+
public func _bjs_ClassArrayRoundtrip_getPool(_ _self: UnsafeMutableRawPointer) -> Void {
1400+
#if arch(wasm32)
1401+
let ret = ClassArrayRoundtrip.bridgeJSLiftParameter(_self).getPool()
1402+
ret.bridgeJSStackPush()
1403+
#else
1404+
fatalError("Only available on WebAssembly")
1405+
#endif
1406+
}
1407+
1408+
@_expose(wasm, "bjs_ClassArrayRoundtrip_makeClassArray")
1409+
@_cdecl("bjs_ClassArrayRoundtrip_makeClassArray")
1410+
public func _bjs_ClassArrayRoundtrip_makeClassArray(_ _self: UnsafeMutableRawPointer) -> Void {
1411+
#if arch(wasm32)
1412+
let ret = ClassArrayRoundtrip.bridgeJSLiftParameter(_self).makeClassArray()
1413+
ret.bridgeJSStackPush()
1414+
#else
1415+
fatalError("Only available on WebAssembly")
1416+
#endif
1417+
}
1418+
1419+
@_expose(wasm, "bjs_ClassArrayRoundtrip_takeClassArray")
1420+
@_cdecl("bjs_ClassArrayRoundtrip_takeClassArray")
1421+
public func _bjs_ClassArrayRoundtrip_takeClassArray(_ _self: UnsafeMutableRawPointer) -> Void {
1422+
#if arch(wasm32)
1423+
ClassArrayRoundtrip.bridgeJSLiftParameter(_self).takeClassArray(_: [SimpleClass].bridgeJSStackPop())
1424+
#else
1425+
fatalError("Only available on WebAssembly")
1426+
#endif
1427+
}
1428+
1429+
@_expose(wasm, "bjs_ClassArrayRoundtrip_roundtripClassArray")
1430+
@_cdecl("bjs_ClassArrayRoundtrip_roundtripClassArray")
1431+
public func _bjs_ClassArrayRoundtrip_roundtripClassArray(_ _self: UnsafeMutableRawPointer) -> Void {
1432+
#if arch(wasm32)
1433+
let ret = ClassArrayRoundtrip.bridgeJSLiftParameter(_self).roundtripClassArray(_: [SimpleClass].bridgeJSStackPop())
1434+
ret.bridgeJSStackPush()
1435+
#else
1436+
fatalError("Only available on WebAssembly")
1437+
#endif
1438+
}
1439+
1440+
@_expose(wasm, "bjs_ClassArrayRoundtrip_deinit")
1441+
@_cdecl("bjs_ClassArrayRoundtrip_deinit")
1442+
public func _bjs_ClassArrayRoundtrip_deinit(_ pointer: UnsafeMutableRawPointer) -> Void {
1443+
#if arch(wasm32)
1444+
Unmanaged<ClassArrayRoundtrip>.fromOpaque(pointer).release()
1445+
#else
1446+
fatalError("Only available on WebAssembly")
1447+
#endif
1448+
}
1449+
1450+
extension ClassArrayRoundtrip: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable {
1451+
var jsValue: JSValue {
1452+
return .object(JSObject(id: UInt32(bitPattern: _bjs_ClassArrayRoundtrip_wrap(Unmanaged.passRetained(self).toOpaque()))))
1453+
}
1454+
consuming func bridgeJSLowerAsProtocolReturn() -> Int32 {
1455+
_bjs_ClassArrayRoundtrip_wrap(Unmanaged.passRetained(self).toOpaque())
1456+
}
1457+
}
1458+
1459+
#if arch(wasm32)
1460+
@_extern(wasm, module: "Benchmarks", name: "bjs_ClassArrayRoundtrip_wrap")
1461+
fileprivate func _bjs_ClassArrayRoundtrip_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32
1462+
#else
1463+
fileprivate func _bjs_ClassArrayRoundtrip_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 {
1464+
fatalError("Only available on WebAssembly")
1465+
}
1466+
#endif
1467+
@inline(never) fileprivate func _bjs_ClassArrayRoundtrip_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 {
1468+
return _bjs_ClassArrayRoundtrip_wrap_extern(pointer)
1469+
}
1470+
1471+
@_expose(wasm, "bjs_IdentityCacheBenchmark_init")
1472+
@_cdecl("bjs_IdentityCacheBenchmark_init")
1473+
public func _bjs_IdentityCacheBenchmark_init() -> UnsafeMutableRawPointer {
1474+
#if arch(wasm32)
1475+
let ret = IdentityCacheBenchmark()
1476+
return ret.bridgeJSLowerReturn()
1477+
#else
1478+
fatalError("Only available on WebAssembly")
1479+
#endif
1480+
}
1481+
1482+
@_expose(wasm, "bjs_IdentityCacheBenchmark_setupPool")
1483+
@_cdecl("bjs_IdentityCacheBenchmark_setupPool")
1484+
public func _bjs_IdentityCacheBenchmark_setupPool(_ _self: UnsafeMutableRawPointer, _ count: Int32) -> Void {
1485+
#if arch(wasm32)
1486+
IdentityCacheBenchmark.bridgeJSLiftParameter(_self).setupPool(_: Int.bridgeJSLiftParameter(count))
1487+
#else
1488+
fatalError("Only available on WebAssembly")
1489+
#endif
1490+
}
1491+
1492+
@_expose(wasm, "bjs_IdentityCacheBenchmark_getPoolRepeated")
1493+
@_cdecl("bjs_IdentityCacheBenchmark_getPoolRepeated")
1494+
public func _bjs_IdentityCacheBenchmark_getPoolRepeated(_ _self: UnsafeMutableRawPointer) -> Void {
1495+
#if arch(wasm32)
1496+
let ret = IdentityCacheBenchmark.bridgeJSLiftParameter(_self).getPoolRepeated()
1497+
ret.bridgeJSStackPush()
1498+
#else
1499+
fatalError("Only available on WebAssembly")
1500+
#endif
1501+
}
1502+
1503+
@_expose(wasm, "bjs_IdentityCacheBenchmark_deinit")
1504+
@_cdecl("bjs_IdentityCacheBenchmark_deinit")
1505+
public func _bjs_IdentityCacheBenchmark_deinit(_ pointer: UnsafeMutableRawPointer) -> Void {
1506+
#if arch(wasm32)
1507+
Unmanaged<IdentityCacheBenchmark>.fromOpaque(pointer).release()
1508+
#else
1509+
fatalError("Only available on WebAssembly")
1510+
#endif
1511+
}
1512+
1513+
extension IdentityCacheBenchmark: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable {
1514+
var jsValue: JSValue {
1515+
return .object(JSObject(id: UInt32(bitPattern: _bjs_IdentityCacheBenchmark_wrap(Unmanaged.passRetained(self).toOpaque()))))
1516+
}
1517+
consuming func bridgeJSLowerAsProtocolReturn() -> Int32 {
1518+
_bjs_IdentityCacheBenchmark_wrap(Unmanaged.passRetained(self).toOpaque())
1519+
}
1520+
}
1521+
1522+
#if arch(wasm32)
1523+
@_extern(wasm, module: "Benchmarks", name: "bjs_IdentityCacheBenchmark_wrap")
1524+
fileprivate func _bjs_IdentityCacheBenchmark_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32
1525+
#else
1526+
fileprivate func _bjs_IdentityCacheBenchmark_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 {
1527+
fatalError("Only available on WebAssembly")
1528+
}
1529+
#endif
1530+
@inline(never) fileprivate func _bjs_IdentityCacheBenchmark_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 {
1531+
return _bjs_IdentityCacheBenchmark_wrap_extern(pointer)
1532+
}
1533+
13761534
@_expose(wasm, "bjs_ArrayRoundtrip_init")
13771535
@_cdecl("bjs_ArrayRoundtrip_init")
13781536
public func _bjs_ArrayRoundtrip_init() -> UnsafeMutableRawPointer {

0 commit comments

Comments
 (0)