diff --git a/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore.cpp b/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore.cpp index e3581cc938..eb1f18da9b 100644 --- a/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore.cpp +++ b/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore.cpp @@ -348,8 +348,35 @@ void StaticRecompCore::LoadModule() desc->num_smc_ranges); } + +bool StaticRecompCore::IsBusyWaitLoop(u32 address) +{ + // Analysis reads guest memory and is not meaningful while a debugger or + // branch watch is active, both of which want every block to actually execute. + if (IsDebuggingEnabled() || IsBranchWatchEnabled()) + return false; + + const auto cached = m_busy_wait_cache.find(address); + if (cached != m_busy_wait_cache.end()) + return cached->second; + + constexpr std::size_t max_loop_instructions = 64; + analyzer.Analyze(address, &code_block, &m_code_buffer, + std::min(max_loop_instructions, m_code_buffer.size())); + + const bool is_busy_wait = !code_block.m_memory_exception && + std::any_of(m_code_buffer.begin(), + m_code_buffer.begin() + code_block.m_num_instructions, + [address](const PPCAnalyst::CodeOp& op) { + return op.branchIsIdleLoop && op.branchTo == address; + }); + m_busy_wait_cache.emplace(address, is_busy_wait); + return is_busy_wait; +} + void StaticRecompCore::ClearCache() { + m_busy_wait_cache.clear(); if (m_fallback_jit) m_fallback_jit->ClearCache(); diff --git a/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore.h b/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore.h index d084fea634..ea86cacea3 100644 --- a/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore.h +++ b/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore.h @@ -56,6 +56,11 @@ class StaticRecompCore : public JitBase bool IsHostCallAddress(u32 address) const; bool ShouldYieldAt(u32 address); + // True when `address` is the head of a guest busy-wait loop, so the + // dispatcher can hand the remaining slice back to CoreTiming instead of + // spinning through it in real time. + bool IsBusyWaitLoop(u32 address); + void ClearCache() override; void Jit(u32 em_address) override {} bool HandleFault(uintptr_t access_address, SContext* ctx) override { return false; } @@ -175,6 +180,9 @@ class StaticRecompCore : public JitBase u64 m_hook_fallback_instructions = 0; u64 m_timebase_cycle_remainder = 0; std::unordered_map m_dispatch_samples; + // Analysis result per loop-head address. Invalidated whenever guest code + // could have changed (ClearCache, SMC re-verification, REL relink). + std::unordered_map m_busy_wait_cache; u64 m_bursts = 0; // SyncIn..SyncOut native runs (diagnostic) u64 m_charged_cycles = 0; // cycles flushed from module charges (diagnostic) diff --git a/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore_Run.cpp b/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore_Run.cpp index d91ec2dc19..21e87366e7 100644 --- a/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore_Run.cpp +++ b/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore_Run.cpp @@ -182,7 +182,14 @@ void StaticRecompCore::Run() m_timebase_cycle_remainder = total_cycles % SystemTimers::TIMER_RATIO; // Idle loop skipping for configured target loops (e.g. Wii Menu OSIdleThread) - if (m_guest.pc == m_idle_pc && m_idle_pc != 0) + // A configured idle PC only covers loops we already know about. A + // dispatch that returns to the address it entered at is a self-loop, + // and if the analyzer agrees it is an idle loop there is nothing to + // gain from spinning it in real time. + const bool configured_idle = m_idle_pc != 0 && m_guest.pc == m_idle_pc; + const bool detected_idle = m_guest.pc == runtime_dispatch_address && + IsBusyWaitLoop(runtime_dispatch_address); + if (configured_idle || detected_idle) { m_system.GetCoreTiming().Idle(); } diff --git a/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore_SMC.cpp b/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore_SMC.cpp index 491c57ef3a..fcea661044 100644 --- a/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore_SMC.cpp +++ b/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore_SMC.cpp @@ -96,6 +96,7 @@ void StaticRecompCore::RefreshRelSections() if (m_chunk_state[i] == CHUNK_FAILED && m_failed_chunks != 0) --m_failed_chunks; m_chunk_state[i] = CHUNK_UNVERIFIED; + m_busy_wait_cache.clear(); m_effective_chunk_hashes[i] = m_module->chunk_hashes[i]; } }