experiment: Swift-side per-class identity cache with shared memory flag#5
Draft
experiment: Swift-side per-class identity cache with shared memory flag#5
Conversation
Replace DataView shared memory flag with @_extern(wasm) JS import for signaling, following the existing BridgeJS intrinsics pattern (_swift_js_push_i32, _swift_js_return_optional_heap_object, etc). Per-class Set tracks exported pointers. On cache hit, thunk calls _swift_js_set_identity_ref(1) and returns passUnretained. JS checks the ref and skips deinit. Trade-off vs JS-only cache: Set.contains with SipHash on WASM adds ~20-30ns per crossing, exceeding the ~4-8ns deinit WASM call it replaces. Improves create-heavy paths by ~20% but regresses roundtrip by ~50%.
444417f to
d632591
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Experimental Swift-side identity cache as an alternative to the JS-side
deinit(pointer)call on cache hits. Builds on top of PRs #2 and #3.Currently, when the same Swift pointer is returned and the JS identity cache hits, JS calls
deinit(pointer)back into WASM to balance thepassRetainedthat Swift did. This PR moves the "already exported?" tracking to Swift, allowing the thunk to skippassRetainedentirely on cache hits.How it works
Each identity-mode class gets a generated
Set<UnsafeMutableRawPointer>tracking exported pointers. The generated thunk checks the Set before returning:Swift signals JS via
@_extern(wasm, module: "bjs", name: "swift_js_set_identity_ref")— the same pattern as_swift_js_push_i32and other existing BridgeJS intrinsics. No DataView, no shared memory buffer management.JS checks the signal before calling
__construct. On cache hit with signal set, JS skipsdeinit. On race condition (stale JS cache), JS callsbjs_identity_retainto recover.The per-class Set and signal are implementation details in the generated code — no changes to user-facing
@JSAPI.What changed
BridgeJSIntrinsics.swift— Added_swift_js_set_identity_refJS import (same pattern as_swift_js_push_i32). Addedbjs_identity_retainWASM export for race recovery. Removed DataView-based shared memory flag.ExportSwift.swift— Identity-mode thunks usewithExtendedLifetime+passUnretained+ Set check. Per-classSet<UnsafeMutableRawPointer>generated alongside thunks. Deinit cleans up Set entry.BridgeJSLink.swift— JS import handler forswift_js_set_identity_ref. Modified__wrapcache hit/miss paths to check signal. Removed DataView infrastructure.Benchmark comparison
Release build, adaptive sampling:
Analysis
The Swift-side approach trades cache-hit speed for create-path improvement.
Set.containswith SipHash on WASM costs ~20-30ns per call (no hardware-accelerated hashing), making the cache-hit path slower than a singledeinitWASM call (~4-8ns after V8 JIT optimization).The
churnObjectsregression is severe — objects are created, crossed, and released in a tight loop. The per-class Set grows because FinalizationRegistry cleanup is asynchronous. This is a known limitation of the per-class Set approach.When this approach wins
When JS-only cache wins