mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
After the removal of the per-store block search, the remaining per-store cost was the C call chain itself: iopMemWrite* region dispatch + WLUT load, the indirect psxCpu->Clear call, and the recClearIOP/psxRecClearMem frames -- ~2.9% of battle EE-thread samples in pstef's profile (iopMemWrite32 1.13%, iopMemWrite16 0.28%, recClearIOP 1.44%). Emit three per-width stubs (_DynGen_StoreStub) alongside the dispatchers instead: gate, IsC check, direct store into Main[addr & (ExposedIopRam-1)], and an inline g_iopCodeCov granule probe that only calls into the clear machinery (iopStoreClearHit -> recClearIOP) when a live block overlaps the store. Store sites emit a single BL, exactly the size of the old C call. Hw/unmapped targets tail-jump from the stub to iopMemWrite*, which returns straight to the store site; compile-time-known hw addresses keep calling C directly. The condition is (addr & 0x1f800000) == 0, not a bit-28 test: iopMemWrite* masks addresses to 0x1fffffff, and within that space pages 0x00-0x7f are the only WLUT-mapped write target reachable this way (plain RAM; the parallel port at 0x1f00 is the other mapped page and the mask excludes it). Everything else -- 0x1f80/0x1f40 hw, SIF, DEV9, SPU2, ROM, and the unmapped 0x00800000-0x0fffffff range that bit 28 alone misclassifies as RAM -- must take the C path. Bits 23-28 survive the phys mask, so the test is exact for every KUSEG/KSEG0/KSEG1 mirror, and matches iopMemReset's `for (i < 0x0080)` mapping loop. The RAM mask equals the recLUT_SetPage mirror collapse, so the masked offset doubles as the coverage probe index. IsC (Status bit 16) swallows the store with no clear, matching the C RAM branch's p != NULL && !IsC guard. Stubs are re-emitted on every recResetIOP, so the baked mask and RAM base track extra-memory-mode flips (which discard all blocks). RPSXSTATE is x21, callee-saved, so it survives both the BL and the slow-path tail jump. Outlining is deliberate: pstef measured a fully inline fast path (~15 insns/site) first, and its per-site code growth cost +10% L1I misses/frame, eating the win (3D net zero, FMV only -1.08% cycles). The shared stubs keep sites baseline-sized and also displace the C functions' own icache footprint. Documented at the stub, not fixed here: the inline probe is mirror-collapsed (addr & (ExposedIopRam-1)) while iopCovAdjust and psxRecClearMem key the same array by HWADDR, which strips the KSEG base but not the RAM mirrors. Under a 2MB configuration a block executed from a mirror page registers coverage at a different granule than a store to its physical alias probes. That is exactly the pre-existing blindness of keying recBlocks by HWADDR -- not a regression -- but the two domains are a trap for anyone making either side finer-grained. Six new IopSmc cases: KSEG1-mirror, const-address, SB, and SH stores over a pre-compiled victim block (compiled first, then stored into via RunResume, so a stale-cache leak is actually observable), unmapped-low-region drop, and IsC-swallow. Verified not inert: a bit-28-only gate turns JitStoreToUnmappedLowRegionIsDropped red, and defeating the IsC check turns six cases red including JitStoreWithCacheIsolatedIsSwallowed. Full suite 1432/1432. Taken from https://github.com/yaps2/yaps2/pull/11 by pstef (https://github.com/pstef). Co-authored-by: pstef <3462925+pstef@users.noreply.github.com>