IOS: checkpoint Starlet JIT, PPC cache and Wii timing fixes
Save the remaining ARM JIT and MMU/cache optimizations, accurate Starlet timer and Wiimote report cadence, opt-in PPC event tracing, and full texture hashing in the LLE launcher. Include regression coverage and exclude local profiling artifacts. Validation: 127 targeted tests from 15 suites passed, with one disabled test. Includes the current user-tested source state following the persistent NAND milestone.
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/HW/WII_IPC.h"
|
||||
@@ -171,6 +172,14 @@ public:
|
||||
return m_sram_fastmem_enabled ? &m_sram_split_mode : nullptr;
|
||||
}
|
||||
|
||||
const u8* GetDirectMemoryPointer(u32 address, u32 size) const override
|
||||
{
|
||||
const size_t offset = ToOffset(address);
|
||||
if (size == 0 || offset > m_memory.size() || size > m_memory.size() - offset)
|
||||
return nullptr;
|
||||
return m_memory.data() + offset;
|
||||
}
|
||||
|
||||
void SetIdlePollSafe(bool safe) { m_idle_poll_safe = safe; }
|
||||
void SetSliceStablePollAddress(u32 address)
|
||||
{
|
||||
@@ -376,7 +385,7 @@ TEST(StarletTimer, ZeroDelayAlarmMatchesImmediatelyAndUsesIRQW1C)
|
||||
memory.Write8(address + 3, static_cast<u8>(value));
|
||||
};
|
||||
|
||||
memory.AdvanceCycles(405);
|
||||
memory.AdvanceCycles(32 * 128);
|
||||
ASSERT_EQ(read_word(timer), 32u);
|
||||
write_word(alarm, read_word(timer));
|
||||
EXPECT_EQ(system.GetWiiIPC().ReadStarletRegister(0x38) & INT_CAUSE_TIMER, INT_CAUSE_TIMER);
|
||||
@@ -388,12 +397,155 @@ TEST(StarletTimer, ZeroDelayAlarmMatchesImmediatelyAndUsesIRQW1C)
|
||||
write_word(arm_irq_flag, INT_CAUSE_TIMER);
|
||||
EXPECT_EQ(system.GetWiiIPC().ReadStarletRegister(0x38) & INT_CAUSE_TIMER, 0u);
|
||||
|
||||
memory.AdvanceCycles(404);
|
||||
memory.AdvanceCycles(32 * 128 - 1);
|
||||
EXPECT_EQ(system.GetWiiIPC().ReadStarletRegister(0x38) & INT_CAUSE_TIMER, 0u);
|
||||
memory.AdvanceCycles(1);
|
||||
EXPECT_EQ(system.GetWiiIPC().ReadStarletRegister(0x38) & INT_CAUSE_TIMER, INT_CAUSE_TIMER);
|
||||
}
|
||||
|
||||
TEST(StarletRegisters, WideTimerAndInterruptAccessesMatchHardwareSemantics)
|
||||
{
|
||||
constexpr u32 hardware_base = 0x0d800000;
|
||||
constexpr u32 timer = hardware_base + 0x10;
|
||||
constexpr u32 arm_irq_flag = hardware_base + 0x38;
|
||||
constexpr u32 arm_irq_mask = hardware_base + 0x3c;
|
||||
Core::DeclareAsCPUThread();
|
||||
auto& system = Core::System::GetInstance();
|
||||
system.GetWiiIPC().Reset();
|
||||
StarletMemory memory(system);
|
||||
memory.Reset();
|
||||
|
||||
memory.AdvanceCycles(32 * 128);
|
||||
EXPECT_EQ(memory.Read32(timer), 32u);
|
||||
memory.Write32(timer, 64);
|
||||
EXPECT_EQ(memory.Read32(timer), 64u);
|
||||
|
||||
system.GetWiiIPC().SetStarletInterrupt(INT_CAUSE_TIMER, true);
|
||||
EXPECT_EQ(memory.Read32(arm_irq_flag) & INT_CAUSE_TIMER, INT_CAUSE_TIMER);
|
||||
memory.Write32(arm_irq_flag, INT_CAUSE_TIMER);
|
||||
EXPECT_EQ(memory.Read32(arm_irq_flag) & INT_CAUSE_TIMER, 0u);
|
||||
|
||||
memory.Write32(arm_irq_mask, 0x800619ef);
|
||||
EXPECT_EQ(memory.Read32(arm_irq_mask), 0x800619efu);
|
||||
}
|
||||
|
||||
TEST(StarletTimer, RunsAtOneTickPer128ARMCycles)
|
||||
{
|
||||
constexpr u32 timer = 0x0d800010;
|
||||
Core::DeclareAsCPUThread();
|
||||
auto& system = Core::System::GetInstance();
|
||||
system.GetWiiIPC().Reset();
|
||||
StarletMemory memory(system);
|
||||
memory.Reset();
|
||||
|
||||
memory.AdvanceCycles(127);
|
||||
EXPECT_EQ(memory.Read32(timer), 0u);
|
||||
memory.AdvanceCycles(1);
|
||||
EXPECT_EQ(memory.Read32(timer), 1u);
|
||||
memory.AdvanceCycles(243'000'000 - 128);
|
||||
EXPECT_EQ(memory.Read32(timer), 1'898'437u);
|
||||
memory.AdvanceCycles(243'000'000);
|
||||
EXPECT_EQ(memory.Read32(timer), 3'796'875u);
|
||||
}
|
||||
|
||||
TEST(StarletTimer, SchedulerSlicePartitionDoesNotChangeClock)
|
||||
{
|
||||
Core::DeclareAsCPUThread();
|
||||
auto& system = Core::System::GetInstance();
|
||||
StarletMemory memory(system);
|
||||
memory.Reset();
|
||||
|
||||
constexpr u64 total_cycles = 243'000'000;
|
||||
// Active, IPC, and idle scheduler slices must use the same clock, with no
|
||||
// fractional timer ticks lost at the end of a slice.
|
||||
for (const u64 slice : {256u, 4096u, 24300u})
|
||||
{
|
||||
memory.Reset();
|
||||
for (u64 elapsed = 0; elapsed < total_cycles;)
|
||||
{
|
||||
const u64 step = std::min(slice, total_cycles - elapsed);
|
||||
memory.AdvanceCycles(step);
|
||||
elapsed += step;
|
||||
}
|
||||
EXPECT_EQ(memory.Read32(0x0d800010), 1'898'437u) << "slice=" << slice;
|
||||
EXPECT_EQ(memory.GetCycles(), total_cycles);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StarletTimer, CounterWritesDoNotRewindPeripheralClock)
|
||||
{
|
||||
constexpr u32 timer = 0x0d800010;
|
||||
Core::DeclareAsCPUThread();
|
||||
auto& system = Core::System::GetInstance();
|
||||
StarletMemory memory(system);
|
||||
memory.Reset();
|
||||
memory.AdvanceCycles(1025);
|
||||
|
||||
for (const u32 value : {1u, 0xffffffffu, 0u, 0x12345678u})
|
||||
{
|
||||
memory.Write32(timer, value);
|
||||
EXPECT_EQ(memory.Read32(timer), value);
|
||||
EXPECT_EQ(memory.GetCycles(), 1025u);
|
||||
}
|
||||
// Reprogramming HW_TIMER leaves the free-running /128 clock phase intact.
|
||||
memory.AdvanceCycles(126);
|
||||
EXPECT_EQ(memory.Read32(timer), 0x12345678u);
|
||||
memory.AdvanceCycles(1);
|
||||
EXPECT_EQ(memory.Read32(timer), 0x12345679u);
|
||||
}
|
||||
|
||||
TEST(StarletTimer, ByteAssembledCounterWritesMatchWideWrites)
|
||||
{
|
||||
constexpr u32 timer = 0x0d800010;
|
||||
Core::DeclareAsCPUThread();
|
||||
auto& system = Core::System::GetInstance();
|
||||
StarletMemory memory(system);
|
||||
memory.Reset();
|
||||
memory.AdvanceCycles(1280);
|
||||
memory.Write8(timer, 0x12);
|
||||
memory.Write8(timer + 1, 0x34);
|
||||
memory.Write8(timer + 2, 0x56);
|
||||
memory.Write8(timer + 3, 0x78);
|
||||
EXPECT_EQ(memory.Read32(timer), 0x12345678u);
|
||||
EXPECT_EQ(memory.GetCycles(), 1280u);
|
||||
memory.AdvanceCycles(128);
|
||||
EXPECT_EQ(memory.Read32(timer), 0x12345679u);
|
||||
}
|
||||
|
||||
TEST(StarletTimer, AlarmFiresAcrossCounterWrap)
|
||||
{
|
||||
constexpr u32 timer = 0x0d800010;
|
||||
constexpr u32 alarm = 0x0d800014;
|
||||
Core::DeclareAsCPUThread();
|
||||
auto& system = Core::System::GetInstance();
|
||||
system.GetWiiIPC().Reset();
|
||||
StarletMemory memory(system);
|
||||
memory.Reset();
|
||||
memory.Write32(timer, 0xfffffffe);
|
||||
memory.Write32(alarm, 1);
|
||||
memory.AdvanceCycles(3 * 128 - 1);
|
||||
EXPECT_EQ(memory.Read32(timer), 0u);
|
||||
EXPECT_EQ(system.GetWiiIPC().ReadStarletRegister(0x38) & INT_CAUSE_TIMER, 0u);
|
||||
memory.AdvanceCycles(1);
|
||||
EXPECT_EQ(memory.Read32(timer), 1u);
|
||||
EXPECT_EQ(system.GetWiiIPC().ReadStarletRegister(0x38) & INT_CAUSE_TIMER, INT_CAUSE_TIMER);
|
||||
}
|
||||
|
||||
TEST(StarletTimer, ResetClearsCounterOffset)
|
||||
{
|
||||
Core::DeclareAsCPUThread();
|
||||
auto& system = Core::System::GetInstance();
|
||||
StarletMemory memory(system);
|
||||
memory.Reset();
|
||||
memory.AdvanceCycles(512);
|
||||
memory.Write32(0x0d800010, 0x12345678);
|
||||
memory.Reset();
|
||||
EXPECT_EQ(memory.GetCycles(), 0u);
|
||||
EXPECT_EQ(memory.Read32(0x0d800010), 0u);
|
||||
memory.AdvanceCycles(128);
|
||||
EXPECT_EQ(memory.Read32(0x0d800010), 1u);
|
||||
}
|
||||
|
||||
TEST(StarletNAND, HardwareResetPreservesProgrammedFlash)
|
||||
{
|
||||
Core::DeclareAsCPUThread();
|
||||
@@ -426,6 +578,36 @@ TEST(StarletNAND, HardwareResetPreservesProgrammedFlash)
|
||||
EXPECT_EQ(memory.Read32(sram), 0x12345678u);
|
||||
}
|
||||
|
||||
TEST(StarletTimer, StateRoundTripPreservesOffsetAndDividerPhase)
|
||||
{
|
||||
constexpr u32 timer = 0x0d800010;
|
||||
Core::DeclareAsCPUThread();
|
||||
auto& system = Core::System::GetInstance();
|
||||
StarletMemory memory(system);
|
||||
memory.Reset();
|
||||
memory.AdvanceCycles(1025);
|
||||
memory.Write32(timer, 0x12345678);
|
||||
|
||||
std::vector<u8> state_buffer(1024 * 1024);
|
||||
u8* state_pointer = state_buffer.data();
|
||||
PointerWrap writer(&state_pointer, state_buffer.size(), PointerWrap::Mode::Write);
|
||||
memory.DoState(writer);
|
||||
ASSERT_TRUE(writer.IsWriteMode());
|
||||
const size_t state_size = state_pointer - state_buffer.data();
|
||||
|
||||
memory.Reset();
|
||||
state_pointer = state_buffer.data();
|
||||
PointerWrap reader(&state_pointer, state_size, PointerWrap::Mode::Read);
|
||||
memory.DoState(reader);
|
||||
ASSERT_TRUE(reader.IsReadMode());
|
||||
EXPECT_EQ(memory.GetCycles(), 1025u);
|
||||
EXPECT_EQ(memory.Read32(timer), 0x12345678u);
|
||||
memory.AdvanceCycles(126);
|
||||
EXPECT_EQ(memory.Read32(timer), 0x12345678u);
|
||||
memory.AdvanceCycles(1);
|
||||
EXPECT_EQ(memory.Read32(timer), 0x12345679u);
|
||||
}
|
||||
|
||||
TEST(StarletGPIO, InterruptFlagIsWriteOneToClear)
|
||||
{
|
||||
constexpr u32 hardware_base = 0x0d800000;
|
||||
@@ -984,6 +1166,35 @@ TEST(StarletARMCore, JitCompilesDrainWriteBufferNatively)
|
||||
EXPECT_EQ(core.GetJitFallbackInstructionCount(), 1u);
|
||||
}
|
||||
|
||||
TEST(StarletARMCore, JitCompilesHotCP15MaintenanceNatively)
|
||||
{
|
||||
#if defined(_M_X86_64)
|
||||
TestBus interpreter_bus;
|
||||
TestBus jit_bus;
|
||||
ARMCore interpreter(interpreter_bus);
|
||||
ARMCore jit(jit_bus);
|
||||
jit.SetJitEnabled(true);
|
||||
const auto install_program = [](TestBus& bus) {
|
||||
bus.WriteARM(0x00, 0xee033f10); // mcr p15, 0, r3, c3, c0, 0 (DACR)
|
||||
bus.WriteARM(0x04, 0xee070f36); // mcr p15, 0, r0, c7, c6, 1
|
||||
bus.WriteARM(0x08, 0xee070f3a); // mcr p15, 0, r0, c7, c10, 1
|
||||
bus.WriteARM(0x0c, 0xeafffffe); // b .
|
||||
};
|
||||
install_program(interpreter_bus);
|
||||
install_program(jit_bus);
|
||||
interpreter.SetRegister(3, 0x55555555);
|
||||
jit.SetRegister(3, 0x55555555);
|
||||
|
||||
ASSERT_EQ(interpreter.RunCycles(4), 4u);
|
||||
ASSERT_EQ(jit.RunCycles(4), 4u);
|
||||
EXPECT_EQ(jit.GetCP15State().domain_access_control,
|
||||
interpreter.GetCP15State().domain_access_control);
|
||||
EXPECT_EQ(jit.GetRegister(15), interpreter.GetRegister(15));
|
||||
EXPECT_EQ(jit.GetJitFallbackInstructionCount(), 0u);
|
||||
EXPECT_EQ(jit.GetJitNativeExecutedInstructions(), 4u);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(StarletARMCore, JitDefersCP15CacheInvalidationUntilTheHostBlockReturns)
|
||||
{
|
||||
TestBus bus;
|
||||
@@ -1041,18 +1252,324 @@ TEST(StarletARMCore, JitPreservedBlocksUseCurrentTLBGenerationForFastmem)
|
||||
|
||||
EXPECT_EQ(core.RunCycles(2), 2u);
|
||||
ASSERT_EQ(core.GetRegister(1), 0x11223344u);
|
||||
ASSERT_EQ(core.GetJitFallbackInstructionCount(), 1u);
|
||||
ASSERT_EQ(core.GetJitFallbackInstructionCount(), 0u);
|
||||
ASSERT_EQ(core.GetJitCompiledBlockCount(), 1u);
|
||||
|
||||
core.SetRegister(1, 0);
|
||||
core.SetRegister(15, 0x80000000);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
EXPECT_EQ(core.GetRegister(1), 0x11223344u);
|
||||
EXPECT_EQ(core.GetJitFallbackInstructionCount(), 1u);
|
||||
EXPECT_EQ(core.GetJitFallbackInstructionCount(), 0u);
|
||||
EXPECT_EQ(core.GetJitCompiledBlockCount(), 1u);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(StarletARMCore, JitTLBRevalidationIsSharedByNativeBlocksOnTheSamePage)
|
||||
{
|
||||
#if defined(_M_X86_64)
|
||||
TestBus bus(0x10000);
|
||||
ARMCore core(bus);
|
||||
bus.WriteARM(0x0000, 0xe3a01001); // mov r1, #1
|
||||
bus.WriteARM(0x0020, 0xe3a02002); // mov r2, #2
|
||||
bus.WriteARM(0x0040, 0xee080f17); // invalidate unified TLB
|
||||
bus.WriteARM(0x6000, 0x00000c02); // VA 0x80000000 section -> PA 0
|
||||
core.GetCP15State().translation_table_base = 0x4000;
|
||||
core.GetCP15State().domain_access_control = 3;
|
||||
core.GetCP15State().control |= 1;
|
||||
core.SetJitEnabled(true);
|
||||
|
||||
core.SetRegister(15, 0x80000000);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
core.SetRegister(15, 0x80000020);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
core.SetRegister(15, 0x80000040);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
|
||||
const u64 dispatches_before_revalidation = core.GetJitDispatchSlowCount();
|
||||
core.SetRegister(15, 0x80000000);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
EXPECT_EQ(core.GetJitDispatchSlowCount(), dispatches_before_revalidation);
|
||||
|
||||
// The first block revalidated the unchanged page-table descriptor directly in generated code.
|
||||
// A second native block on that physical page must likewise avoid a page-table walk and C++
|
||||
// block-map lookup.
|
||||
core.SetRegister(15, 0x80000020);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
EXPECT_EQ(core.GetJitDispatchSlowCount(), dispatches_before_revalidation);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(StarletARMCore, JitSharedTLBRefillRejectsRemappedPhysicalCode)
|
||||
{
|
||||
#if defined(_M_X86_64)
|
||||
TestBus bus(0x110000);
|
||||
ARMCore core(bus);
|
||||
bus.WriteARM(0x000000, 0xe3a01001); // old page: mov r1, #1
|
||||
bus.WriteARM(0x000020, 0xe3a02002); // old page: mov r2, #2
|
||||
bus.WriteARM(0x000040, 0xee080f17); // invalidate unified TLB
|
||||
bus.WriteARM(0x100000, 0xe3a01003); // new page: mov r1, #3
|
||||
bus.WriteARM(0x100020, 0xe3a02004); // new page: mov r2, #4
|
||||
bus.WriteARM(0x006000, 0x00000c02); // VA 0x80000000 section -> PA 0
|
||||
core.GetCP15State().translation_table_base = 0x4000;
|
||||
core.GetCP15State().domain_access_control = 3;
|
||||
core.GetCP15State().control |= 1;
|
||||
core.SetJitEnabled(true);
|
||||
|
||||
core.SetRegister(15, 0x80000000);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
core.SetRegister(15, 0x80000020);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
const size_t old_block_count = core.GetJitCompiledBlockCount();
|
||||
|
||||
// Change the page table under the still-valid TLB, then execute the architectural invalidation
|
||||
// through the old mapping. The following dispatch must discover the new physical page.
|
||||
bus.WriteARM(0x006000, 0x00100c02);
|
||||
core.SetRegister(15, 0x80000040);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
core.SetRegister(15, 0x80000000);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
EXPECT_EQ(core.GetRegister(1), 3u);
|
||||
core.SetRegister(15, 0x80000020);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
EXPECT_EQ(core.GetRegister(2), 4u);
|
||||
EXPECT_EQ(core.GetJitCompiledBlockCount(), old_block_count + 3);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(StarletARMCore, JitRetainsPhysicalAliasesAcrossAddressSpaceSwitches)
|
||||
{
|
||||
#if defined(_M_X86_64)
|
||||
TestBus bus(0x110000);
|
||||
ARMCore core(bus);
|
||||
bus.WriteARM(0x000000, 0xe3a01001); // physical mapping 0: mov r1, #1
|
||||
bus.WriteARM(0x000020, 0xe3a02002); // physical mapping 0: mov r2, #2
|
||||
bus.WriteARM(0x000040, 0xee080f17); // invalidate unified TLB
|
||||
bus.WriteARM(0x100000, 0xe3a01003); // physical mapping 1: mov r1, #3
|
||||
bus.WriteARM(0x100020, 0xe3a02004); // physical mapping 1: mov r2, #4
|
||||
bus.WriteARM(0x100040, 0xee080f17); // invalidate unified TLB
|
||||
bus.WriteARM(0x006000, 0x00000c02); // VA 0x80000000 section -> PA 0
|
||||
core.GetCP15State().translation_table_base = 0x4000;
|
||||
core.GetCP15State().domain_access_control = 3;
|
||||
core.GetCP15State().control |= 1;
|
||||
core.SetJitEnabled(true);
|
||||
|
||||
for (const u32 address : {0x80000000U, 0x80000020U})
|
||||
{
|
||||
core.SetRegister(15, address);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
}
|
||||
|
||||
bus.WriteARM(0x006000, 0x00100c02); // Same virtual section -> PA 1 MiB.
|
||||
core.SetRegister(15, 0x80000040);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
for (const u32 address : {0x80000000U, 0x80000020U})
|
||||
{
|
||||
core.SetRegister(15, address);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
}
|
||||
|
||||
// Return to the first address space. The first block refills the shared page translation; the
|
||||
// second must immediately find its retained (MVA, physical page) entry instead of overwriting a
|
||||
// single virtual-key slot and falling back to C++ again.
|
||||
bus.WriteARM(0x006000, 0x00000c02);
|
||||
core.SetRegister(15, 0x80000040);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
const u64 dispatches_before_refill = core.GetJitDispatchSlowCount();
|
||||
core.SetRegister(15, 0x80000000);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
EXPECT_EQ(core.GetRegister(1), 1u);
|
||||
core.SetRegister(15, 0x80000020);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
EXPECT_EQ(core.GetRegister(2), 2u);
|
||||
EXPECT_EQ(core.GetJitDispatchSlowCount(), dispatches_before_refill + 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(StarletARMCore, JitFastBlockCacheRetainsFourCollidingHotBlocks)
|
||||
{
|
||||
#if defined(_M_X86_64)
|
||||
TestBus bus(0xd0000);
|
||||
ARMCore core(bus);
|
||||
core.SetJitEnabled(true);
|
||||
|
||||
// These ARM addresses deliberately have the same upper 16 bits after multiplying by the JIT
|
||||
// cache's 0x9e3779b1 hash constant. They therefore occupy the four ways of one cache set.
|
||||
constexpr std::array<u32, 5> addresses = {0x000014, 0x04cb94, 0x07e168, 0x099714, 0x0cace8};
|
||||
for (const u32 address : addresses)
|
||||
bus.WriteARM(address, 0xe3a01001); // mov r1, #1
|
||||
|
||||
for (size_t i = 0; i < 4; ++i)
|
||||
{
|
||||
const u32 address = addresses[i];
|
||||
core.SetRegister(15, address);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
}
|
||||
|
||||
const u64 dispatches_after_fill = core.GetJitDispatchSlowCount();
|
||||
const u64 collisions_after_fill = core.GetJitDispatchCollisionCount();
|
||||
for (size_t i = 0; i < 4; ++i)
|
||||
{
|
||||
const u32 address = addresses[i];
|
||||
core.SetRegister(15, address);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
}
|
||||
EXPECT_EQ(core.GetJitDispatchSlowCount(), dispatches_after_fill);
|
||||
EXPECT_EQ(core.GetJitDispatchCollisionCount(), collisions_after_fill);
|
||||
|
||||
// A fifth distinct key proves that the set is actually full and exercises bounded replacement.
|
||||
core.SetRegister(15, addresses.back());
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
EXPECT_EQ(core.GetJitDispatchSlowCount(), dispatches_after_fill + 1);
|
||||
EXPECT_EQ(core.GetJitDispatchCollisionCount(), collisions_after_fill + 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(StarletARMCore, JitCachesFallbackOnlyBlocks)
|
||||
{
|
||||
#if defined(_M_X86_64)
|
||||
TestBus bus(0x10000);
|
||||
ARMCore core(bus);
|
||||
// MUL uses the exact interpreter helper in this JIT. A block beginning with it therefore has
|
||||
// zero directly emitted ARM instructions, but its generated fallback wrapper is still reusable.
|
||||
bus.WriteARM(0x0000, 0xe0010190); // mul r1, r0, r1
|
||||
core.SetJitEnabled(true);
|
||||
|
||||
core.SetRegister(0, 3);
|
||||
core.SetRegister(1, 4);
|
||||
core.SetRegister(15, 0);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
EXPECT_EQ(core.GetRegister(1), 12u);
|
||||
const u64 dispatches_after_compile = core.GetJitDispatchSlowCount();
|
||||
const u64 fallbacks_after_compile = core.GetJitFallbackInstructionCount();
|
||||
|
||||
core.SetRegister(1, 5);
|
||||
core.SetRegister(15, 0);
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
EXPECT_EQ(core.GetRegister(1), 15u);
|
||||
EXPECT_EQ(core.GetJitFallbackInstructionCount(), fallbacks_after_compile + 1);
|
||||
EXPECT_EQ(core.GetJitDispatchSlowCount(), dispatches_after_compile);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(StarletARMCore, ARMJitCompilesLogicalImmediateAndShiftCarry)
|
||||
{
|
||||
#if defined(_M_X86_64)
|
||||
TestBus interpreter_bus(0x1000);
|
||||
TestBus jit_bus(0x1000);
|
||||
ARMCore interpreter(interpreter_bus);
|
||||
ARMCore jit(jit_bus);
|
||||
jit.SetJitEnabled(true);
|
||||
const auto install_program = [](TestBus& bus) {
|
||||
bus.WriteARM(0x00, 0xe3180701); // tst r8, #0x40000; rotated immediate supplies C
|
||||
bus.WriteARM(0x04, 0xeafffffe); // b .
|
||||
bus.WriteARM(0x20, 0xe1b02820); // movs r2, r0, lsr #16; bit 15 supplies C
|
||||
bus.WriteARM(0x24, 0xeafffffe); // b .
|
||||
};
|
||||
install_program(interpreter_bus);
|
||||
install_program(jit_bus);
|
||||
|
||||
for (ARMCore* core : {&interpreter, &jit})
|
||||
{
|
||||
core->SetCPSR(static_cast<u32>(ARMCore::Mode::System) | ARMCore::CPSR_C | ARMCore::CPSR_V);
|
||||
core->SetRegister(8, 0x40000);
|
||||
core->SetRegister(15, 0);
|
||||
}
|
||||
ASSERT_EQ(interpreter.RunCycles(2), 2u);
|
||||
ASSERT_EQ(jit.RunCycles(2), 2u);
|
||||
EXPECT_EQ(jit.GetCPSR(), interpreter.GetCPSR());
|
||||
|
||||
for (ARMCore* core : {&interpreter, &jit})
|
||||
{
|
||||
core->SetCPSR(static_cast<u32>(ARMCore::Mode::System) | ARMCore::CPSR_V);
|
||||
core->SetRegister(0, 0x80018000);
|
||||
core->SetRegister(2, 0);
|
||||
core->SetRegister(15, 0x20);
|
||||
}
|
||||
ASSERT_EQ(interpreter.RunCycles(2), 2u);
|
||||
ASSERT_EQ(jit.RunCycles(2), 2u);
|
||||
EXPECT_EQ(jit.GetRegister(2), interpreter.GetRegister(2));
|
||||
EXPECT_EQ(jit.GetCPSR(), interpreter.GetCPSR());
|
||||
EXPECT_EQ(jit.GetJitFallbackInstructionCount(), 0u);
|
||||
EXPECT_EQ(jit.GetJitNativeExecutedInstructions(), 4u);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(StarletARMCore, ARMJitCompilesIRQVectorLoadPCWithInterworking)
|
||||
{
|
||||
#if defined(_M_X86_64)
|
||||
TestBus bus;
|
||||
ARMCore core(bus);
|
||||
bus.SetFastmemEnabled(true);
|
||||
bus.WriteARM(0x00, 0xe59ff018); // ldr pc, [pc, #0x18] -> 0x20
|
||||
bus.WriteARM(0x20, 0x00000101); // enter Thumb at 0x100
|
||||
core.SetRegister(15, 0);
|
||||
core.SetJitEnabled(true);
|
||||
|
||||
EXPECT_EQ(core.RunCycles(1), 1u);
|
||||
EXPECT_EQ(core.GetRegister(15), 0x100u);
|
||||
EXPECT_NE(core.GetCPSR() & ARMCore::CPSR_T, 0u);
|
||||
EXPECT_EQ(core.GetJitFallbackInstructionCount(), 0u);
|
||||
EXPECT_EQ(core.GetJitSlowReadCount(), 0u);
|
||||
EXPECT_EQ(core.GetJitNativeExecutedInstructions(), 1u);
|
||||
EXPECT_TRUE(bus.SRAMCanariesIntact());
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(StarletARMCore, ARMJitCompilesLongMultiplyFamily)
|
||||
{
|
||||
#if defined(_M_X86_64)
|
||||
struct Case
|
||||
{
|
||||
u32 instruction;
|
||||
u32 cpsr;
|
||||
u32 rm;
|
||||
u32 rs;
|
||||
u32 rd_hi;
|
||||
u32 rd_lo;
|
||||
};
|
||||
constexpr std::array cases = {
|
||||
Case{0xe0834291, static_cast<u32>(ARMCore::Mode::System) | ARMCore::CPSR_C, 0x10000, 0x10001,
|
||||
0, 0}, // UMULL
|
||||
Case{0xe0c34291, static_cast<u32>(ARMCore::Mode::System) | ARMCore::CPSR_V, 0xfffffff0, 0x10,
|
||||
0, 0}, // SMULL
|
||||
Case{0xe0b34291, static_cast<u32>(ARMCore::Mode::System) | ARMCore::CPSR_C | ARMCore::CPSR_V,
|
||||
0xffffffff, 1, 0, 1}, // UMLALS, result wraps to zero and preserves CV
|
||||
Case{0x10834291, static_cast<u32>(ARMCore::Mode::System) | ARMCore::CPSR_Z, 7, 9, 0x11223344,
|
||||
0x55667788}, // UMULLNE, predicate fails
|
||||
};
|
||||
|
||||
for (const Case& test : cases)
|
||||
{
|
||||
TestBus interpreter_bus;
|
||||
TestBus jit_bus;
|
||||
ARMCore interpreter(interpreter_bus);
|
||||
ARMCore jit(jit_bus);
|
||||
interpreter_bus.WriteARM(0, test.instruction);
|
||||
interpreter_bus.WriteARM(4, 0xeafffffe); // b .
|
||||
jit_bus.WriteARM(0, test.instruction);
|
||||
jit_bus.WriteARM(4, 0xeafffffe); // b .
|
||||
for (ARMCore* core : {&interpreter, &jit})
|
||||
{
|
||||
core->SetCPSR(test.cpsr);
|
||||
core->SetRegister(1, test.rm);
|
||||
core->SetRegister(2, test.rs);
|
||||
core->SetRegister(3, test.rd_hi);
|
||||
core->SetRegister(4, test.rd_lo);
|
||||
core->SetRegister(15, 0);
|
||||
}
|
||||
jit.SetJitEnabled(true);
|
||||
|
||||
ASSERT_EQ(interpreter.RunCycles(2), 2u);
|
||||
ASSERT_EQ(jit.RunCycles(2), 2u);
|
||||
EXPECT_EQ(jit.GetRegister(3), interpreter.GetRegister(3));
|
||||
EXPECT_EQ(jit.GetRegister(4), interpreter.GetRegister(4));
|
||||
EXPECT_EQ(jit.GetCPSR(), interpreter.GetCPSR());
|
||||
EXPECT_EQ(jit.GetJitFallbackInstructionCount(), 0u);
|
||||
EXPECT_EQ(jit.GetJitNativeExecutedInstructions(), 2u);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(StarletARMCore, FCSESwitchPreservesTaggedTLBTranslations)
|
||||
{
|
||||
TestBus bus(0x10000);
|
||||
|
||||
Reference in New Issue
Block a user