tests: reset GIF PATH1 per replay — escaped wrap-head bytes filled the ring

The vurunner/VuReplay PATH1 sink covers Gif_Unit::TransferGSPacketData, but
microVU's XGKICK wrap path sends the pre-wrap head through
Gif_Path::CopyGSPacketData directly (the same harness blind spot pstef found
landing the console XGKICK cases). Those bytes land in the REAL gifPath[1]
ring, which nothing drains in a runner with no GS thread: across a few
hundred wrapped-kick captures in one process the ring fills,
CopyGSPacketData calls mtgsReadWait, and MTGS::WaitGS trips its devel
closed-thread assert — aborting corpus sweeps mid-batch (release would
early-return instead and lose the wait). Backtrace: mVU_XGKICK_ →
CopyGSPacketData → mtgsReadWait → WaitGS, cap ~360 of a 400-cap batch.

Reset gifPath[1] at each replay entry so escaped bytes can never accumulate
across captures. Still correct once the sink covers both entry points —
then it's just belt-and-suspenders. The 400-cap batch that aborted now
completes; suite stays green.
This commit is contained in:
Brian Degenhardt
2026-07-29 20:28:41 -07:00
parent efeab3d35b
commit 355a1a8739
@@ -129,6 +129,21 @@ void RunJitFromSeeded(int idx, u32 cycles)
JitCpu(idx)->Execute(cycles);
}
// The PATH1 sink covers Gif_Unit::TransferGSPacketData, but microVU's XGKICK
// wrap path sends the pre-wrap head through Gif_Path::CopyGSPacketData
// directly (the harness blind spot pstef found landing the console XGKICK
// cases) — those bytes land in the REAL gifPath[1] ring, which nothing drains
// in a runner with no GS thread. Across a few hundred wrapped-kick captures
// in one process the ring fills, CopyGSPacketData calls mtgsReadWait, and
// MTGS::WaitGS trips its devel closed-thread assert (abort mid-sweep; release
// would early-return and lose the wait entirely). Reset the path per replay
// so escaped bytes can never accumulate across captures — still correct once
// the sink covers both entry points, just belt-and-suspenders.
static void ResetGifPath1()
{
gifUnit.gifPath[GIF_PATH_1].Reset();
}
} // namespace
void ReseedFromCapture(const vu_capture::CaptureRecord& rec)
@@ -159,6 +174,7 @@ std::vector<PmuCounters::Values> BenchJitSteady(const vu_capture::CaptureRecord&
std::vector<u8> path1_buf;
gif_test_hooks::g_path1_sink = &path1_buf;
ResetGifPath1();
// Prime once WITH a cache reset so iter 0 compiles the block; subsequent
// iters re-seed without a reset (block stays compiled) so the measured
@@ -213,6 +229,7 @@ VuReplayResult ReplayCapture(const vu_capture::CaptureRecord& rec,
std::vector<u8> path1_buf;
gif_test_hooks::g_path1_sink = &path1_buf;
ResetGifPath1();
PrimeFromCapture(rec);
const VuSnapshot pre = VuSnapshot::Capture(idx, windows);
path1_buf.clear();