3.3 KiB
Review: M2 round 2 (pipe 4d207468 959ebc9e 6e18acaa 5ba34fcf + integrator f62703e9 1ab62ece) — integrator (Fable 5.1, main session)
Verdict: land (no change required before push; one cost recorded in CONTRACT-P7 §12 as M3's denominator).
The must-fix from round 1 (memo handle-reuse ABA) is closed the way the review asked: VkBufferManager::m_wireStoreDestroyEpoch is bumped on every path that destroys a wire store's VkBuffer and never reset; UniformManager folds it into the descriptor-reuse signature (mix64(epoch)) and records/compares it in FastRebindMemo, all under MOBILEGL_BUILD_DISAGGREGATED.
Verified on the pipe source, not the note:
- Every destroy site is covered.
DeferWireRelease's immediate destroy (buffer.Destroy(); ++epoch),SweepDeferredWireReleases(returns atdead == 0BEFORE the bump, so a sweep that freed nothing does not defeat the memo on the next draw),DestroyAllDeferredWireReleases(bump iff non-empty),Shutdown'sm_wireBuffers.clear()(bump iff non-empty).DestroyWireBuffermoves the store intoDeferWireReleaseand only then erases the record, so the erase destroys nothing. The seven remainingbuffer.Destroy()calls in the file are the non-wire records, which still die at the frame boundary that clears the memos. - The epoch is the destroy count's parity, not the slice epoch:
m_sliceEpochCountermoves on everyWriteWireBufferand would miss on everyglBufferSubData; the destroy epoch moves ~26k times over bsl's 1.3M calls. Cost = one extra descriptor-set acquire+write on the first draw after each destroy event, which also feeds §6.1's in-frame descriptor-set growth (M3). FastRebindMemoaggregate initialiser order matches the struct (epoch last, guarded on both sides); the wire arm never takes the fast rebind (SetupWireDrawpasses no sampler hint), so the signature half is the one the scenario reds through; the memo half is defensive and costs nothing.- The red-once is deterministic by construction (no sleep, no fence-poll race): orphan → flush-without-wait → a staged copy keeps the recording pending so the blocking wait cannot take the memo-clearing drain → the next park's sweep destroys S1 → a 64 KiB mint through the freed handle cannot alias the 256-byte hole a 1 MiB pin bounds → the draw reads the dead store's colour without the epoch. Both arms red at base, green with the guards; the note's static-link finding (the Split arm runs the backend compiled into the test binary) is worth keeping for every future red-once.
- Integrator additions:
RespecifyWireBufferclears B3'slastUseSubmitIndexbesidelastUseSerial(pipe-only field; the create path already zeroes it at :303, so 0 = never submitted); the parity exception list on pipe is keyed by exact tail (two merges ago) so the fourth Reclaim case is named there (the package branch excepts by prefix).ServerSpawnkeepsSPIN_US/SERVER_AFFINITY/AUDITfor the child (tuning / audit values, no endpoint, role, path or size; ServerMain's catch (b) still verifies its four).
Gate on f62703e9: unit 2436, split 304, spawn 220, tcp 223, magma 101 / 80 / 71, full-split 539, G1 .text 0xa52203 nm +0/-0, census 79, ratchet 173, wire-declines 53/53/0 + 16/16, OpenRA DirectVulkan inproc + spawn ssim 1.000000 / 0 unsound lines, verify lanes 1150 / 1084; parity 0 errors after 1ab62ece.