IOS: optimize Starlet LLE and accurate CPU cache

Retain ARM926 FCSE translations, accelerate common Starlet JIT blocks and cache sparse Hollywood register reads. Inline the hot Broadway write-back cache path while preserving exact cache miss, locking and split-access behavior.
This commit is contained in:
2026-08-30 02:35:19 +02:00
parent 9389e38f75
commit 1bd3fac1f1
12 changed files with 968 additions and 223 deletions
+302 -25
View File
@@ -83,8 +83,7 @@ public:
if (offset + 3 >= SRAM_SIZE)
return 0;
const u8* const sram = SRAMData();
return (static_cast<u32>(sram[offset]) << 24) |
(static_cast<u32>(sram[offset + 1]) << 16) |
return (static_cast<u32>(sram[offset]) << 24) | (static_cast<u32>(sram[offset + 1]) << 16) |
(static_cast<u32>(sram[offset + 2]) << 8) | sram[offset + 3];
}
const size_t offset = ToOffset(address);
@@ -197,8 +196,7 @@ public:
{
EXPECT_LT(offset + 3, SRAM_SIZE);
const u8* const sram = SRAMData();
return (static_cast<u32>(sram[offset]) << 24) |
(static_cast<u32>(sram[offset + 1]) << 16) |
return (static_cast<u32>(sram[offset]) << 24) | (static_cast<u32>(sram[offset + 1]) << 16) |
(static_cast<u32>(sram[offset + 2]) << 8) | sram[offset + 3];
}
@@ -288,6 +286,73 @@ TEST(WiiIPCCtrlRegister, ProducerBitsRemainLatchedUntilPeerAcknowledges)
EXPECT_EQ(control.ppc() & 0x06, 0x00);
}
TEST(StarletSRAM, WideAccessesPreserveAliasesSplitMappingAndBoot0Protection)
{
constexpr u32 sram_low = StarletMemory::SRAM_BASE;
constexpr u32 sram_high = StarletMemory::SRAM_MIRROR_BASE;
constexpr u32 hardware_srnprot = 0x0d800060;
constexpr u32 sram_split_mode = 1U << 5;
Core::DeclareAsCPUThread();
auto& system = Core::System::GetInstance();
system.GetWiiIPC().Reset();
StarletMemory memory(system);
memory.Reset();
memory.Write32(sram_low + 0x20, 0x11223344);
EXPECT_EQ(memory.Read32(sram_high + 0x20), 0x11223344u);
memory.Write16(sram_high + 0x24, 0xa1b2);
EXPECT_EQ(memory.Read16(sram_low + 0x24), 0xa1b2u);
memory.Write8(sram_low + 0x26, 0x5a);
EXPECT_EQ(memory.Read8(sram_high + 0x26), 0x5au);
// With the reset (non-split) mapping, boot0 overlays the upper half of the top aperture.
const u32 boot0_word = memory.Read32(0xffff0020);
memory.Write32(0xffff0020, 0xdeadbeef);
EXPECT_EQ(memory.Read32(0xffff0020), boot0_word);
memory.Write32(hardware_srnprot, sram_split_mode);
memory.Write32(sram_low + 0x20, 0x55667788); // Logical page zero now selects SRAM B.
EXPECT_EQ(memory.Read32(sram_high + 0x20), 0x55667788u);
EXPECT_EQ(memory.Read32(0xffff0020), 0x11223344u); // Upper aperture selects SRAM A.
memory.Write32(0xffff0020, 0xaabbccdd);
// The split gap remains unmapped and ignores writes.
memory.Write32(sram_high + 0x8000, 0xcafebabe);
EXPECT_EQ(memory.Read32(sram_high + 0x8000), 0u);
memory.Write32(hardware_srnprot, 0);
EXPECT_EQ(memory.Read32(sram_low + 0x20), 0xaabbccddu);
}
TEST(StarletRegisters, CachePreservesSparseValuesAndCollisions)
{
constexpr u32 address_a = 0x0d900100;
constexpr u32 address_b = address_a ^ 0x1001;
constexpr auto cache_index = [](u32 address) { return (address ^ (address >> 12)) & 0xfff; };
static_assert(cache_index(address_a) == cache_index(address_b));
Core::DeclareAsCPUThread();
auto& system = Core::System::GetInstance();
system.GetWiiIPC().Reset();
StarletMemory memory(system);
memory.Reset();
EXPECT_EQ(memory.Read8(address_a), 0u);
EXPECT_EQ(memory.Read8(address_b), 0u);
memory.Write8(address_a, 0x12);
EXPECT_EQ(memory.Read8(address_a), 0x12u);
memory.Write8(address_b, 0x34);
EXPECT_EQ(memory.Read8(address_b), 0x34u);
EXPECT_EQ(memory.Read8(address_a), 0x12u);
memory.Write8(address_a, 0x56);
EXPECT_EQ(memory.Read8(address_a), 0x56u);
EXPECT_EQ(memory.Read8(address_b), 0x34u);
memory.Reset();
EXPECT_EQ(memory.Read8(address_a), 0u);
EXPECT_EQ(memory.Read8(address_b), 0u);
}
TEST(StarletTimer, ZeroDelayAlarmMatchesImmediatelyAndUsesIRQW1C)
{
constexpr u32 hardware_base = 0x0d800000;
@@ -956,6 +1021,133 @@ TEST(StarletARMCore, JitPreservedBlocksUseCurrentTLBGenerationForFastmem)
#endif
}
TEST(StarletARMCore, FCSESwitchPreservesTaggedTLBTranslations)
{
TestBus bus(0x10000);
ARMCore core(bus);
bus.WriteARM(0x0000, 0xe5921000); // ldr r1, [r2]
bus.WriteARM(0x0004, 0xe3a00402); // mov r0, #0x02000000
bus.WriteARM(0x0008, 0xee0d0f10); // mcr p15, 0, r0, c13, c0, 0
bus.WriteARM(0x000c, 0xe5923000); // ldr r3, [r2]
bus.WriteARM(0x0010, 0xe3a00000); // mov r0, #0
bus.WriteARM(0x0014, 0xee0d0f10); // mcr p15, 0, r0, c13, c0, 0
bus.WriteARM(0x0018, 0xe5924000); // ldr r4, [r2]
bus.WriteARM(0x0100, 0x12345678);
bus.WriteARM(0x4000, 0x00000c02); // FCSE PID 0 low section -> PA 0
bus.WriteARM(0x4080, 0x00000c02); // FCSE PID 1 low section -> PA 0
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.SetRegister(2, 0x100);
core.SetRegister(15, 0x80000000);
bus.ResetReadCounts();
EXPECT_EQ(core.RunCycles(7), 7u);
EXPECT_EQ(core.GetRegister(1), 0x12345678u);
EXPECT_EQ(core.GetRegister(3), 0x12345678u);
EXPECT_EQ(core.GetRegister(4), 0x12345678u);
// Seven instruction fetches, three data reads, and exactly three first-level walks: kernel,
// PID 0 and PID 1. Switching back to PID 0 must reuse its MVA-tagged TLB entry.
EXPECT_EQ(bus.GetRead32Count(), 13u);
}
TEST(StarletARMCore, JitFastCacheSeparatesFCSEProcesses)
{
#if defined(_M_X86_64)
TestBus bus(0x204000);
ARMCore core(bus);
bus.WriteARM(0x000000, 0xe3a01001); // PID 0: mov r1, #1
bus.WriteARM(0x000004, 0xea00003d); // b 0x100
bus.WriteARM(0x100000, 0xe3a01002); // PID 1: mov r1, #2
bus.WriteARM(0x100004, 0xea00003d); // b 0x100
bus.WriteARM(0x200000, 0x00000c02); // FCSE PID 0 section -> PA 0
bus.WriteARM(0x200080, 0x00100c02); // FCSE PID 1 section -> PA 0x00100000
core.GetCP15State().translation_table_base = 0x200000;
core.GetCP15State().domain_access_control = 3;
core.GetCP15State().control |= 1;
core.SetJitEnabled(true);
core.SetRegister(15, 0);
EXPECT_EQ(core.RunCycles(2), 2u);
EXPECT_EQ(core.GetRegister(1), 1u);
core.GetCP15State().process_id = 0x02000000;
core.SetRegister(15, 0);
EXPECT_EQ(core.RunCycles(2), 2u);
EXPECT_EQ(core.GetRegister(1), 2u);
EXPECT_EQ(core.GetJitCompiledBlockCount(), 2u);
// Both the software TLB and native fast-entry cache retain distinct MVA-tagged entries. Returning
// to PID 0 therefore needs neither a page-table read nor another block compilation.
bus.ResetReadCounts();
core.GetCP15State().process_id = 0;
core.SetRegister(15, 0);
EXPECT_EQ(core.RunCycles(2), 2u);
EXPECT_EQ(core.GetRegister(1), 1u);
EXPECT_EQ(bus.GetRead32Count(), 0u);
EXPECT_EQ(core.GetJitCompiledBlockCount(), 2u);
#endif
}
TEST(StarletARMCore, ARMJitCompilesConditionalBranchExchange)
{
#if defined(_M_X86_64)
TestBus bus(0x100);
ARMCore core(bus);
bus.WriteARM(0x00, 0x012fff1e); // bxeq lr
core.SetJitEnabled(true);
core.SetRegister(14, 0x20);
core.SetCPSR(core.GetCPSR() | ARMCore::CPSR_Z);
EXPECT_EQ(core.RunCycles(1), 1u);
EXPECT_EQ(core.GetRegister(15), 0x20u);
core.SetRegister(15, 0);
core.SetCPSR(core.GetCPSR() & ~ARMCore::CPSR_Z);
EXPECT_EQ(core.RunCycles(1), 1u);
EXPECT_EQ(core.GetRegister(15), 4u);
EXPECT_EQ(core.GetJitFallbackInstructionCount(), 0u);
EXPECT_EQ(core.GetJitNativeExecutedInstructions(), 2u);
#endif
}
TEST(StarletARMCore, ARMJitCompilesConditionalSingleDataTransfers)
{
#if defined(_M_X86_64)
TestBus bus(0x200);
ARMCore core(bus);
bus.SetFastmemEnabled(true);
bus.WriteARM(0x00, 0x05921000); // ldreq r1, [r2]
bus.WriteARM(0x04, 0xea00003d); // b 0x100
bus.WriteARM(0x20, 0x15823004); // strne r3, [r2, #4]
bus.WriteARM(0x24, 0xea000035); // b 0x100
bus.WriteARM(0x100, 0x11223344);
bus.WriteARM(0x104, 0xaabbccdd);
core.SetJitEnabled(true);
core.SetRegister(2, 0x100);
core.SetRegister(3, 0x55667788);
core.SetCPSR(core.GetCPSR() | ARMCore::CPSR_Z);
EXPECT_EQ(core.RunCycles(2), 2u);
EXPECT_EQ(core.GetRegister(1), 0x11223344u);
core.SetRegister(15, 0x20);
EXPECT_EQ(core.RunCycles(2), 2u);
EXPECT_EQ(bus[0x104], 0xaau);
core.SetRegister(15, 0x20);
core.SetCPSR(core.GetCPSR() & ~ARMCore::CPSR_Z);
EXPECT_EQ(core.RunCycles(2), 2u);
EXPECT_EQ(bus[0x104], 0x55u);
EXPECT_EQ(bus[0x105], 0x66u);
EXPECT_EQ(bus[0x106], 0x77u);
EXPECT_EQ(bus[0x107], 0x88u);
EXPECT_EQ(core.GetJitFallbackInstructionCount(), 0u);
EXPECT_EQ(core.GetJitNativeExecutedInstructions(), 6u);
#endif
}
TEST(StarletARMCore, WaitForInterruptFastForwardsAndWakesOnMaskedIRQ)
{
TestBus bus;
@@ -1223,7 +1415,7 @@ TEST(StarletARMCore, ARMJitConditionalBranchesMatchInterpreter)
{
const u32 address = condition * 0x20;
const u32 branch = (condition << 28) | 0x0a000000;
interpreter_bus.WriteARM(address, branch); // b<cond> address + 8
interpreter_bus.WriteARM(address, branch); // b<cond> address + 8
jit_bus.WriteARM(address, branch);
interpreter_bus.WriteARM(address + 4, 0xe3a00001); // mov r0, #1
jit_bus.WriteARM(address + 4, 0xe3a00001);
@@ -1233,10 +1425,8 @@ TEST(StarletARMCore, ARMJitConditionalBranchesMatchInterpreter)
for (u32 flags = 0; flags < 16; ++flags)
{
const u32 cpsr = static_cast<u32>(ARMCore::Mode::Supervisor) |
((flags & 1) ? ARMCore::CPSR_N : 0) |
((flags & 2) ? ARMCore::CPSR_Z : 0) |
((flags & 4) ? ARMCore::CPSR_C : 0) |
((flags & 8) ? ARMCore::CPSR_V : 0);
((flags & 1) ? ARMCore::CPSR_N : 0) | ((flags & 2) ? ARMCore::CPSR_Z : 0) |
((flags & 4) ? ARMCore::CPSR_C : 0) | ((flags & 8) ? ARMCore::CPSR_V : 0);
interpreter.SetCPSR(cpsr);
jit.SetCPSR(cpsr);
interpreter.SetRegister(15, address);
@@ -1272,7 +1462,7 @@ TEST(StarletARMCore, ARMJitCompilesPredicatedDataProcessing)
bus.WriteARM(address + 0x04, (condition << 28) | 0x03530002); // cmp<cond> r3, #2
bus.WriteARM(address + 0x08, (condition << 28) | 0x03c44018); // bic<cond> r4, r4, #0x18
bus.WriteARM(address + 0x0c, (condition << 28) | 0x03a05007); // mov<cond> r5, #7
bus.WriteARM(address + 0x10, 0xea000000); // b address + 0x18
bus.WriteARM(address + 0x10, 0xea000000); // b address + 0x18
};
install_program(interpreter_bus);
install_program(jit_bus);
@@ -1280,10 +1470,8 @@ TEST(StarletARMCore, ARMJitCompilesPredicatedDataProcessing)
for (u32 flags = 0; flags < 16; ++flags)
{
const u32 cpsr = static_cast<u32>(ARMCore::Mode::Supervisor) |
((flags & 1) ? ARMCore::CPSR_N : 0) |
((flags & 2) ? ARMCore::CPSR_Z : 0) |
((flags & 4) ? ARMCore::CPSR_C : 0) |
((flags & 8) ? ARMCore::CPSR_V : 0);
((flags & 1) ? ARMCore::CPSR_N : 0) | ((flags & 2) ? ARMCore::CPSR_Z : 0) |
((flags & 4) ? ARMCore::CPSR_C : 0) | ((flags & 8) ? ARMCore::CPSR_V : 0);
interpreter.SetCPSR(cpsr);
jit.SetCPSR(cpsr);
for (ARMCore* core : {&interpreter, &jit})
@@ -1443,8 +1631,8 @@ TEST(StarletARMCore, ThumbJitCompilesRegisterLSREdgeCases)
interpreter_bus.WriteThumb(0x02, 0xe7fe); // b .
jit_bus.WriteThumb(0x02, 0xe7fe);
constexpr std::array<u32, 7> values = {0, 1, 0x80000000, 0xffffffff, 0x12345678,
0x7fffffff, 0xa5a5a5a5};
constexpr std::array<u32, 7> values = {0, 1, 0x80000000, 0xffffffff,
0x12345678, 0x7fffffff, 0xa5a5a5a5};
constexpr std::array<u32, 8> shifts = {0, 1, 2, 31, 32, 33, 63, 255};
for (const u32 value : values)
{
@@ -1775,8 +1963,50 @@ TEST(StarletARMCore, ARMJitDoesNotCrossFromSRAMIntoBoot0)
core.SetJitEnabled(true);
EXPECT_EQ(core.RunCycles(2), 2u);
EXPECT_EQ(core.GetJitFallbackInstructionCount(), 1u);
EXPECT_EQ(core.GetJitNativeExecutedInstructions(), 1u);
EXPECT_EQ(core.GetJitFallbackInstructionCount(), 0u);
EXPECT_EQ(core.GetJitNativeExecutedInstructions(), 2u);
#endif
}
TEST(StarletARMCore, ARMJitKeepsSRAMStackBlockTransfersInsideNativeBlock)
{
#if defined(_M_X86_64)
TestBus interpreter_bus(0x1000);
TestBus jit_bus(0x1000);
interpreter_bus.SetFastmemEnabled(true);
interpreter_bus.SetSRAMFastmemEnabled(true);
jit_bus.SetFastmemEnabled(true);
jit_bus.SetSRAMFastmemEnabled(true);
ARMCore interpreter(interpreter_bus);
ARMCore jit(jit_bus);
jit.SetJitEnabled(true);
const auto install_program = [](TestBus& bus) {
bus.WriteARM(0x00, 0xe92d4070); // push {r4-r6, lr}
bus.WriteARM(0x04, 0xe3a04000); // mov r4, #0
bus.WriteARM(0x08, 0xe3a05000); // mov r5, #0
bus.WriteARM(0x0c, 0xe3a06000); // mov r6, #0
bus.WriteARM(0x10, 0xe3a0e000); // mov lr, #0
bus.WriteARM(0x14, 0xe8bd4070); // pop {r4-r6, lr}
bus.WriteARM(0x18, 0xeafffffe); // b .
};
install_program(interpreter_bus);
install_program(jit_bus);
for (ARMCore* core : {&interpreter, &jit})
{
core->SetRegister(4, 0x11223344);
core->SetRegister(5, 0x55667788);
core->SetRegister(6, 0x99aabbcc);
core->SetRegister(13, 0xfff12000);
core->SetRegister(14, 0xddeeff00);
}
ASSERT_EQ(interpreter.RunCycles(7), 7u);
ASSERT_EQ(jit.RunCycles(7), 7u);
for (u32 reg = 0; reg < 16; ++reg)
EXPECT_EQ(jit.GetRegister(reg), interpreter.GetRegister(reg)) << "r" << reg;
EXPECT_EQ(jit.GetCPSR(), interpreter.GetCPSR());
EXPECT_EQ(jit.GetJitFallbackInstructionCount(), 0u);
EXPECT_EQ(jit.GetJitNativeExecutedInstructions(), 7u);
#endif
}
@@ -2042,6 +2272,53 @@ TEST(StarletARMCore, RunCyclesStopsAtBudget)
2u); // Both words stay resident across loop iterations.
}
TEST(StarletARMCore, ARMJitConditionalBackEdgeKeepsExactBudgetAndFallthrough)
{
#if defined(_M_X86_64)
TestBus bus;
bus.SetFastmemEnabled(true);
ARMCore core(bus);
core.SetJitEnabled(true);
bus.WriteARM(0x00, 0xe2800001); // add r0, r0, #1
bus.WriteARM(0x04, 0xe3500003); // cmp r0, #3
bus.WriteARM(0x08, 0x1afffffc); // bne 0x00
bus.WriteARM(0x0c, 0xe3a0102a); // mov r1, #42
EXPECT_EQ(core.RunCycles(9), 9u);
EXPECT_EQ(core.GetExecutedInstructions(), 9u);
EXPECT_EQ(core.GetRegister(0), 3u);
EXPECT_EQ(core.GetRegister(15), 0x0cu);
EXPECT_EQ(core.GetJitNativeExecutedInstructions(), 9u);
EXPECT_EQ(core.RunCycles(1), 1u);
EXPECT_EQ(core.GetRegister(1), 42u);
#endif
}
TEST(StarletARMCore, ThumbJitConditionalBackEdgeKeepsExactBudgetAndFallthrough)
{
#if defined(_M_X86_64)
TestBus bus;
bus.SetFastmemEnabled(true);
ARMCore core(bus);
core.SetJitEnabled(true);
core.SetCPSR(static_cast<u32>(ARMCore::Mode::Supervisor) | ARMCore::CPSR_T);
bus.WriteThumb(0x00, 0x3001); // add r0, #1
bus.WriteThumb(0x02, 0x2803); // cmp r0, #3
bus.WriteThumb(0x04, 0xd1fc); // bne 0x00
bus.WriteThumb(0x06, 0x212a); // mov r1, #42
EXPECT_EQ(core.RunCycles(9), 9u);
EXPECT_EQ(core.GetExecutedInstructions(), 9u);
EXPECT_EQ(core.GetRegister(0), 3u);
EXPECT_EQ(core.GetRegister(15), 0x06u);
EXPECT_EQ(core.GetJitNativeExecutedInstructions(), 9u);
EXPECT_EQ(core.RunCycles(1), 1u);
EXPECT_EQ(core.GetRegister(1), 42u);
#endif
}
TEST(StarletARMCore, ARMJitFastForwardsSliceStableHollywoodTimerPoll)
{
TestBus bus;
@@ -2073,14 +2350,14 @@ TEST(StarletARMCore, ARMJitFastForwardsSliceStableHollywoodTimerPoll)
TEST(StarletARMCore, ARMJitKeepsNonblockingReceiveMessagePollGuestVisible)
{
const auto install_program = [](TestBus& bus, bool non_blocking) {
bus.WriteARM(0x00, 0xe5960000); // ldr r0, [r6]
bus.WriteARM(0x04, 0xe1a01008); // mov r1, r8
bus.WriteARM(0x00, 0xe5960000); // ldr r0, [r6]
bus.WriteARM(0x04, 0xe1a01008); // mov r1, r8
bus.WriteARM(0x08, non_blocking ? 0xe3a02000 : 0xe3a02001); // mov r2, #block
bus.WriteARM(0x0c, 0xeb00000b); // bl 0x40
bus.WriteARM(0x10, 0xe2505000); // subs r5, r0, #0
bus.WriteARM(0x14, 0x1afffff9); // bne 0x00
bus.WriteARM(0x40, 0xe60001d0); // IOS ReceiveMessage syscall 0x0e
bus.WriteARM(0x44, 0xe12fff1e); // bx lr
bus.WriteARM(0x0c, 0xeb00000b); // bl 0x40
bus.WriteARM(0x10, 0xe2505000); // subs r5, r0, #0
bus.WriteARM(0x14, 0x1afffff9); // bne 0x00
bus.WriteARM(0x40, 0xe60001d0); // IOS ReceiveMessage syscall 0x0e
bus.WriteARM(0x44, 0xe12fff1e); // bx lr
};
TestBus bus;