From ba5e4ebd66fa3efb051e549aa84ee5f10b89892a Mon Sep 17 00:00:00 2001 From: jpolo1224 Date: Tue, 11 Aug 2026 00:01:45 -0400 Subject: [PATCH] SPU: stop an out-buffer verdict from spinning GETLLAR forever The out-buffer check answers 'unlikely to be a loop', and that answer is not free. It resets the spin count and leaves the busy-waiting switch at umax, so the caller skips busy_wait, skips the sleep path, and returns immediately: the SPU re-executes GETLLAR at full rate with no backoff. The spin count never reaches 4, so the spin optimisation is never evaluated, and the 400ms fallback that would force a sleep is never reached either. One SPU in that state holds a core flat out, and the setting meant to control this has nothing to act on. Spider-Man: Web of Shadows sits in that case. Its GETLLAR sites use an LSA in the top 64K of local store, which is what the check looks for, and process_mfc_cmd measured 55% of all CPU across the process while the game ran at 10-15fps. Re-entering the same site with the same stack 32 times is itself the evidence that it is a loop, whatever the LSA looks like. After that the verdict is dropped and the normal spin detection decides between busy-waiting and sleeping. Any real change of site or stack resets the count, so a genuine OUT buffer still gets the original treatment. --- rpcs3/Emu/Cell/SPUThread.cpp | 25 ++++++++++++++++++++++++- rpcs3/Emu/Cell/SPUThread.h | 5 +++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index 05f53a8e9..e687fffd0 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -4330,6 +4330,7 @@ bool spu_thread::process_mfc_cmd() // Seemingly not getllar_busy_waiting_switch = umax; getllar_spin_count = 0; + getllar_outbuf_hits = 0; return true; } @@ -4337,6 +4338,7 @@ bool spu_thread::process_mfc_cmd() { getllar_busy_waiting_switch = umax; getllar_spin_count = 0; + getllar_outbuf_hits = 0; return true; } @@ -4375,8 +4377,29 @@ bool spu_thread::process_mfc_cmd() getllar_cs_first = cs.empty() ? umax : cs[0].second; } - if (getllar_cs_first != umax && last_getllar_lsa > getllar_cs_first) + // Stop believing the verdict once it is contradicted by repetition. + // + // Saying "not a loop" here is not free. It resets the spin count and + // leaves the switch at umax, and the caller then skips both busy_wait + // and the sleep path and returns immediately, so the SPU re-executes + // GETLLAR at full rate with no backoff at all. The spin count never + // reaches 4, so the optimisation is never evaluated, and the 400ms + // "don't be stubborn" fallback that would force a sleep is never + // reached either. One SPU in that state holds a core flat out. + // + // Web of Shadows sits in exactly that case: its GETLLAR sites use an + // LSA in the top 64K of local store that looks like a caller's OUT + // buffer, and process_mfc_cmd measured 55% of all CPU across the + // process while the game ran at 10-15fps. + // + // Re-entering the same site with the same stack this many times is + // itself the evidence that it is a loop, whatever the LSA looks like, + // so hand it back to the normal spin detection and let that decide + // between busy-waiting and sleeping. + if (getllar_cs_first != umax && last_getllar_lsa > getllar_cs_first && + getllar_outbuf_hits < 32) { + getllar_outbuf_hits++; getllar_busy_waiting_switch = umax; getllar_spin_count = 0; return true; diff --git a/rpcs3/Emu/Cell/SPUThread.h b/rpcs3/Emu/Cell/SPUThread.h index 25714514d..eaa89792d 100644 --- a/rpcs3/Emu/Cell/SPUThread.h +++ b/rpcs3/Emu/Cell/SPUThread.h @@ -822,6 +822,11 @@ public: u32 getllar_cs_sp = umax; u32 getllar_cs_lr = umax; u32 getllar_cs_first = umax; + + // Consecutive "not a loop" verdicts from that check at the same site. The verdict suppresses + // both the busy-wait and the sleep, so believing it forever leaves the SPU re-entering + // GETLLAR at full rate; repetition is the evidence that it is wrong. + u32 getllar_outbuf_hits = 0; u32 getllar_busy_waiting_switch = umax; // umax means the test needs evaluation, otherwise it's a boolean u64 getllar_evaluate_time = 0;