IOS LLE: checkpoint NAND conformance and diagnostic tooling

Preserve formatted NAND boot and Wii Remote pairing, add DMA/SRAM conformance tests and a Wii comparison probe. Keep P1 profiling tools and native update A/B evidence; reject the unhelpful targeted SRAM shortcut.
This commit is contained in:
Yaya48 committed 2026-09-23 13:30:17 +02:00
1 parent b931671fde
commit c230971f07
64 files changed
+6488 -238

No files matched your search

+2
View File
@@ -122,6 +122,8 @@ add_library(common
OneShotEvent.h
PcapFile.cpp
PcapFile.h
PerformanceTrace.cpp
PerformanceTrace.h
Profiler.cpp
Profiler.h
Projection.h
+9 -1
View File
@@ -9,6 +9,7 @@
#include "Common/Assert.h"
#include "Common/CommonTypes.h"
#include "Common/MemoryUtil.h"
#include "Common/PerformanceTrace.h"
namespace Common
{
@@ -72,6 +73,8 @@ public:
void FreeCodeSpace()
{
ASSERT(!m_is_child);
if (region)
PerformanceTrace::CodeReset("Generated", this, region, region + total_region_size);
Common::FreeMemoryPages(region, total_region_size);
region = nullptr;
region_size = 0;
@@ -97,7 +100,12 @@ public:
{
Common::UnWriteProtectMemory(region, region_size, allow_execute);
}
void ResetCodePtr() { T::SetCodePtr(region, region + region_size); }
void ResetCodePtr()
{
if (region)
PerformanceTrace::CodeReset("Generated", this, region, region + region_size);
T::SetCodePtr(region, region + region_size);
}
size_t GetSpaceLeft() const
{
ASSERT(static_cast<size_t>(T::GetCodePtr() - region) < region_size);
+17
View File
@@ -11,6 +11,7 @@
#include "Common/CommonTypes.h"
#include "Common/IOFile.h"
#include "Common/PerformanceTrace.h"
#ifdef _WIN32
#include <process.h>
@@ -66,8 +67,24 @@ bool IsEnabled()
return s_is_enabled;
}
bool IsTraceEnabled()
{
return PerformanceTrace::Enabled();
}
void Register(const void* base_address, u32 code_size, const std::string& symbol_name)
{
// Block mappings (including invalidation) are emitted by JitCache itself.
if (!symbol_name.starts_with("JIT_PPC_"))
{
// Vertex decoding also uses JitRegister. Do not attribute it to the guest CPU.
const char* domain = symbol_name.starts_with("JIT_") ? "PPC" :
symbol_name.starts_with("VertexLoader") ? "VertexDecode" :
"OtherGenerated";
PerformanceTrace::CodeMap(domain, nullptr, base_address,
static_cast<const u8*>(base_address) + code_size, 0, 0,
symbol_name.c_str());
}
#ifdef USE_VTUNE
iJIT_Method_Load jmethod = {0};
jmethod.method_id = iJIT_GetNewMethodID();
+4 -2
View File
@@ -15,12 +15,14 @@ void Init(const std::string& perf_dir);
void Shutdown();
void Register(const void* base_address, u32 code_size, const std::string& symbol_name);
bool IsEnabled();
// ETW mappings can be requested without enabling perf-map/VTune registration.
bool IsTraceEnabled();
template <typename... Args>
inline void Register(const void* base_address, u32 code_size, fmt::format_string<Args...> format,
Args&&... args)
{
if (!IsEnabled())
if (!IsEnabled() && !IsTraceEnabled())
return;
Register(base_address, code_size, fmt::format(format, std::forward<Args>(args)...));
@@ -30,7 +32,7 @@ template <typename... Args>
inline void Register(const void* start, const void* end, fmt::format_string<Args...> format,
Args&&... args)
{
if (!IsEnabled())
if (!IsEnabled() && !IsTraceEnabled())
return;
u32 code_size = (u32)((const char*)end - (const char*)start);
+294
View File
@@ -0,0 +1,294 @@
// Copyright 2026 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Common/PerformanceTrace.h"
#include <atomic>
#include <cstdlib>
#include <cstring>
#ifdef _WIN32
#include <Windows.h>
#include <TraceLoggingProvider.h>
#pragma comment(lib, "advapi32.lib")
TRACELOGGING_DEFINE_PROVIDER(s_dolphin_perf, "Dolphin.Performance",
(0x54ee9231, 0x28be, 0x4ab0, 0x98, 0x1e, 0xb2, 0x6f, 0x97, 0x9c, 0x56,
0xa4));
#endif
namespace Common::PerformanceTrace
{
namespace
{
constexpr u32 SCHEMA = 1;
constexpr u32 SAMPLE_MASK = 1023;
struct ThreadState
{
bool sampling = false;
u32 rng = 0x9e3779b9;
u64 roots = 0;
u64 selected = 0;
u64 last_snapshot = 0;
ScopeStack stack;
std::array<u64, static_cast<size_t>(Counter::Count)> counters{};
};
thread_local ThreadState s_thread;
#ifdef _WIN32
struct Registration
{
Registration() : status(TraceLoggingRegister(s_dolphin_perf)) {}
~Registration()
{
if (status == ERROR_SUCCESS)
TraceLoggingUnregister(s_dolphin_perf);
}
ULONG status;
};
#endif
} // namespace
bool ScopeStack::Begin(Region region, u64 now)
{
if (m_depth == m_stack.size() || static_cast<size_t>(region) >= m_timings.size())
{
++m_errors;
return false;
}
m_stack[m_depth++] = {region, now, 0};
return true;
}
bool ScopeStack::End(u64 now)
{
if (m_depth == 0)
{
++m_errors;
return false;
}
const Entry entry = m_stack[--m_depth];
if (now < entry.start || now - entry.start < entry.children)
{
++m_errors;
return false;
}
const u64 elapsed = now - entry.start;
auto& timing = m_timings[static_cast<size_t>(entry.region)];
++timing.calls;
timing.inclusive += elapsed;
timing.exclusive += elapsed - entry.children;
if (m_depth != 0)
m_stack[m_depth - 1].children += elapsed;
return true;
}
void ScopeStack::Reset()
{
m_depth = 0;
m_errors = 0;
m_timings = {};
}
bool Requested()
{
#ifdef _WIN32
static const bool requested = [] {
const char* value = std::getenv("DOLPHIN_PERF_TRACE");
return value && std::strcmp(value, "1") == 0;
}();
return requested;
#else
return false;
#endif
}
bool Enabled()
{
#ifdef _WIN32
if (!Requested())
return false;
static Registration registration;
return registration.status == ERROR_SUCCESS && TraceLoggingProviderEnabled(s_dolphin_perf, 0, 0);
#else
return false;
#endif
}
u64 Now()
{
#ifdef _WIN32
LARGE_INTEGER value;
QueryPerformanceCounter(&value);
return static_cast<u64>(value.QuadPart);
#else
return 0;
#endif
}
u64 Frequency()
{
#ifdef _WIN32
static const u64 frequency = [] {
LARGE_INTEGER value;
QueryPerformanceFrequency(&value);
return static_cast<u64>(value.QuadPart);
}();
return frequency;
#else
return 0;
#endif
}
void Add(Counter counter, u64 value)
{
if (Requested() && static_cast<size_t>(counter) < s_thread.counters.size())
s_thread.counters[static_cast<size_t>(counter)] += value;
}
u64 NewGeneration()
{
static std::atomic<u64> generation{0};
return Requested() ? generation.fetch_add(1, std::memory_order_relaxed) + 1 : 0;
}
SampleRoot::SampleRoot(bool force_for_test)
{
m_denominator = force_for_test ? 1 : SAMPLE_MASK + 1;
if (!Enabled() || s_thread.sampling)
return;
++s_thread.roots;
auto& x = s_thread.rng;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
if (!force_for_test && (x & SAMPLE_MASK) != 0)
return;
++s_thread.selected;
s_thread.stack.Reset();
m_start = Now();
m_active = s_thread.stack.Begin(Region::Slice, m_start);
s_thread.sampling = m_active;
}
SampleRoot::~SampleRoot()
{
if (!m_active)
return;
const u64 end = Now();
s_thread.stack.End(end);
s_thread.sampling = false;
#ifdef _WIN32
// Sampled wall times, NOT exact total CPU times. Nested regions are exclusive here.
for (size_t i = 0; i < s_thread.stack.Timings().size(); ++i)
{
const auto& timing = s_thread.stack.Timings()[i];
if (timing.calls == 0)
continue;
TraceLoggingWrite(s_dolphin_perf, "ScopeSample", TraceLoggingUInt32(SCHEMA, "Schema"),
TraceLoggingUInt64(m_start, "BeginQPC"), TraceLoggingUInt64(end, "EndQPC"),
TraceLoggingUInt64(s_thread.selected, "Sample"),
TraceLoggingUInt32(m_denominator, "SamplingDenominator"),
TraceLoggingUInt32(static_cast<u32>(i), "Region"),
TraceLoggingUInt64(timing.calls, "Calls"),
TraceLoggingUInt64(timing.inclusive, "InclusiveQPC"),
TraceLoggingUInt64(timing.exclusive, "ExclusiveQPC"),
TraceLoggingUInt64(s_thread.stack.Errors(), "AccountingErrors"));
}
#endif
}
Scope::Scope(Region region)
{
if (s_thread.sampling)
m_active = s_thread.stack.Begin(region, Now());
}
Scope::~Scope()
{
if (m_active)
s_thread.stack.End(Now());
}
void Snapshot(u64 core_id, u64 instructions, u64 native_instructions, u64 slow_reads,
u64 slow_writes, u64 compiles, u64 fallbacks, u32 pc, bool waiting)
{
if (!Enabled())
return;
const u64 now = Now();
if (now - s_thread.last_snapshot < Frequency())
return;
s_thread.last_snapshot = now;
#ifdef _WIN32
TraceLoggingWrite(
s_dolphin_perf, "Snapshot", TraceLoggingUInt32(SCHEMA, "Schema"),
TraceLoggingUInt64(now, "QPC"), TraceLoggingUInt64(Frequency(), "QPCFrequency"),
TraceLoggingUInt64(core_id, "CoreId"), TraceLoggingUInt64(instructions, "Instructions"),
TraceLoggingUInt64(native_instructions, "NativeInstructions"),
TraceLoggingUInt64(slow_reads, "SlowReads"), TraceLoggingUInt64(slow_writes, "SlowWrites"),
TraceLoggingUInt64(compiles, "Compiles"), TraceLoggingUInt64(fallbacks, "Fallbacks"),
TraceLoggingUInt32(pc, "GuestPC"), TraceLoggingBool(waiting, "Waiting"),
TraceLoggingUInt64(s_thread.roots, "Roots"),
TraceLoggingUInt64(s_thread.selected, "SelectedRoots"));
for (size_t i = 0; i < s_thread.counters.size(); ++i)
{
TraceLoggingWrite(s_dolphin_perf, "Counter", TraceLoggingUInt32(SCHEMA, "Schema"),
TraceLoggingUInt64(now, "QPC"),
TraceLoggingUInt32(static_cast<u32>(i), "Counter"),
TraceLoggingUInt64(s_thread.counters[i], "Value"));
}
#endif
}
void CodeMap(const char* domain, const void* owner, const void* begin, const void* end,
u32 guest_pc, u64 identity, const char* kind)
{
if (!begin || reinterpret_cast<uintptr_t>(end) <= reinterpret_cast<uintptr_t>(begin) ||
!Enabled())
return;
#ifdef _WIN32
TraceLoggingWrite(s_dolphin_perf, "CodeMap", TraceLoggingUInt32(SCHEMA, "Schema"),
TraceLoggingUInt64(Now(), "QPC"), TraceLoggingString(domain, "Domain"),
TraceLoggingPointer(owner, "Owner"), TraceLoggingPointer(begin, "Begin"),
TraceLoggingPointer(end, "End"), TraceLoggingUInt32(guest_pc, "GuestPC"),
TraceLoggingUInt64(identity, "Identity"), TraceLoggingString(kind, "Kind"));
#endif
}
void CodeReset(const char* domain, const void* owner, const void* begin, const void* end)
{
CodeMap(domain, owner, begin, end, 0, 0, "invalidate");
}
void Frame(const char* kind, s64 guest_ticks, u32 guest_frequency)
{
if (!Enabled())
return;
#ifdef _WIN32
TraceLoggingWrite(s_dolphin_perf, "Frame", TraceLoggingUInt32(SCHEMA, "Schema"),
TraceLoggingUInt64(Now(), "QPC"),
TraceLoggingUInt64(Frequency(), "QPCFrequency"),
TraceLoggingString(kind, "Kind"), TraceLoggingInt64(guest_ticks, "GuestTicks"),
TraceLoggingUInt32(guest_frequency, "GuestFrequency"));
#endif
}
void Marker(const char* name)
{
if (!Enabled())
return;
#ifdef _WIN32
TraceLoggingWrite(s_dolphin_perf, "Marker", TraceLoggingUInt32(SCHEMA, "Schema"),
TraceLoggingUInt64(Now(), "QPC"),
TraceLoggingUInt64(Frequency(), "QPCFrequency"),
TraceLoggingString(name, "Name"));
#endif
}
void Queue(const void* owner, u64 frames, u64 bytes, u64 oldest_qpc, u64 peak)
{
if (!Enabled())
return;
#ifdef _WIN32
TraceLoggingWrite(s_dolphin_perf, "USBQueue", TraceLoggingUInt32(SCHEMA, "Schema"),
TraceLoggingUInt64(Now(), "QPC"), TraceLoggingPointer(owner, "Owner"),
TraceLoggingUInt64(frames, "Frames"), TraceLoggingUInt64(bytes, "Bytes"),
TraceLoggingUInt64(oldest_qpc, "OldestQPC"),
TraceLoggingUInt64(peak, "PeakFrames"));
#endif
}
} // namespace Common::PerformanceTrace
+126
View File
@@ -0,0 +1,126 @@
// Copyright 2026 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <cstddef>
#include "Common/CommonTypes.h"
namespace Common::PerformanceTrace
{
// Opt-in diagnostic events, never guest state. Windows provider:
// Dolphin.Performance / {54ee9231-28be-4ab0-981e-b26f979c56a4}, schema 1.
// Set DOLPHIN_PERF_TRACE=1 before process creation and start ETW before launching.
enum class Region : u32
{
Slice,
ARM,
Jit,
Compile,
Devices,
NAND,
Journal,
JournalBuild,
JournalWrite,
JournalFlush,
NAT,
DMARead,
DMAWrite,
AES,
SHA,
Count
};
enum class Counter : u32
{
JournalTransactions,
JournalBytes,
JournalErrors,
NatRXFrames,
NatRXBytes,
NatTXFrames,
NatTXBytes,
NatPollErrors,
UsbRXFrames,
UsbRXBytes,
UsbDrops,
UsbDeliveredBytes,
NANDReads,
NANDPrograms,
NANDErases,
Count
};
struct Timing
{
u64 calls = 0;
u64 inclusive = 0;
u64 exclusive = 0;
};
// Clock-independent accounting, also exercised with deterministic timestamps in tests.
class ScopeStack
{
public:
bool Begin(Region region, u64 now);
bool End(u64 now);
void Reset();
size_t Depth() const { return m_depth; }
u64 Errors() const { return m_errors; }
const auto& Timings() const { return m_timings; }
private:
struct Entry
{
Region region;
u64 start;
u64 children;
};
std::array<Entry, 32> m_stack{};
std::array<Timing, static_cast<size_t>(Region::Count)> m_timings{};
size_t m_depth = 0;
u64 m_errors = 0;
};
bool Requested();
bool Enabled();
u64 Now();
u64 Frequency();
u64 NewGeneration();
void Add(Counter counter, u64 value = 1);
void Snapshot(u64 core_id, u64 instructions, u64 native_instructions, u64 slow_reads,
u64 slow_writes, u64 compiles, u64 fallbacks, u32 pc, bool waiting);
void CodeMap(const char* domain, const void* owner, const void* begin, const void* end,
u32 guest_pc, u64 identity, const char* kind);
void CodeReset(const char* domain, const void* owner, const void* begin, const void* end);
void Frame(const char* kind, s64 guest_ticks = 0, u32 guest_frequency = 0);
void Marker(const char* name);
void Queue(const void* owner, u64 frames, u64 bytes, u64 oldest_qpc, u64 peak);
// Only scopes inside a randomly selected root slice read the clock. No per-access timers.
class SampleRoot
{
public:
explicit SampleRoot(bool force_for_test = false);
~SampleRoot();
SampleRoot(const SampleRoot&) = delete;
SampleRoot& operator=(const SampleRoot&) = delete;
private:
bool m_active = false;
u64 m_start = 0;
u32 m_denominator = 1024;
};
class Scope
{
public:
explicit Scope(Region region);
~Scope();
Scope(const Scope&) = delete;
Scope& operator=(const Scope&) = delete;
private:
bool m_active = false;
};
} // namespace Common::PerformanceTrace
+2
View File
@@ -258,6 +258,8 @@ const Info<std::string> MAIN_GPU_DETERMINISM_MODE{{System::Main, "Core", "GPUDet
const Info<s32> MAIN_OVERRIDE_BOOT_IOS{{System::Main, "Core", "OverrideBootIOS"}, -1};
const Info<bool> MAIN_WII_IOS_LLE{{System::Main, "Core", "WiiIOSLLE"}, false};
const Info<bool> MAIN_WII_STARLET_JIT{{System::Main, "Core", "WiiStarletJIT"}, true};
const Info<bool> MAIN_WII_STARLET_JIT_MEMORY_PROFILING{
{System::Main, "Core", "WiiStarletJITMemoryProfiling"}, false};
const Info<bool> MAIN_WII_LLE_ETHERNET{{System::Main, "Core", "WiiLLEEthernet"}, false};
GPUDeterminismMode GetGPUDeterminismMode()
+1
View File
@@ -168,6 +168,7 @@ extern const Info<bool> MAIN_REAL_WII_REMOTE_REPEAT_REPORTS;
extern const Info<s32> MAIN_OVERRIDE_BOOT_IOS;
extern const Info<bool> MAIN_WII_IOS_LLE;
extern const Info<bool> MAIN_WII_STARLET_JIT;
extern const Info<bool> MAIN_WII_STARLET_JIT_MEMORY_PROFILING;
extern const Info<bool> MAIN_WII_LLE_ETHERNET;
extern const Info<std::string> MAIN_WII_NUS_SHOP_URL;
extern const Info<bool> MAIN_WII_WIILINK_ENABLE;
+13 -4
View File
@@ -12,6 +12,7 @@
#include <utility>
#include "Common/ChunkFile.h"
#include "Common/PerformanceTrace.h"
#if defined(_M_X86_64)
// ARMCore owns ARMJitX64 by value through unique_ptr construction in this translation unit, so a
// private JIT layout change must rebuild this file as well as the emitter.
@@ -32,7 +33,8 @@ constexpr u32 ModeValue(ARMCore::Mode mode)
}
} // namespace
ARMCore::ARMCore(ARMBus& bus) : m_bus(bus)
ARMCore::ARMCore(ARMBus& bus, bool profile_jit_memory)
: m_bus(bus), m_profile_jit_memory(profile_jit_memory)
{
Reset();
}
@@ -52,6 +54,7 @@ void ARMCore::SetJitEnabled(bool enabled)
void ARMCore::Reset(u32 reset_vector)
{
m_trace_generation = Common::PerformanceTrace::NewGeneration();
m_registers.fill(0);
m_user_r8_r14.fill(0);
m_fiq_r8_r14.fill(0);
@@ -237,9 +240,12 @@ u32 ARMCore::WalkPageTables(u32 modified_address, const u8** first_descriptor,
u32* first_descriptor_value, const u8** second_descriptor,
u32* second_descriptor_value) const
{
const auto read_descriptor = [&](u32 physical_address, const u8** host_pointer,
u32* host_value) {
const u8* const pointer = m_bus.GetDirectMemoryPointer(physical_address, sizeof(u32));
const auto read_descriptor = [&](u32 physical_address, const u8** host_pointer, u32* host_value) {
// Only native block validation needs a persistent descriptor pointer. Ordinary TLB
// misses need the bus value alone; do not resolve the same physical memory twice.
const u8* const pointer = (host_pointer || host_value) ?
m_bus.GetDirectMemoryPointer(physical_address, sizeof(u32)) :
nullptr;
if (host_pointer)
*host_pointer = pointer;
if (host_value)
@@ -791,6 +797,7 @@ u64 ARMCore::Run(u64 instruction_count)
u64 ARMCore::RunCycles(u64 cycle_budget)
{
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::ARM);
u64 cycles = 0;
while (cycles < cycle_budget && !m_yield_requested)
{
@@ -1341,6 +1348,8 @@ std::vector<ARMCore::HotPCSample> ARMCore::TakeHotJitFallbackIntervalSamples(siz
void ARMCore::DoState(PointerWrap& p)
{
if (p.IsReadMode())
m_trace_generation = Common::PerformanceTrace::NewGeneration();
p.DoArray(m_registers);
p.Do(m_cpsr);
p.DoArray(m_user_r8_r14);
+6 -1
View File
@@ -112,7 +112,8 @@ public:
static constexpr u32 CPSR_T = 1U << 5;
static constexpr u32 CPSR_MODE_MASK = 0x1f;
explicit ARMCore(ARMBus& bus);
// Host-only diagnostic choice, fixed for this core's lifetime and not part of guest state.
explicit ARMCore(ARMBus& bus, bool profile_jit_memory = false);
~ARMCore();
void Reset(u32 reset_vector = 0);
@@ -165,6 +166,8 @@ public:
u64 GetExecutedInstructions() const { return m_executed_instructions; }
void SetJitEnabled(bool enabled);
bool IsJitEnabled() const { return m_jit_enabled; }
bool IsJitMemoryProfilingEnabled() const { return m_profile_jit_memory; }
u64 GetTraceGeneration() const { return m_trace_generation; }
u64 GetJitExecutedInstructions() const;
u64 GetJitNativeExecutedInstructions() const;
size_t GetJitCompiledBlockCount() const;
@@ -322,6 +325,8 @@ private:
void WriteCP15(u32 opcode1, u32 crn, u32 crm, u32 opcode2, u32 value);
ARMBus& m_bus;
const bool m_profile_jit_memory;
u64 m_trace_generation = 0; // Host diagnostics only; never serialized.
std::array<u32, 16> m_registers{};
u32 m_cpsr = 0;
+104 -84
View File
@@ -10,6 +10,7 @@
#include "Common/BitSet.h"
#include "Common/CommonTypes.h"
#include "Common/PerformanceTrace.h"
#include "Common/x64ABI.h"
#include "Core/HW/Memmap.h"
#include "Core/IOS/Starlet/ARMCore.h"
@@ -94,6 +95,7 @@ ARMJitX64::ARMJitX64(ARMCore& core) : m_core(core)
ARMJitX64::~ARMJitX64()
{
Common::PerformanceTrace::CodeReset("ARM", this, region, region + region_size);
FreeCodeSpace();
}
@@ -128,6 +130,7 @@ void ARMJitX64::RequestClear()
void ARMJitX64::ClearCodeCache()
{
Common::PerformanceTrace::CodeReset("ARM", this, m_block_code_begin, region + region_size);
m_discarded_block_count += m_blocks.size();
m_blocks.clear();
std::ranges::fill(m_fast_entries, FastEntry{});
@@ -150,6 +153,7 @@ void ARMJitX64::InvalidateTranslationContext()
u32 ARMJitX64::Run(u64 cycle_budget)
{
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::Jit);
if (cycle_budget == 0 || !m_run_entry)
return 0;
@@ -405,6 +409,7 @@ void ARMJitX64::GenerateDispatcher()
RET();
m_block_code_begin = AlignCode16();
Common::PerformanceTrace::CodeMap("ARM", this, region, m_block_code_begin, 0, 0, "dispatcher");
}
u32 ARMJitX64::MakeFastEntryKey(u32 address, u32 control, u32 process_id)
@@ -562,13 +567,17 @@ ARMJitX64::Block* ARMJitX64::GetOrCompileBlock(u32 address, u32* physical_addres
return nullptr;
}
const u8* const trace_begin = GetCodePtr();
Block block = CompileBlock(virtual_address, thumb);
Common::PerformanceTrace::CodeMap("ARM", this, trace_begin, GetCodePtr(), virtual_address,
block_key, thumb ? "thumb" : "arm");
++m_lifetime_compiled_block_count;
return &m_blocks.emplace(block_key, block).first->second;
}
ARMJitX64::Block ARMJitX64::CompileBlock(u32 address, bool thumb)
{
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::Compile);
const u8* const body = AlignCode16();
LoadRegisterCache();
MOV(8, MPCWritten(), Imm8(0));
@@ -2256,8 +2265,10 @@ void ARMJitX64::EmitFastmemAddress(std::vector<FixupBranch>* slow_paths, u32 acc
// Addresses above MEM2 are only directly accessible when they hit the high Starlet SRAM
// mirror. Everything else remains on the exact bus/MMIO path.
if (sram_access == SRAMFastmemAccess::None || !m_sram_base || !m_boot0_mapped ||
!m_sram_split_mode)
// SRAM writes are still bus-only. Reject them before testing the direct-read aperture;
// retain R9's physical address for the exact slow write without redundant SRAM checks.
if (sram_access == SRAMFastmemAccess::None || sram_access == SRAMFastmemAccess::Write ||
!m_sram_base || !m_boot0_mapped || !m_sram_split_mode)
{
slow_paths->push_back(J(Jump::Near));
SetJumpTarget(below_mem2);
@@ -2311,37 +2322,20 @@ void ARMJitX64::EmitFastmemAddress(std::vector<FixupBranch>* slow_paths, u32 acc
MOV(32, R(EAX), R(R9));
AND(32, R(EAX), Imm32(0x1ffff));
// Full-chain profiling gives a narrow safe aperture for reads. Direct writes to the measured
// IOS pages 0x00/0x19 passed isolated bounds tests, but reproducibly terminated BootMii during
// its hardware setup. Keep every SRAM write on the exact bus path until write coherency with
// the live boot chain is understood. Boot0, split-window holes and register-list transfers also
// remain on the bus.
// Limit scalar direct reads to pages measured across the boot chain and native IOS update.
// EDX is an aperture page in [0, 31]; the mask avoids a growing chain of comparisons. Keep
// ECX intact for the caller's unaligned ARM word rotation. Boot0 and split-window holes still
// use the bus. Writes and register-list transfers were rejected above: direct SRAM writes
// previously regressed BootMii despite passing isolated bounds tests.
MOV(32, R(EDX), R(EAX));
SHR(32, R(EDX), Imm8(12));
if (sram_access == SRAMFastmemAccess::Write)
{
slow_paths->push_back(J(Jump::Near));
}
else
{
CMP(32, R(EDX), Imm32(0x00));
const FixupBranch profiled_read_page_00 = J_CC(CC_E, Jump::Near);
CMP(32, R(EDX), Imm32(0x10));
const FixupBranch irq_vector_read_page_10 = J_CC(CC_E, Jump::Near);
CMP(32, R(EDX), Imm32(0x12));
const FixupBranch profiled_read_page_12 = J_CC(CC_E, Jump::Near);
CMP(32, R(EDX), Imm32(0x14));
const FixupBranch profiled_read_page_14 = J_CC(CC_E, Jump::Near);
CMP(32, R(EDX), Imm32(0x19));
const FixupBranch profiled_read_page_19 = J_CC(CC_E, Jump::Near);
CMP(32, R(EDX), Imm32(0x1e));
slow_paths->push_back(J_CC(CC_NE, Jump::Near));
SetJumpTarget(profiled_read_page_00);
SetJumpTarget(irq_vector_read_page_10);
SetJumpTarget(profiled_read_page_12);
SetJumpTarget(profiled_read_page_14);
SetJumpTarget(profiled_read_page_19);
}
constexpr u32 profiled_read_pages = (1U << 0x00) | (1U << 0x01) | (1U << 0x02) | (1U << 0x04) |
(1U << 0x10) | (1U << 0x12) | (1U << 0x13) | (1U << 0x14) |
(1U << 0x15) | (1U << 0x16) | (1U << 0x19) | (1U << 0x1a) |
(1U << 0x1c) | (1U << 0x1d) | (1U << 0x1e);
MOV(32, R(R8), Imm32(profiled_read_pages));
BT(32, R(R8), R(EDX));
slow_paths->push_back(J_CC(CC_NC, Jump::Near));
MOV(64, R(R8), ImmPtr(m_sram_split_mode));
CMP(8, MatR(R8), Imm8(0));
@@ -2408,17 +2402,25 @@ void ARMJitX64::EmitThumbMemorySlowPath(const std::vector<FixupBranch>& slow_pat
MOV(32, R(ABI_PARAM4), Imm32(0));
MOV(32, R(ABI_PARAM3), Imm32(access_size));
MOV(64, R(ABI_PARAM1), ImmPtr(this));
ABI_CallFunction(ReadMemorySlow);
ABI_CallFunction(m_core.IsJitMemoryProfilingEnabled() ? ReadMemorySlow<true> :
ReadMemorySlow<false>);
if (sign_extend)
MOVSX(32, access_size * 8, EAX, R(EAX));
MOV(32, MStoredRegister(rd), R(EAX));
}
else
{
MOV(32, R(ABI_PARAM4), MStoredRegister(rd));
MOV(32, R(ABI_PARAM3), Imm32(access_size));
MOV(32, R(ABI_PARAM3), MStoredRegister(rd));
MOV(64, R(ABI_PARAM1), ImmPtr(this));
ABI_CallFunction(WriteMemorySlow);
if (access_size == 1)
ABI_CallFunction(m_core.IsJitMemoryProfilingEnabled() ? WriteMemorySlow<1, true> :
WriteMemorySlow<1, false>);
else if (access_size == 2)
ABI_CallFunction(m_core.IsJitMemoryProfilingEnabled() ? WriteMemorySlow<2, true> :
WriteMemorySlow<2, false>);
else
ABI_CallFunction(m_core.IsJitMemoryProfilingEnabled() ? WriteMemorySlow<4, true> :
WriteMemorySlow<4, false>);
}
if (access_size > 1)
{
@@ -2514,16 +2516,21 @@ void ARMJitX64::EmitARMMemorySlowPath(const std::vector<FixupBranch>& slow_paths
MOV(32, R(ABI_PARAM4), R(RBP));
MOV(32, R(ABI_PARAM3), Imm32(access_size));
MOV(64, R(ABI_PARAM1), ImmPtr(this));
ABI_CallFunction(ReadMemorySlow);
ABI_CallFunction(m_core.IsJitMemoryProfilingEnabled() ? ReadMemorySlow<true> :
ReadMemorySlow<false>);
MOV(32, MStoredRegister(rd), R(EAX));
}
else
{
MOV(32, R(ABI_PARAM2), R(R9));
MOV(32, R(ABI_PARAM4), MStoredRegister(rd));
MOV(32, R(ABI_PARAM3), Imm32(access_size));
MOV(32, R(ABI_PARAM3), MStoredRegister(rd));
MOV(64, R(ABI_PARAM1), ImmPtr(this));
ABI_CallFunction(WriteMemorySlow);
if (byte)
ABI_CallFunction(m_core.IsJitMemoryProfilingEnabled() ? WriteMemorySlow<1, true> :
WriteMemorySlow<1, false>);
else
ABI_CallFunction(m_core.IsJitMemoryProfilingEnabled() ? WriteMemorySlow<4, true> :
WriteMemorySlow<4, false>);
}
if (writeback)
MOV(32, MStoredRegister(rn), R(RBX));
@@ -2959,29 +2966,35 @@ void ARMJitX64::ExceptionReturn(ARMJitX64* jit, u32 target)
core.WritePC(target);
}
template <bool profile>
u32 ARMJitX64::ReadMemorySlow(ARMJitX64* jit, u32 physical_address, u32 access_size,
u32 byte_offset)
{
++jit->m_slow_read_count;
if ((jit->m_slow_read_count & 0xff) == 0)
++jit->m_slow_memory_address_samples[physical_address & ~3ULL];
switch (ClassifySlowMemoryAddress(physical_address))
// The helper is selected at code generation, so disabling detailed diagnostics adds no
// per-access mode branch. Keep the aggregate counter and the exact bus transaction in both.
if constexpr (profile)
{
case SlowMemoryRegion::RAM:
++jit->m_slow_ram_read_count;
break;
case SlowMemoryRegion::SRAM:
++jit->m_slow_sram_read_count;
++(physical_address < 0xfff00000 ? jit->m_slow_sram_low_access_count :
jit->m_slow_sram_high_access_count);
++jit->m_slow_sram_page_read_access_count[(physical_address & 0x1ffff) >> 12];
break;
case SlowMemoryRegion::MMIO:
++jit->m_slow_mmio_read_count;
break;
case SlowMemoryRegion::Other:
++jit->m_slow_other_read_count;
break;
if ((jit->m_slow_read_count & 0xff) == 0)
++jit->m_slow_memory_address_samples[physical_address & ~3ULL];
switch (ClassifySlowMemoryAddress(physical_address))
{
case SlowMemoryRegion::RAM:
++jit->m_slow_ram_read_count;
break;
case SlowMemoryRegion::SRAM:
++jit->m_slow_sram_read_count;
++(physical_address < 0xfff00000 ? jit->m_slow_sram_low_access_count :
jit->m_slow_sram_high_access_count);
++jit->m_slow_sram_page_read_access_count[(physical_address & 0x1ffff) >> 12];
break;
case SlowMemoryRegion::MMIO:
++jit->m_slow_mmio_read_count;
break;
case SlowMemoryRegion::Other:
++jit->m_slow_other_read_count;
break;
}
}
ARMCore& core = jit->m_core;
if (access_size == 1)
@@ -2998,45 +3011,52 @@ u32 ARMJitX64::ReadMemorySlow(ARMJitX64* jit, u32 physical_address, u32 access_s
return std::rotr(value, static_cast<int>((byte_offset & 3) * 8));
}
void ARMJitX64::WriteMemorySlow(ARMJitX64* jit, u32 physical_address, u32 access_size, u32 value)
template <u32 access_size, bool profile>
void ARMJitX64::WriteMemorySlow(ARMJitX64* jit, u32 physical_address, u32 value)
{
// Select width while compiling the guest instruction, not on every dynamic bus write.
// Mapping, protection, MMIO side effects, endian conversion and aggregate counts stay live.
static_assert(access_size == 1 || access_size == 2 || access_size == 4);
++jit->m_slow_write_count;
if ((jit->m_slow_write_count & 0xff) == 0)
++jit->m_slow_memory_address_samples[(1ULL << 32) | (physical_address & ~3ULL)];
switch (ClassifySlowMemoryAddress(physical_address))
if constexpr (profile)
{
case SlowMemoryRegion::RAM:
++jit->m_slow_ram_write_count;
break;
case SlowMemoryRegion::SRAM:
++jit->m_slow_sram_write_count;
++(physical_address < 0xfff00000 ? jit->m_slow_sram_low_access_count :
jit->m_slow_sram_high_access_count);
++jit->m_slow_sram_page_write_access_count[(physical_address & 0x1ffff) >> 12];
break;
case SlowMemoryRegion::MMIO:
++jit->m_slow_mmio_write_count;
break;
case SlowMemoryRegion::Other:
++jit->m_slow_other_write_count;
break;
if ((jit->m_slow_write_count & 0xff) == 0)
++jit->m_slow_memory_address_samples[(1ULL << 32) | (physical_address & ~3ULL)];
switch (ClassifySlowMemoryAddress(physical_address))
{
case SlowMemoryRegion::RAM:
++jit->m_slow_ram_write_count;
break;
case SlowMemoryRegion::SRAM:
++jit->m_slow_sram_write_count;
++(physical_address < 0xfff00000 ? jit->m_slow_sram_low_access_count :
jit->m_slow_sram_high_access_count);
++jit->m_slow_sram_page_write_access_count[(physical_address & 0x1ffff) >> 12];
break;
case SlowMemoryRegion::MMIO:
++jit->m_slow_mmio_write_count;
break;
case SlowMemoryRegion::Other:
++jit->m_slow_other_write_count;
break;
}
}
ARMCore& core = jit->m_core;
if (access_size == 1)
if constexpr (access_size == 1)
{
core.m_bus.Write8(physical_address, static_cast<u8>(value));
return;
}
if (access_size == 2)
else if constexpr (access_size == 2)
{
const u16 halfword = static_cast<u16>(value);
core.m_bus.Write16(physical_address, core.m_big_endian ? halfword : std::byteswap(halfword));
return;
}
if (!core.m_big_endian)
value = std::byteswap(value);
core.m_bus.Write32(physical_address & ~3U, value);
else
{
if (!core.m_big_endian)
value = std::byteswap(value);
core.m_bus.Write32(physical_address & ~3U, value);
}
}
std::vector<std::pair<u64, u64>> ARMJitX64::TakeHotSlowMemorySamples(size_t maximum_count)
+3 -1
View File
@@ -227,8 +227,10 @@ private:
static void ExecuteUserBankBlockTransfer(ARMJitX64* jit, u32 instruction, u32 address);
static void ExecuteThumbPushPop(ARMJitX64* jit, u16 instruction, u32 address);
static void ExceptionReturn(ARMJitX64* jit, u32 target);
template <bool profile>
static u32 ReadMemorySlow(ARMJitX64* jit, u32 physical_address, u32 access_size, u32 byte_offset);
static void WriteMemorySlow(ARMJitX64* jit, u32 physical_address, u32 access_size, u32 value);
template <u32 access_size, bool profile>
static void WriteMemorySlow(ARMJitX64* jit, u32 physical_address, u32 value);
static u32 TranslateAddress(ARMJitX64* jit, u32 address);
static void EnterUndefinedARM(ARMJitX64* jit, u32 instruction, u32 address);
static void EnterSVCARM(ARMJitX64* jit, u32 address);
+36
View File
@@ -11,6 +11,7 @@
#include "Common/ChunkFile.h"
#include "Common/Logging/Log.h"
#include "Common/PerformanceTrace.h"
namespace IOS::LLE
{
@@ -43,6 +44,8 @@ AX88772::AX88772(Transmit transmit) : m_transmit(std::move(transmit))
void AX88772::Reset()
{
m_trace_rx_times = {};
m_trace_rx_head = m_trace_rx_peak = 0;
m_address = m_configuration = 0;
m_control_valid = m_control_stalled = false;
m_control.clear();
@@ -77,6 +80,11 @@ bool AX88772::LinkUp() const
void AX88772::DoState(PointerWrap& p)
{
if (p.IsReadMode())
{
m_trace_rx_times = {};
m_trace_rx_head = m_trace_rx_peak = 0;
}
p.Do(m_mac);
p.Do(m_eeprom);
p.Do(m_phy);
@@ -403,6 +411,8 @@ bool AX88772::ApplyControl(std::span<const u8> data)
if (value & 1)
{
m_rx.clear();
m_trace_rx_times = {};
m_trace_rx_head = 0;
m_rx_offset = 0;
}
if (value & 2)
@@ -451,7 +461,10 @@ void AX88772::ReceiveFrame(std::span<const u8> frame)
{
if (!LinkUp() || (m_rx_control & 0x80) == 0 || (m_medium & 0x100) == 0 || frame.size() < 14 ||
frame.size() > MAX_FRAME || m_rx.size() >= MAX_QUEUE)
{
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::UsbDrops);
return;
}
const bool broadcast =
std::all_of(frame.begin(), frame.begin() + 6, [](u8 b) { return b == 0xff; });
const bool multicast = (frame[0] & 1) != 0;
@@ -465,7 +478,28 @@ void AX88772::ReceiveFrame(std::span<const u8> frame)
packet.insert(packet.end(), frame.begin(), frame.end());
if (length & 1)
packet.push_back(0);
if (Common::PerformanceTrace::Requested())
{
m_trace_rx_times[(m_trace_rx_head + m_rx.size()) % m_trace_rx_times.size()] =
Common::PerformanceTrace::Now();
m_trace_rx_peak = std::max(m_trace_rx_peak, m_rx.size() + 1);
}
m_rx.push_back(std::move(packet));
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::UsbRXFrames);
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::UsbRXBytes, frame.size());
}
void AX88772::TraceQueue() const
{
if (!Common::PerformanceTrace::Enabled())
return;
u64 bytes = 0;
for (const auto& packet : m_rx)
bytes += packet.size();
if (!m_rx.empty())
bytes -= m_rx_offset;
Common::PerformanceTrace::Queue(this, m_rx.size(), bytes,
m_rx.empty() ? 0 : m_trace_rx_times[m_trace_rx_head], m_trace_rx_peak);
}
AX88772::Result AX88772::Transfer(u8 endpoint, u8 direction, std::span<u8> buffer, size_t* actual)
@@ -560,9 +594,11 @@ AX88772::Result AX88772::Transfer(u8 endpoint, u8 direction, std::span<u8> buffe
*actual = std::min(buffer.size(), packet.size() - m_rx_offset);
std::copy_n(packet.begin() + m_rx_offset, *actual, buffer.begin());
m_rx_offset += *actual;
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::UsbDeliveredBytes, *actual);
if (m_rx_offset == packet.size())
{
m_rx.pop_front();
m_trace_rx_head = (m_trace_rx_head + 1) % m_trace_rx_times.size();
m_rx_offset = 0;
}
return Result::Completed;
+4
View File
@@ -36,6 +36,7 @@ public:
void ReceiveFrame(std::span<const u8> frame);
Result Transfer(u8 endpoint, u8 direction, std::span<u8> buffer, size_t* actual);
u8 GetAddress() const { return m_address; }
void TraceQueue() const;
private:
static constexpr u8 INTERRUPT_INTERVAL_MS = 10;
@@ -77,5 +78,8 @@ private:
// Diagnostics only; not part of guest-visible/savestate state.
u32 m_control_log_count = 0;
u64 m_interrupt_reports = 0;
std::array<u64, 128> m_trace_rx_times{};
size_t m_trace_rx_head = 0;
size_t m_trace_rx_peak = 0;
};
} // namespace IOS::LLE
+31 -12
View File
@@ -13,6 +13,7 @@
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Common/PerformanceTrace.h"
namespace IOS::LLE
{
@@ -137,31 +138,49 @@ bool NANDJournal::Open(const std::string& path, const Common::SHA1::Digest& sour
bool NANDJournal::Append(const Pages& pages, bool replace)
{
namespace Trace = Common::PerformanceTrace;
const Trace::Scope trace(Trace::Region::Journal);
if (!m_file || pages.size() > PAGE_COUNT)
return false;
if (pages.empty() && !replace)
return true;
std::vector<u8> record;
record.reserve(TRANSACTION_HEADER_SIZE + pages.size() * ENTRY_SIZE);
Put32(record, TRANSACTION_MAGIC);
Put32(record, replace ? 1 : 0);
Put32(record, static_cast<u32>(pages.size()));
for (const auto& [page, data] : pages)
Common::SHA1::Digest checksum;
{
if (page >= PAGE_COUNT)
return false;
Put32(record, page);
record.insert(record.end(), data.begin(), data.end());
const Trace::Scope build_trace(Trace::Region::JournalBuild);
record.reserve(TRANSACTION_HEADER_SIZE + pages.size() * ENTRY_SIZE);
Put32(record, TRANSACTION_MAGIC);
Put32(record, replace ? 1 : 0);
Put32(record, static_cast<u32>(pages.size()));
for (const auto& [page, data] : pages)
{
if (page >= PAGE_COUNT)
return false;
Put32(record, page);
record.insert(record.end(), data.begin(), data.end());
}
checksum = Common::SHA1::CalculateDigest(record);
}
const auto checksum = Common::SHA1::CalculateDigest(record);
if (!m_file.WriteArray(record.data(), record.size()) || !m_file.WriteArray(checksum) ||
!m_file.Flush())
bool success;
{
const Trace::Scope write_trace(Trace::Region::JournalWrite);
success = m_file.WriteArray(record.data(), record.size()) && m_file.WriteArray(checksum);
}
if (success)
{
const Trace::Scope flush_trace(Trace::Region::JournalFlush);
success = m_file.Flush();
}
if (!success)
{
Trace::Add(Trace::Counter::JournalErrors);
// Never acknowledge volatile writes as successful after a storage error.
ERROR_LOG_FMT(IOS, "Persistent NAND write failed; subsequent NAND writes are disabled");
m_file.Close();
return false;
}
Trace::Add(Trace::Counter::JournalTransactions);
Trace::Add(Trace::Counter::JournalBytes, record.size() + checksum.size());
return true;
}
} // namespace IOS::LLE
@@ -18,6 +18,7 @@
#include "Common/DynamicLibrary.h"
#include "Common/Logging/Log.h"
#include "Common/Network.h"
#include "Common/PerformanceTrace.h"
#include "Common/StringUtil.h"
namespace IOS::LLE
@@ -192,6 +193,8 @@ bool SlirpNetwork::Start(const std::string& directory, Receive receive, std::str
auto& self = *static_cast<Impl*>(opaque);
if (length >= 14 && length <= 1518 && self.receive)
{
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::NatRXFrames);
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::NatRXBytes, length);
if (++self.rx_frames <= 8)
INFO_LOG_FMT(IOS_NET, "Starlet NAT -> USB Ethernet: frame {} ({} bytes)", self.rx_frames,
length);
@@ -244,8 +247,11 @@ bool SlirpNetwork::Start(const std::string& directory, Receive receive, std::str
void SlirpNetwork::Input(std::span<const u8> frame)
{
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::NAT);
if (m_impl && frame.size() >= 14 && frame.size() <= 1518)
{
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::NatTXFrames);
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::NatTXBytes, frame.size());
if (++m_impl->tx_frames <= 8)
INFO_LOG_FMT(IOS_NET, "Starlet USB Ethernet -> NAT: frame {} ({} bytes, type {:#06x})",
m_impl->tx_frames, frame.size(), (u16(frame[12]) << 8) | frame[13]);
@@ -256,6 +262,7 @@ void SlirpNetwork::Input(std::span<const u8> frame)
void SlirpNetwork::Poll(u64 arm_cycles)
{
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::NAT);
if (!m_impl)
return;
auto& self = *m_impl;
@@ -285,6 +292,7 @@ void SlirpNetwork::Poll(u64 arm_cycles)
#endif
if (result < 0)
{
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::NatPollErrors);
#ifdef _WIN32
const int error = WSAGetLastError();
#else
+64 -48
View File
@@ -9,6 +9,7 @@
#include "Common/ChunkFile.h"
#include "Common/GekkoDisassembler.h"
#include "Common/Logging/Log.h"
#include "Common/PerformanceTrace.h"
#include "Common/Timer.h"
#include "Core/Config/MainSettings.h"
#include "Core/CoreTiming.h"
@@ -43,7 +44,8 @@ bool Starlet::Init(const std::string& dump_directory, std::string* error)
}
// ARMCore owns generation-tagged software TLB and instruction caches, so
// construct it only after the complete physical bus exists.
m_core = std::make_unique<ARMCore>(*m_memory);
m_core = std::make_unique<ARMCore>(*m_memory,
Config::Get(Config::MAIN_WII_STARLET_JIT_MEMORY_PROFILING));
#if defined(_M_X86_64)
m_core->SetJitEnabled(Config::Get(Config::MAIN_WII_STARLET_JIT));
#else
@@ -62,6 +64,8 @@ bool Starlet::Init(const std::string& dump_directory, std::string* error)
m_system.GetCoreTiming().ScheduleEvent(0, m_run_event);
INFO_LOG_FMT(IOS, "Starlet LLE started at PC {:#010x} ({})", m_core->GetRegister(15),
m_core->IsJitEnabled() ? "JIT" : "interpreter");
INFO_LOG_FMT(IOS, "Starlet JIT detailed memory profiling={} (aggregate counts remain active)",
m_core->IsJitMemoryProfilingEnabled());
return true;
}
@@ -176,6 +180,7 @@ void Starlet::RunCallback(Core::System& system, u64, s64 cycles_late)
void Starlet::RunSlice(s64 cycles_late)
{
const Common::PerformanceTrace::SampleRoot trace_root;
if (!m_initialized)
return;
@@ -205,6 +210,13 @@ void Starlet::RunSlice(s64 cycles_late)
const u64 now_ms = check_realtime_diagnostic ? Common::Timer::NowMs() : 0;
if (check_realtime_diagnostic && now_ms >= m_next_realtime_diagnostic_ms)
{
Common::PerformanceTrace::Snapshot(
m_core->GetTraceGeneration(), m_core->GetExecutedInstructions(),
m_core->GetJitNativeExecutedInstructions(), m_core->GetJitSlowReadCount(),
m_core->GetJitSlowWriteCount(), m_core->GetJitLifetimeCompiledBlockCount(),
m_core->GetJitFallbackInstructionCount(), m_core->GetRegister(15),
m_core->IsWaitingForExternalEvent());
m_memory->TraceNetwork();
m_next_realtime_diagnostic_ms = now_ms + 1000;
const PowerPC::PowerPCState& ppc = m_system.GetPPCState();
const PowerPC::TryReadInstResult instruction = m_system.GetMMU().TryReadInstruction(ppc.pc);
@@ -282,18 +294,19 @@ void Starlet::RunSlice(s64 cycles_late)
INFO_LOG_FMT(IOS,
"Starlet live JIT: instructions={} native={:.1f}% fallbacks={} blocks={} "
"translations={} slow-reads={} "
"slow-writes={} slow-regions=ram:{}/sram:{}/mmio:{}/other:{} "
"slow-writes={} memory-profile={} slow-regions=ram:{}/sram:{}/mmio:{}/other:{} "
"interval-fallbacks={} idle-polls={} ipc-armctrl={:#04x} "
"PC={:#010x} "
"CPSR={:#010x} irqflag={:#010x} irqmask={:#010x} fiqmask={:#010x}",
jit_executed, jit_executed == 0 ? 0.0 : 100.0 * jit_native / jit_executed,
m_core->GetJitFallbackInstructionCount(), m_core->GetJitCompiledBlockCount(),
m_core->GetJitAddressTranslationCount(), m_core->GetJitSlowReadCount(),
m_core->GetJitSlowWriteCount(), m_core->GetJitSlowRAMAccessCount(),
m_core->GetJitSlowSRAMAccessCount(), m_core->GetJitSlowMMIOAccessCount(),
m_core->GetJitSlowOtherAccessCount(), interval_fallbacks,
m_core->GetMemoryPollEntryCount(), ipc_arm_ctrl, m_core->GetRegister(15),
m_core->GetCPSR(), m_system.GetWiiIPC().ReadStarletRegister(0x38),
m_core->GetJitSlowWriteCount(), m_core->IsJitMemoryProfilingEnabled(),
m_core->GetJitSlowRAMAccessCount(), m_core->GetJitSlowSRAMAccessCount(),
m_core->GetJitSlowMMIOAccessCount(), m_core->GetJitSlowOtherAccessCount(),
interval_fallbacks, m_core->GetMemoryPollEntryCount(), ipc_arm_ctrl,
m_core->GetRegister(15), m_core->GetCPSR(),
m_system.GetWiiIPC().ReadStarletRegister(0x38),
m_system.GetWiiIPC().ReadStarletRegister(0x3c),
m_system.GetWiiIPC().ReadStarletRegister(0x40));
INFO_LOG_FMT(IOS,
@@ -322,50 +335,53 @@ void Starlet::RunSlice(s64 cycles_late)
m_core->GetJitFastEntryReplacementCount(),
m_core->GetJitInstructionCacheClearCount(), m_core->GetJitCodeSpaceClearCount(),
m_core->GetJitDiscardedBlockCount());
std::array<u32, 4> hot_sram_read_pages{};
std::array<u64, 4> hot_sram_read_page_counts{};
std::array<u32, 4> hot_sram_write_pages{};
std::array<u64, 4> hot_sram_write_page_counts{};
const auto rank_sram_page = [](u32 page, u64 count, auto& pages, auto& counts) {
for (size_t rank = 0; rank < counts.size(); ++rank)
{
if (count <= counts[rank])
continue;
for (size_t move = counts.size() - 1; move > rank; --move)
if (m_core->IsJitMemoryProfilingEnabled())
{
std::array<u32, 4> hot_sram_read_pages{};
std::array<u64, 4> hot_sram_read_page_counts{};
std::array<u32, 4> hot_sram_write_pages{};
std::array<u64, 4> hot_sram_write_page_counts{};
const auto rank_sram_page = [](u32 page, u64 count, auto& pages, auto& counts) {
for (size_t rank = 0; rank < counts.size(); ++rank)
{
counts[move] = counts[move - 1];
pages[move] = pages[move - 1];
if (count <= counts[rank])
continue;
for (size_t move = counts.size() - 1; move > rank; --move)
{
counts[move] = counts[move - 1];
pages[move] = pages[move - 1];
}
counts[rank] = count;
pages[rank] = page;
break;
}
counts[rank] = count;
pages[rank] = page;
break;
};
for (u32 page = 0; page < 32; ++page)
{
rank_sram_page(page, m_core->GetJitSlowSRAMPageReadAccessCount(page), hot_sram_read_pages,
hot_sram_read_page_counts);
rank_sram_page(page, m_core->GetJitSlowSRAMPageWriteAccessCount(page), hot_sram_write_pages,
hot_sram_write_page_counts);
}
INFO_LOG_FMT(
IOS,
"Starlet slow SRAM aliases=low:{}/high:{} reads:{}/writes:{} "
"hot-reads={:#04x}:{}, {:#04x}:{}, {:#04x}:{}, {:#04x}:{} "
"hot-writes={:#04x}:{}, {:#04x}:{}, {:#04x}:{}, {:#04x}:{}",
m_core->GetJitSlowSRAMLowAccessCount(), m_core->GetJitSlowSRAMHighAccessCount(),
m_core->GetJitSlowSRAMReadAccessCount(), m_core->GetJitSlowSRAMWriteAccessCount(),
hot_sram_read_pages[0], hot_sram_read_page_counts[0], hot_sram_read_pages[1],
hot_sram_read_page_counts[1], hot_sram_read_pages[2], hot_sram_read_page_counts[2],
hot_sram_read_pages[3], hot_sram_read_page_counts[3], hot_sram_write_pages[0],
hot_sram_write_page_counts[0], hot_sram_write_pages[1], hot_sram_write_page_counts[1],
hot_sram_write_pages[2], hot_sram_write_page_counts[2], hot_sram_write_pages[3],
hot_sram_write_page_counts[3]);
for (const auto& [key, samples] : m_core->TakeHotJitSlowMemorySamples(8))
{
const bool write = (key >> 32) != 0;
INFO_LOG_FMT(IOS, "Starlet slow memory {} address={:#010x} samples={}",
write ? "write" : "read", static_cast<u32>(key), samples);
}
};
for (u32 page = 0; page < 32; ++page)
{
rank_sram_page(page, m_core->GetJitSlowSRAMPageReadAccessCount(page), hot_sram_read_pages,
hot_sram_read_page_counts);
rank_sram_page(page, m_core->GetJitSlowSRAMPageWriteAccessCount(page), hot_sram_write_pages,
hot_sram_write_page_counts);
}
INFO_LOG_FMT(IOS,
"Starlet slow SRAM aliases=low:{}/high:{} reads:{}/writes:{} "
"hot-reads={:#04x}:{}, {:#04x}:{}, {:#04x}:{}, {:#04x}:{} "
"hot-writes={:#04x}:{}, {:#04x}:{}, {:#04x}:{}, {:#04x}:{}",
m_core->GetJitSlowSRAMLowAccessCount(), m_core->GetJitSlowSRAMHighAccessCount(),
m_core->GetJitSlowSRAMReadAccessCount(), m_core->GetJitSlowSRAMWriteAccessCount(),
hot_sram_read_pages[0], hot_sram_read_page_counts[0], hot_sram_read_pages[1],
hot_sram_read_page_counts[1], hot_sram_read_pages[2], hot_sram_read_page_counts[2],
hot_sram_read_pages[3], hot_sram_read_page_counts[3], hot_sram_write_pages[0],
hot_sram_write_page_counts[0], hot_sram_write_pages[1],
hot_sram_write_page_counts[1], hot_sram_write_pages[2],
hot_sram_write_page_counts[2], hot_sram_write_pages[3],
hot_sram_write_page_counts[3]);
for (const auto& [key, samples] : m_core->TakeHotJitSlowMemorySamples(8))
{
const bool write = (key >> 32) != 0;
INFO_LOG_FMT(IOS, "Starlet slow memory {} address={:#010x} samples={}",
write ? "write" : "read", static_cast<u32>(key), samples);
}
const ARMCore::HotPCSample current = m_core->GetCurrentPCSample();
INFO_LOG_FMT(IOS, "Starlet current PC {:#010x} {} instruction={:#010x}", current.address,
+219 -79
View File
@@ -4,6 +4,7 @@
#include "Core/IOS/Starlet/StarletMemory.h"
#include <algorithm>
#include <bitset>
#include <cstring>
#include <limits>
#include <string_view>
@@ -15,8 +16,10 @@
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Common/PerformanceTrace.h"
#include "Common/SDCardUtil.h"
#include "Common/StringUtil.h"
#include "Common/Swap.h"
#include "Core/Config/MainSettings.h"
#include "Core/HW/MMIO.h"
#include "Core/HW/Memmap.h"
@@ -873,6 +876,7 @@ std::optional<u32> StarletMemory::TryReadBroadwayResetInstruction(u32 address) c
void StarletMemory::AdvanceCycles(u64 cycles)
{
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::Devices);
const u32 old_timer = GetTimer();
m_arm_cycles += cycles;
const u32 new_timer = GetTimer();
@@ -909,6 +913,12 @@ void StarletMemory::AdvanceCycles(u64 cycles)
}
}
void StarletMemory::TraceNetwork() const
{
if (m_ethernet)
m_ethernet->TraceQueue();
}
bool StarletMemory::IsIdlePollAddress(u32 address, u32 size) const
{
if (size == 0 || address > std::numeric_limits<u32>::max() - (size - 1))
@@ -1007,21 +1017,11 @@ u32 StarletMemory::GetBootROMOffset(u32 address) const
u32 StarletMemory::GetSRAMOffset(u32 address) const
{
const u32 window_offset = address & (SRAM_WINDOW_SIZE - 1);
if (!m_sram_split_mode)
{
if (window_offset < SRAM_A_SIZE)
return window_offset;
if (window_offset < SRAM_A_SIZE + SRAM_B_SIZE)
return SRAM_A_SIZE + window_offset - SRAM_A_SIZE;
return INVALID_SRAM_OFFSET;
}
if (window_offset < SRAM_B_SIZE)
return SRAM_A_SIZE + window_offset;
if (window_offset >= SRAM_A_SIZE)
return window_offset - SRAM_A_SIZE;
return INVALID_SRAM_OFFSET;
// SM swaps the two 64 KiB halves of the CPU aperture. Bank B occupies only 32 KiB;
// its remaining half is a hole in either mapping, never a wrap into bank A.
static_assert(SRAM_SIZE == SRAM_A_SIZE + SRAM_B_SIZE);
const u32 offset = (address & (SRAM_WINDOW_SIZE - 1)) ^ (m_sram_split_mode ? SRAM_A_SIZE : 0);
return offset < SRAM_SIZE ? offset : INVALID_SRAM_OFFSET;
}
u8 StarletMemory::ReadMapped8(u32 address) const
@@ -1092,6 +1092,56 @@ void StarletMemory::WriteDMA8(u32 address, u8 value)
WriteMapped8(address, value);
}
u8* StarletMemory::GetDMAMemoryPointer(u32 address, size_t size) const
{
// Only coalesce a transfer wholly inside ordinary backing memory. In particular, DMA's
// low SRAM aperture uses physical banks, independent of SM and the CPU boot0 overlay.
// Subtraction after checking the starting address also prevents 32-bit range wraparound.
if (address >= SRAM_BASE && address < SRAM_BASE + SRAM_SIZE &&
size <= SRAM_BASE + SRAM_SIZE - address)
{
return const_cast<u8*>(m_sram.data()) + (address - SRAM_BASE);
}
if ((address < Memory::MEM1_SIZE_RETAIL && size <= Memory::MEM1_SIZE_RETAIL - address) ||
(address >= 0x10000000 && address < 0x10000000 + Memory::MEM2_SIZE_RETAIL &&
size <= 0x10000000 + Memory::MEM2_SIZE_RETAIL - address))
{
const auto memory = m_system.GetMemory().GetSpanForAddress(address);
if (size <= memory.size())
return memory.data();
}
return nullptr;
}
void StarletMemory::ReadDMABuffer(u32 address, std::span<u8> buffer) const
{
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::DMARead);
if (buffer.empty())
return;
if (const u8* memory = GetDMAMemoryPointer(address, buffer.size()))
{
// Callers use separate device staging buffers, never an overlapping guest-memory span.
std::memcpy(buffer.data(), memory, buffer.size());
return;
}
for (size_t i = 0; i < buffer.size(); ++i)
buffer[i] = ReadDMA8(address + static_cast<u32>(i));
}
void StarletMemory::WriteDMABuffer(u32 address, std::span<const u8> buffer)
{
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::DMAWrite);
if (buffer.empty())
return;
if (u8* memory = GetDMAMemoryPointer(address, buffer.size()))
{
std::memcpy(memory, buffer.data(), buffer.size());
return;
}
for (size_t i = 0; i < buffer.size(); ++i)
WriteDMA8(address + static_cast<u32>(i), buffer[i]);
}
void StarletMemory::ClearRegisterCache()
{
for (auto& entry : m_register_cache)
@@ -1633,17 +1683,17 @@ void StarletMemory::AdvanceOHCI(size_t controller, u64 cycles)
u32 StarletMemory::ReadOHCIMemory32(u32 address) const
{
return static_cast<u32>(ReadDMA8(address)) | static_cast<u32>(ReadDMA8(address + 1)) << 8 |
static_cast<u32>(ReadDMA8(address + 2)) << 16 |
static_cast<u32>(ReadDMA8(address + 3)) << 24;
std::array<u8, 4> bytes;
ReadDMABuffer(address, bytes);
return static_cast<u32>(bytes[0]) | static_cast<u32>(bytes[1]) << 8 |
static_cast<u32>(bytes[2]) << 16 | static_cast<u32>(bytes[3]) << 24;
}
void StarletMemory::WriteOHCIMemory32(u32 address, u32 value)
{
WriteDMA8(address, static_cast<u8>(value));
WriteDMA8(address + 1, static_cast<u8>(value >> 8));
WriteDMA8(address + 2, static_cast<u8>(value >> 16));
WriteDMA8(address + 3, static_cast<u8>(value >> 24));
const std::array<u8, 4> bytes = {static_cast<u8>(value), static_cast<u8>(value >> 8),
static_cast<u8>(value >> 16), static_cast<u8>(value >> 24)};
WriteDMABuffer(address, bytes);
}
std::vector<u8> StarletMemory::ReadOHCIBuffer(u32 current_buffer, u32 buffer_end) const
@@ -1661,11 +1711,9 @@ std::vector<u8> StarletMemory::ReadOHCIBuffer(u32 current_buffer, u32 buffer_end
return {};
std::vector<u8> buffer(first_size + second_size);
for (size_t i = 0; i < first_size; ++i)
buffer[i] = ReadDMA8(current_buffer + static_cast<u32>(i));
ReadDMABuffer(current_buffer, std::span(buffer).first(first_size));
const u32 second_page = buffer_end & ~0xfffU;
for (size_t i = 0; i < second_size; ++i)
buffer[first_size + i] = ReadDMA8(second_page + static_cast<u32>(i));
ReadDMABuffer(second_page, std::span(buffer).subspan(first_size));
return buffer;
}
@@ -1678,15 +1726,13 @@ void StarletMemory::WriteOHCIBuffer(u32 current_buffer, u32 buffer_end, const u8
const size_t first_capacity =
same_page ? buffer_end - current_buffer + 1 : 0x1000 - (current_buffer & 0xfff);
const size_t first_size = std::min(size, first_capacity);
for (size_t i = 0; i < first_size; ++i)
WriteDMA8(current_buffer + static_cast<u32>(i), data[i]);
WriteDMABuffer(current_buffer, std::span(data, first_size));
if (size > first_size)
{
const u32 second_page = buffer_end & ~0xfffU;
const size_t second_size =
std::min(size - first_size, static_cast<size_t>((buffer_end & 0xfff) + 1));
for (size_t i = 0; i < second_size; ++i)
WriteDMA8(second_page + static_cast<u32>(i), data[first_size + i]);
WriteDMABuffer(second_page, std::span(data + first_size, second_size));
}
}
@@ -3081,15 +3127,13 @@ bool StarletMemory::TransferSDCardBlocks(bool read, u32 argument, u32 block_size
{
if (!m_sd_card.ReadBytes(data.data(), data.size()))
return false;
for (size_t i = 0; i < data.size(); ++i)
WriteDMA8(dma_address + static_cast<u32>(i), data[i]);
WriteDMABuffer(dma_address, data);
}
else
{
if (!Config::Get(Config::MAIN_ALLOW_SD_WRITES))
return false;
for (size_t i = 0; i < data.size(); ++i)
data[i] = ReadDMA8(dma_address + static_cast<u32>(i));
ReadDMABuffer(dma_address, data);
if (!m_sd_card.WriteBytes(data.data(), data.size()))
return false;
}
@@ -3479,16 +3523,28 @@ u8 StarletMemory::Read8(u32 address)
u16 StarletMemory::Read16(u32 address)
{
// Aligned reads stay inside one SRAM/boot0 mapping segment. Resolve the live mapping once,
// including its holes, before considering unrelated memory and peripheral ranges.
if ((address & 1) == 0 && IsSRAMWindowAddress(address))
{
if (IsBootROMAddress(address))
return Common::swap16(m_boot_rom.data() + GetBootROMOffset(address));
const u32 offset = GetSRAMOffset(address);
return offset == INVALID_SRAM_OFFSET ? 0 : Common::swap16(m_sram.data() + offset);
}
if (IsMemoryAddress(address) && IsMemoryAddress(address + 1))
return m_system.GetMemory().Read_U16(address);
if (IsBootROMAddress(address) && IsBootROMAddress(address + 1))
{
const u32 offset = GetBootROMOffset(address);
return static_cast<u16>((static_cast<u16>(m_boot_rom[offset]) << 8) | m_boot_rom[offset + 1]);
if (GetBootROMOffset(address + 1) == offset + 1)
return static_cast<u16>((static_cast<u16>(m_boot_rom[offset]) << 8) | m_boot_rom[offset + 1]);
}
if (IsSRAMWindowAddress(address) && IsSRAMWindowAddress(address + 1))
if (!IsBootROMAddress(address) && !IsBootROMAddress(address + 1) &&
IsSRAMWindowAddress(address) && IsSRAMWindowAddress(address + 1))
{
const u32 offset = GetSRAMOffset(address);
const u32 end_offset = GetSRAMOffset(address + 1);
@@ -3515,6 +3571,18 @@ u32 StarletMemory::Read32(u32 address)
{
return m_system.GetWiiIPC().ReadStarletRegister(address - HW_BASE);
}
// Keep timer/IPC reads first: SRAM optimization must not add range tests to their hot path.
if (IsSRAMWindowAddress(address))
{
if (IsBootROMAddress(address))
return Common::swap32(m_boot_rom.data() + GetBootROMOffset(address));
const u32 offset = GetSRAMOffset(address);
return offset == INVALID_SRAM_OFFSET ? 0 : Common::swap32(m_sram.data() + offset);
}
// IOS probes HW_VERSION frequently. It is ordinary register backing, with no read side
// effects: retain its live byte values without decoding the device range four times.
if (address == HW_VERSION)
return ReadRegister(address);
}
if ((address & 3) == 0 && IsDIAddress(address))
@@ -3534,12 +3602,18 @@ u32 StarletMemory::Read32(u32 address)
if (IsBootROMAddress(address) && IsBootROMAddress(address + 3))
{
const u32 offset = GetBootROMOffset(address);
return (static_cast<u32>(m_boot_rom[offset]) << 24) |
(static_cast<u32>(m_boot_rom[offset + 1]) << 16) |
(static_cast<u32>(m_boot_rom[offset + 2]) << 8) | m_boot_rom[offset + 3];
// Unaligned reads can cross the 8 KiB ROM mirror or the boot0/SRAM overlay boundary.
// Such reads must resolve each byte; indexing beyond m_boot_rom would read unrelated data.
if (GetBootROMOffset(address + 3) == offset + 3)
{
return (static_cast<u32>(m_boot_rom[offset]) << 24) |
(static_cast<u32>(m_boot_rom[offset + 1]) << 16) |
(static_cast<u32>(m_boot_rom[offset + 2]) << 8) | m_boot_rom[offset + 3];
}
}
if (IsSRAMWindowAddress(address) && IsSRAMWindowAddress(address + 3))
if (!IsBootROMAddress(address) && !IsBootROMAddress(address + 3) &&
IsSRAMWindowAddress(address) && IsSRAMWindowAddress(address + 3))
{
const u32 offset = GetSRAMOffset(address);
const u32 end_offset = GetSRAMOffset(address + 3);
@@ -3769,6 +3843,19 @@ void StarletMemory::Write8(u32 address, u8 value)
void StarletMemory::Write16(u32 address, u16 value)
{
// An aligned scalar cannot cross an SRAM bank, aperture or boot0-overlay boundary.
// Keep this on the bus (including live mapping/protection), but resolve it only once.
if ((address & 1) == 0 && IsSRAMWindowAddress(address))
{
if (!IsBootROMAddress(address))
{
const u32 offset = GetSRAMOffset(address);
if (offset != INVALID_SRAM_OFFSET)
Common::WriteSwap16(m_sram.data() + offset, value);
}
return;
}
if (IsMemoryAddress(address) && IsMemoryAddress(address + 1))
{
m_system.GetMemory().Write_U16(value, address);
@@ -3793,6 +3880,20 @@ void StarletMemory::Write16(u32 address, u16 value)
void StarletMemory::Write32(u32 address, u32 value)
{
// SRAM is ordinary byte-addressed storage, not MMIO. Aligned words stay inside one
// mapping segment, so there is no need to decode every device or map all four bytes.
// Unaligned/boundary accesses below still use the original byte-exact bus path.
if ((address & 3) == 0 && IsSRAMWindowAddress(address))
{
if (!IsBootROMAddress(address))
{
const u32 offset = GetSRAMOffset(address);
if (offset != INVALID_SRAM_OFFSET)
Common::WriteSwap32(m_sram.data() + offset, value);
}
return;
}
// Match the aligned read fast path above. WriteRegister keeps the byte-addressable backing image
// coherent, while the exact device handlers retain W1C interrupt flags, reset side effects and
// the Starlet/Broadway scheduling boundary of the four-byte Write8 path.
@@ -4005,6 +4106,7 @@ bool StarletMemory::ReadRawNANDPage(u32 page, NANDPage* raw)
bool StarletMemory::CommitNANDPages(const NANDJournal::Pages& pages)
{
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::NAND);
if (m_nand_journal && !m_nand_journal->Append(pages))
return false;
for (const auto& [page, data] : pages)
@@ -4068,14 +4170,26 @@ bool StarletMemory::InstallEmulatedWiimotePairings()
const char* const end = std::find(entry.name, std::end(entry.name), '\0');
return std::string_view(entry.name, static_cast<size_t>(end - entry.name));
};
const auto find_child = [&superblock, &entry_name](u16 parent, std::string_view name) {
if (parent >= superblock->fst.size())
bool invalid_directory = false;
const auto find_child = [&superblock, &entry_name, &invalid_directory](u16 parent,
std::string_view name) {
if (parent >= superblock->fst.size() ||
(superblock->fst[parent].mode & 3) !=
static_cast<u8>(DiscIO::NANDImporter::Type::Directory))
{
invalid_directory = true;
return std::optional<u16>{};
}
std::bitset<DiscIO::NANDImporter::FSTEntryCount> visited;
u16 child = superblock->fst[parent].sub;
while (child != NAND_INVALID_ENTRY)
{
if (child >= superblock->fst.size())
if (child >= superblock->fst.size() || visited.test(child))
{
invalid_directory = true;
return std::optional<u16>{};
}
visited.set(child);
if (entry_name(superblock->fst[child]) == name)
return std::optional<u16>{child};
child = superblock->fst[child].sib;
@@ -4089,8 +4203,17 @@ bool StarletMemory::InstallEmulatedWiimotePairings()
entry = entry ? find_child(*entry, component) : std::nullopt;
if (!entry)
{
ERROR_LOG_FMT(IOS_WIIMOTE, "Raw NAND SFFS is missing /shared2/sys/SYSCONF");
return false;
if (invalid_directory)
{
ERROR_LOG_FMT(IOS_WIIMOTE, "Invalid raw NAND directory while looking for SYSCONF");
return false;
}
// Pairing injection is a convenience, not a prerequisite for boot0/BootMii.
// A freshly formatted NAND may not have SYSCONF yet. Leave it untouched:
// native software can create it, and controllers keep their default identities.
INFO_LOG_FMT(IOS_WIIMOTE, "Skipping Wii Remote auto-pairing: /shared2/sys/SYSCONF is absent; "
"continuing boot without modifying NAND");
return true;
}
}
@@ -4159,8 +4282,13 @@ bool StarletMemory::InstallEmulatedWiimotePairings()
return false;
const std::string_view name(reinterpret_cast<const char*>(sysconf.data() + *raw_offset + 1),
name_size);
if (name == "BT.DINF" && type == 1)
if (name == "BT.DINF")
{
if (type != 1)
{
ERROR_LOG_FMT(IOS_WIIMOTE, "Raw NAND SYSCONF has an invalid BT.DINF type");
return false;
}
const std::optional<u16> size_minus_one = read_be16(value_header);
if (!size_minus_one)
return false;
@@ -4170,8 +4298,15 @@ bool StarletMemory::InstallEmulatedWiimotePairings()
}
}
if (!pairing_offset)
{
INFO_LOG_FMT(IOS_WIIMOTE, "Skipping Wii Remote auto-pairing: SYSCONF has no BT.DINF entry; "
"continuing boot without modifying NAND");
return true;
}
const std::vector<u8> pairing = IOS::HLE::GetEmulatedWiimotePairingData();
if (!pairing_offset || !pairing_size || *pairing_size != pairing.size() ||
if (!pairing_size || *pairing_size != pairing.size() ||
*pairing_offset + *pairing_size > sysconf.size())
{
ERROR_LOG_FMT(IOS_WIIMOTE, "Raw NAND SYSCONF has no compatible BT.DINF section");
@@ -4223,8 +4358,8 @@ bool StarletMemory::InstallEmulatedWiimotePairings()
std::ranges::copy(pairing, sysconf.begin() + static_cast<ptrdiff_t>(*pairing_offset));
// Re-encrypt only the file's SFFS clusters and place their raw pages in the
// persistent overlay. Preserve all spare metadata from the dump and replace only
// the ECC for the modified ciphertext.
// persistent overlay. Preserve unrelated spare metadata and regenerate the
// plaintext HMAC copies and ciphertext ECC for each modified cluster.
size_t file_offset = 0;
NANDJournal::Pages pairing_pages;
for (size_t cluster_number = 0; cluster_number < cluster_indices.size(); ++cluster_number)
@@ -4274,10 +4409,20 @@ bool StarletMemory::InstallEmulatedWiimotePairings()
return false;
std::copy_n(encrypted_cluster.begin() + page_in_cluster * NAND_PAGE_DATA_SIZE,
NAND_PAGE_DATA_SIZE, raw.begin());
// SFFS keeps redundant HMACs at spare offset 0x0c in the seventh and
// eighth pages.
if (page_in_cluster >= 6)
std::ranges::copy(hmac, raw.begin() + NAND_PAGE_DATA_SIZE + 0x0c);
// Native SFFS stores a full HMAC at spare byte 1 in the seventh page.
// Its redundant copy spans that page's bytes 21-32 (first 12 bytes)
// and the eighth page's bytes 1-8 (last 8 bytes). Byte 0 is the bad-block
// marker. Writing two full copies at 0x0c leaves both native copies
// invalid, so IOS discards the updated SYSCONF and loses the pairings.
if (page_in_cluster == 6)
{
std::ranges::copy(hmac, raw.begin() + NAND_PAGE_DATA_SIZE + 1);
std::copy_n(hmac.begin(), 12, raw.begin() + NAND_PAGE_DATA_SIZE + 21);
}
else if (page_in_cluster == 7)
{
std::copy_n(hmac.begin() + 12, 8, raw.begin() + NAND_PAGE_DATA_SIZE + 1);
}
const auto ecc = CalculateNANDECC(raw.data());
std::ranges::copy(ecc, raw.begin() + NAND_PAGE_DATA_SIZE + 0x30);
pairing_pages.insert_or_assign(page, raw);
@@ -4293,6 +4438,7 @@ bool StarletMemory::InstallEmulatedWiimotePairings()
bool StarletMemory::ReadNANDPage(u32 command)
{
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::NANDReads);
if (!(command & NAND_CTRL_READ) || !m_nand_read_pending)
return false;
@@ -4306,21 +4452,18 @@ bool StarletMemory::ReadNANDPage(u32 command)
if (!ReadRawNANDPage(m_nand_read_page, &raw))
return false;
for (u32 i = 0; i < length; ++i)
{
const u32 raw_offset = m_nand_read_column + i;
u32 destination = data_address + i;
if (m_nand_read_column < NAND_PAGE_DATA_SIZE && raw_offset >= NAND_PAGE_DATA_SIZE)
destination = spare_address + raw_offset - NAND_PAGE_DATA_SIZE;
WriteDMA8(destination, raw[raw_offset]);
}
const u32 data_size = m_nand_read_column < NAND_PAGE_DATA_SIZE ?
std::min(length, NAND_PAGE_DATA_SIZE - m_nand_read_column) :
length;
const auto transfer = std::span(raw).subspan(m_nand_read_column, length);
WriteDMABuffer(data_address, transfer.first(data_size));
WriteDMABuffer(spare_address, transfer.subspan(data_size));
if (command & NAND_CTRL_ECC)
{
const auto ecc = CalculateNANDECC(raw.data());
const u32 calculated_ecc_address = spare_address ^ 0x40U;
for (u32 i = 0; i < ecc.size(); ++i)
WriteDMA8(calculated_ecc_address + i, ecc[i]);
WriteDMABuffer(calculated_ecc_address, ecc);
}
return true;
}
@@ -4383,21 +4526,17 @@ bool StarletMemory::StageNANDProgram(u32 command, bool random_data_input)
const u32 data_address = ReadRegister(NAND_DATA) & ~0xfU;
const u32 spare_address = ReadRegister(NAND_ECC) & ~0xfU;
for (u32 i = 0; i < length; ++i)
{
const u32 raw_offset = column + i;
u32 source = data_address + i;
if (column < NAND_PAGE_DATA_SIZE && raw_offset >= NAND_PAGE_DATA_SIZE)
source = spare_address + raw_offset - NAND_PAGE_DATA_SIZE;
m_nand_program_data[raw_offset] = ReadDMA8(source);
}
const u32 data_size =
column < NAND_PAGE_DATA_SIZE ? std::min(length, NAND_PAGE_DATA_SIZE - column) : length;
const auto transfer = std::span(m_nand_program_data).subspan(column, length);
ReadDMABuffer(data_address, transfer.first(data_size));
ReadDMABuffer(spare_address, transfer.subspan(data_size));
if ((command & NAND_CTRL_ECC) != 0 && column == 0 && length >= NAND_PAGE_DATA_SIZE)
{
const auto ecc = CalculateNANDECC(m_nand_program_data.data());
const u32 calculated_ecc_address = spare_address ^ 0x40U;
for (u32 i = 0; i < ecc.size(); ++i)
WriteDMA8(calculated_ecc_address + i, ecc[i]);
WriteDMABuffer(calculated_ecc_address, ecc);
m_nand_program_ecc_enabled = true;
}
return true;
@@ -4405,6 +4544,7 @@ bool StarletMemory::StageNANDProgram(u32 command, bool random_data_input)
bool StarletMemory::CommitNANDProgram()
{
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::NANDPrograms);
if (!m_nand_program_pending)
return false;
@@ -4452,6 +4592,7 @@ bool StarletMemory::StageNANDErase()
bool StarletMemory::CommitNANDErase()
{
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::NANDErases);
if (!m_nand_erase_pending)
return false;
@@ -4557,6 +4698,7 @@ void StarletMemory::HandleAESWrite(u32 address, u32 value)
void StarletMemory::ExecuteAESCommand(u32 command)
{
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::AES);
if (command == 0)
return;
if ((command & AES_CTRL_EXEC) == 0)
@@ -4567,8 +4709,7 @@ void StarletMemory::ExecuteAESCommand(u32 command)
const u32 destination = ReadRegister(AES_DEST) & ~0xfU;
std::vector<u8> input(size);
std::vector<u8> output(size);
for (size_t i = 0; i < size; ++i)
input[i] = ReadDMA8(source + static_cast<u32>(i));
ReadDMABuffer(source, input);
std::array<u8, 16> next_iv{};
bool succeeded = true;
@@ -4588,8 +4729,7 @@ void StarletMemory::ExecuteAESCommand(u32 command)
{
if ((command & AES_CTRL_ENABLE) != 0)
m_aes_iv = next_iv;
for (size_t i = 0; i < size; ++i)
WriteDMA8(destination + static_cast<u32>(i), output[i]);
WriteDMABuffer(destination, output);
WriteRegister(AES_SRC, source + static_cast<u32>(size));
WriteRegister(AES_DEST, destination + static_cast<u32>(size));
}
@@ -4667,6 +4807,7 @@ void StarletMemory::CompressSHA1(const u8* block)
void StarletMemory::ExecuteSHACommand(u32 command)
{
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::SHA);
if (!(command & (1U << 31)))
return;
@@ -4675,8 +4816,7 @@ void StarletMemory::ExecuteSHACommand(u32 command)
std::array<u8, 64> block{};
for (u32 i = 0; i < blocks; ++i)
{
for (u32 j = 0; j < block.size(); ++j)
block[j] = ReadDMA8(source + i * 64 + j);
ReadDMABuffer(source + i * 64, block);
CompressSHA1(block.data());
}
for (u32 i = 0; i < m_sha_state.size(); ++i)
@@ -11,6 +11,7 @@
#include <memory>
#include <mutex>
#include <optional>
#include <span>
#include <string>
#include <vector>
@@ -74,11 +75,16 @@ public:
const u8* GetDirectMemoryPointer(u32 address, u32 size) const override;
u64 GetCycles() const { return m_arm_cycles; }
void TraceNetwork() const;
std::optional<u32> TryReadBroadwayResetInstruction(u32 address) const;
void SetWiimoteSource(size_t index, WiimoteCommon::HIDWiimote* source);
bool EnableEthernetNAT(const std::string& runtime_directory, std::string* error);
private:
// Synthetic NAND fixtures exercise startup pairing without any console dumps or keys.
friend class StarletPairingTest;
friend class StarletDMATest;
static constexpr u32 NAND_PAGE_DATA_SIZE = 0x800;
static constexpr u32 NAND_PAGE_SPARE_SIZE = 0x40;
static constexpr u32 NAND_RAW_PAGE_SIZE = NAND_PAGE_DATA_SIZE + NAND_PAGE_SPARE_SIZE;
@@ -96,6 +102,9 @@ private:
void WriteMapped8(u32 address, u8 value);
u8 ReadDMA8(u32 address) const;
void WriteDMA8(u32 address, u8 value);
u8* GetDMAMemoryPointer(u32 address, size_t size) const;
void ReadDMABuffer(u32 address, std::span<u8> buffer) const;
void WriteDMABuffer(u32 address, std::span<const u8> buffer);
bool IsBootROMAddress(u32 address) const;
u32 GetBootROMOffset(u32 address) const;
u32 GetSRAMOffset(u32 address) const;
@@ -15,6 +15,7 @@
#include "Common/CommonTypes.h"
#include "Common/JitRegister.h"
#include "Common/PerformanceTrace.h"
#include "Core/Config/MainSettings.h"
#include "Core/Core.h"
#include "Core/Host.h"
@@ -189,6 +190,10 @@ void JitBaseBlockCache::FinalizeBlock(JitBlock& block, bool block_link,
LinkBlock(block);
}
Common::PerformanceTrace::CodeMap("PPC", this, block.near_begin, block.near_end,
block.effectiveAddress, block.physicalAddress, "block-near");
Common::PerformanceTrace::CodeMap("PPC", this, block.far_begin, block.far_end,
block.effectiveAddress, block.physicalAddress, "block-far");
const Common::Symbol* symbol = nullptr;
if (Common::JitRegister::IsEnabled() &&
(symbol = m_jit.m_ppc_symbol_db.GetSymbolFromAddr(block.effectiveAddress)) != nullptr)
@@ -497,6 +502,8 @@ void JitBaseBlockCache::UnlinkBlock(const JitBlock& block)
void JitBaseBlockCache::DestroyBlock(JitBlock& block)
{
Common::PerformanceTrace::CodeReset("PPC", this, block.near_begin, block.near_end);
Common::PerformanceTrace::CodeReset("PPC", this, block.far_begin, block.far_end);
if (m_entry_points_ptr)
{
if (m_entry_points_ptr[block.fast_block_map_index] == block.normalEntry)
@@ -9,6 +9,7 @@
#include <implot.h>
#include "Common/HookableEvent.h"
#include "Common/PerformanceTrace.h"
#include "Core/Config/GraphicsSettings.h"
#include "Core/Core.h"
#include "VideoCommon/VideoConfig.h"
@@ -24,6 +25,7 @@ PerformanceMetrics::PerformanceMetrics()
void PerformanceMetrics::Reset()
{
Common::PerformanceTrace::Marker("PerformanceMetricsReset");
m_fps_counter.Reset();
m_vps_counter.Reset();
@@ -38,11 +40,13 @@ void PerformanceMetrics::Reset()
void PerformanceMetrics::CountFrame()
{
Common::PerformanceTrace::Frame("produced");
m_fps_counter.Count();
}
void PerformanceMetrics::CountVBlank()
{
Common::PerformanceTrace::Frame("vblank");
m_vps_counter.Count();
}
@@ -62,6 +66,7 @@ void PerformanceMetrics::AdjustClockSpeed(s64 ticks, u32 new_ppc_clock, u32 old_
void PerformanceMetrics::CountPerformanceMarker(s64 core_ticks, u32 ticks_per_second)
{
Common::PerformanceTrace::Frame("guest-clock", core_ticks, ticks_per_second);
const auto clock_time = Clock::now();
const auto work_time = clock_time - m_time_sleeping;