Spin detector: measure the cia range, and report it

The 1 KiB base+offset window measured nothing. On the hung device it reported
tracked=1 longest=0s: one running thread, whose cia left the window every
second, so the window reset on every tick and the counter never advanced past
zero. The loop is wider than a handful of instructions -- it polls and calls
helpers -- which the previous shape could not represent at all.

Track the RANGE cia has covered instead, and allow 64 KiB of it. A polling
loop that calls helpers stays within tens of KiB; ordinary execution covers
megabytes in a second.

The state line now reports the widest range being tracked. If this still does
not fire, that number is itself the answer -- it says how large the loop
really is, and therefore what the threshold must be, instead of costing
another reproduction to find out.
This commit is contained in:
jpolo1224
2026-08-24 15:26:30 -04:00
parent 59dc158841
commit df1cb84789
+29 -7
View File
@@ -1269,7 +1269,7 @@ public:
// Threads parked in a syscall carry cpu_flag::wait and are skipped, so a normal idle
// game cannot trip this.
{
struct spin_state { u32 base; u32 secs; };
struct spin_state { u32 lo; u32 hi; u32 secs; };
static std::unordered_map<u32, spin_state> s_spin;
static u32 s_dumps = 0;
static u32 s_worst = 0;
@@ -1299,11 +1299,23 @@ public:
const u32 cia = ppu.cia;
auto it = s_spin.find(id);
// 1 KiB window: wide enough for a loop with a call in it, far narrower
// than the range real execution covers in a second.
if (it == s_spin.end() || cia < it->second.base || cia - it->second.base > 0x400)
if (it == s_spin.end())
{
s_spin[id] = spin_state{cia, 0};
s_spin[id] = spin_state{cia, cia, 0};
return;
}
// The RANGE cia has covered, not an offset from where it was first seen.
// A 1 KiB base+offset window measured nothing: the loop here walks
// further than that every second, so the window reset on every tick and
// the counter never advanced past zero. A polling loop that calls helpers
// still stays within tens of KiB; real execution covers megabytes.
it->second.lo = std::min(it->second.lo, cia);
it->second.hi = std::max(it->second.hi, cia);
if (it->second.hi - it->second.lo > 0x10000)
{
s_spin[id] = spin_state{cia, cia, 0};
return;
}
@@ -1337,8 +1349,18 @@ public:
if (i % 10 == 0)
{
ppu_log.notice("spin detector: tracked=%u longest=%us dumps=%u",
static_cast<u32>(s_spin.size()), s_worst, s_dumps);
// Report the widest range any tracked thread is covering. If this still does
// not fire, that number IS the answer -- it says how big the loop actually is
// and therefore what the threshold has to be, instead of costing another
// reproduction to find out.
u32 widest = 0;
for (const auto& [tid, st] : s_spin)
{
widest = std::max(widest, st.hi - st.lo);
}
ppu_log.notice("spin detector: tracked=%u longest=%us widest_range=0x%x dumps=%u",
static_cast<u32>(s_spin.size()), s_worst, widest, s_dumps);
}
}