IOS LLE: optional ARM926 MMU aborts and CP15 privilege (StarOS MMU-001 F1)

ARMCore::SetMMUFaultsEnabled(true) makes the Starlet core deliver the
faults of the ARM926EJ-S MMU instead of the identity fallback:
- translation (section 0x5, page 0x7), domain (0x9, 0xb) and permission
  (0xd, 0xf) faults from the descriptors, the DACR (no access and the
  reserved value fault; manager skips AP) and AP with the S and R bits;
  ARMv5 has no execute permission, so a fetch needs read permission;
- data aborts (vector 0x10, LR = instruction + 8 in both states) set the
  FSR and the FAR (the MVA); prefetch aborts (vector 0x0c, LR + 4) set the
  IFSR (c5 opcode2 1) and leave the FAR unchanged;
- alignment faults (0x1) when control bit A is set;
- base-restored data aborts: the first faulting access ends the
  instruction's bus traffic and its register results are undone; words
  stored before the fault stay written;
- LDRT/STRT/LDRBT/STRBT check User permissions in any mode;
- the TLB keeps each granule's domain and AP, never caches a faulting
  walk, and c8 single-entry operations drop only the page that maps the
  MVA, so an entry stays stale until the guest invalidates it;
- MRC/MCR to CP15 from User mode is an undefined instruction.

The JIT stays usable: with the option on, every block entry goes through
C++, which checks the fetch under the current mode and DACR, and only
instructions that touch neither memory, CP15, the PSRs nor the exception
model run natively; the rest use the interpreter.

Off by default. The IOS LLE path keeps its identity fallback, its
savestate format (the option and the IFSR are not serialized) and its
idle-loop fast-forwarding; the only change there is that the data
accessors read one abort flag that stays clear.

20 StarletARMCoreMMUFaults tests, each run in the interpreter and the
JIT; the existing Starlet tests pass unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Yaya48andClaude Opus 5.5 committed 2026-10-13 21:09:18 +02:00
1 parent f9193539fb
commit 9b493a1e16
5 files changed
+1052 -34

No files matched your search

@@ -6,7 +6,9 @@
#include <array>
#include <bit>
#include <chrono>
#include <initializer_list>
#include <iostream>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
@@ -3536,5 +3538,495 @@ TEST(StarletARMCore, JitCachesProvisionalIdentityTranslationForFaultPages)
EXPECT_GT(bus.GetDirectPointerCount(), 0u); // Native descriptor validation is still present.
#endif
}
// StarOS MMU-001 (F1): ARM926 MMU aborts with ARMCore::SetMMUFaultsEnabled. Every case runs in the
// interpreter and, on x86-64, in the JIT; both must reach the same state.
class MMUFaultMachine
{
public:
static constexpr u32 TABLE = 0x4000; // first-level table, 16 KiB aligned
static constexpr u32 COARSE = 0x8000; // one coarse second-level table
// Section 0x800 maps physical 0 privileged-only (AP 01), section 0x880 the same bytes with full
// access (AP 11), both in domain 0. Code is written once and run from either view.
static constexpr u32 KERNEL = 0x80000000;
static constexpr u32 CODE = KERNEL + 0x1000;
static constexpr u32 USER_CODE = 0x88001000;
static constexpr u32 FAR_SENTINEL = 0xdeadbeef;
static constexpr u32 SVC_CPSR = 0xd3;
static constexpr u32 USER_CPSR = 0xd0;
static constexpr u32 ABORT_MODE = 0x17;
static constexpr u32 UNDEFINED_MODE = 0x1b;
explicit MMUFaultMachine(bool jit) : m_bus(0x100000), m_core(m_bus)
{
// High vectors: undefined and SWI stop on themselves; both aborts branch to a handler that
// records SPSR (r8), DFSR (r9), FAR (r10) and IFSR (r11) through MRS/MRC, then marks its kind
// in r12 (0xd data, 0xc prefetch) and stops.
for (u32 vector = 0; vector < 0x20; vector += 4)
m_bus.WriteARM(0xffff0000 + vector, 0xeafffffe); // b .
m_bus.WriteARM(0xffff000c, 0xea00004b); // b 0xffff0140
m_bus.WriteARM(0xffff0010, 0xea00003a); // b 0xffff0100
for (const auto [handler, kind] : {std::pair{0xffff0100u, 0xe3a0c00du},
std::pair{0xffff0140u, 0xe3a0c00cu}})
{
m_bus.WriteARM(handler, 0xe14f8000); // mrs r8, spsr
m_bus.WriteARM(handler + 4, 0xee159f10); // mrc p15, 0, r9, c5, c0, 0 (DFSR)
m_bus.WriteARM(handler + 8, 0xee16af10); // mrc p15, 0, r10, c6, c0, 0 (FAR)
m_bus.WriteARM(handler + 12, 0xee15bf30); // mrc p15, 0, r11, c5, c0, 1 (IFSR)
m_bus.WriteARM(handler + 16, kind); // mov r12, #kind
m_bus.WriteARM(handler + 20, 0xeafffffe); // b .
}
MapSection(0x800, 0, 1, 0);
MapSection(0x880, 0, 3, 0);
MapSection(0xfff, 0xfff00000, 1, 0);
auto& cp15 = m_core.GetCP15State();
cp15.translation_table_base = TABLE;
cp15.domain_access_control = 1; // domain 0 client, every other domain no access
cp15.control |= 1;
cp15.fault_address = FAR_SENTINEL;
m_core.SetMMUFaultsEnabled(true);
m_core.SetJitEnabled(jit);
m_core.SetRegister(15, CODE);
}
void MapSection(u32 index, u32 physical, u32 ap, u32 domain)
{
m_bus.WriteARM(TABLE + index * 4,
(physical & 0xfff00000) | (ap << 10) | (domain << 5) | 0x12);
}
// The section of `virtual_address` points at the coarse table, whose entry for it maps a small
// page of `physical` with the same AP in its four subpages.
void MapSmallPage(u32 virtual_address, u32 physical, u32 ap, u32 domain)
{
m_bus.WriteARM(TABLE + (virtual_address >> 20) * 4, COARSE | (domain << 5) | 0x11);
m_bus.WriteARM(COARSE + ((virtual_address >> 12) & 0xff) * 4,
(physical & 0xfffff000) | ((ap * 0x55) << 4) | 2);
}
// Code at a physical address (its view in section 0x800 or 0x880 is `physical` + base).
void Code(u32 physical, std::initializer_list<u32> instructions)
{
for (const u32 instruction : instructions)
{
m_bus.WriteARM(physical, instruction);
physical += 4;
}
}
void Start(u32 cpsr, u32 pc)
{
m_core.SetCPSR(cpsr);
m_core.SetRegister(15, pc);
}
void Run(u64 cycles = 48) { m_core.RunCycles(cycles); }
void ExpectDataAbort(u32 instruction_address, u32 dfsr, u32 far, u32 spsr)
{
EXPECT_EQ(m_core.GetCPSR() & 0x1f, ABORT_MODE);
EXPECT_EQ(m_core.GetRegister(12), 0xdu) << "not the data abort vector";
EXPECT_EQ(m_core.GetRegister(14), instruction_address + 8);
EXPECT_EQ(m_core.GetRegister(8), spsr);
EXPECT_EQ(m_core.GetRegister(9), dfsr);
EXPECT_EQ(m_core.GetRegister(10), far);
}
void ExpectPrefetchAbort(u32 instruction_address, u32 ifsr, u32 spsr)
{
EXPECT_EQ(m_core.GetCPSR() & 0x1f, ABORT_MODE);
EXPECT_EQ(m_core.GetRegister(12), 0xcu) << "not the prefetch abort vector";
EXPECT_EQ(m_core.GetRegister(14), instruction_address + 4);
EXPECT_EQ(m_core.GetRegister(8), spsr);
EXPECT_EQ(m_core.GetRegister(11), ifsr);
// A prefetch abort leaves the FAR as it was.
EXPECT_EQ(m_core.GetRegister(10), FAR_SENTINEL);
}
TestBus& Bus() { return m_bus; }
ARMCore& Core() { return m_core; }
private:
TestBus m_bus;
ARMCore m_core;
};
template <typename Case>
void ForEachEngine(Case run_case)
{
for (const bool jit : {false, true})
{
#if !defined(_M_X86_64)
if (jit)
continue;
#endif
SCOPED_TRACE(jit ? "JIT" : "interpreter");
MMUFaultMachine machine(jit);
run_case(machine);
}
}
using M = MMUFaultMachine;
TEST(StarletARMCoreMMUFaults, SectionTranslationFaultIsADataAbort)
{
ForEachEngine([](M& m) {
m.Code(0x1000, {0xe5921000}); // ldr r1, [r2]
m.Core().SetRegister(1, 0x11111111);
m.Core().SetRegister(2, 0x90000004); // section 0x900 has no descriptor
m.Run();
m.ExpectDataAbort(M::CODE, 0x05, 0x90000004, M::SVC_CPSR);
EXPECT_EQ(m.Core().GetRegister(1), 0x11111111u);
});
}
TEST(StarletARMCoreMMUFaults, PageTranslationFaultReportsItsDomain)
{
ForEachEngine([](M& m) {
m.MapSmallPage(0x90000000, 0x2000, 3, 2);
m.Code(0x1000, {0xe5921000}); // ldr r1, [r2]
m.Core().SetRegister(2, 0x90001000); // the next small page has no descriptor
m.Run();
m.ExpectDataAbort(M::CODE, 0x27, 0x90001000, M::SVC_CPSR);
});
}
TEST(StarletARMCoreMMUFaults, DomainWithoutAccessFaultsForSectionsAndPages)
{
ForEachEngine([](M& m) {
m.MapSection(0x900, 0, 3, 2);
m.Code(0x1000, {0xe5921000}); // ldr r1, [r2]
m.Core().SetRegister(2, 0x90002000);
m.Run();
m.ExpectDataAbort(M::CODE, 0x29, 0x90002000, M::SVC_CPSR);
});
ForEachEngine([](M& m) {
m.MapSmallPage(0x91000000, 0x2000, 3, 4);
m.Code(0x1000, {0xe5921000}); // ldr r1, [r2]
m.Core().SetRegister(2, 0x91000010);
m.Run();
m.ExpectDataAbort(M::CODE, 0x4b, 0x91000010, M::SVC_CPSR);
});
}
TEST(StarletARMCoreMMUFaults, UserAccessFollowsAP)
{
// AP 01: privileged only. AP 10: User read-only. AP 11: full access.
ForEachEngine([](M& m) {
m.Code(0x1000, {0xe5921000}); // ldr r1, [r2]
m.Core().SetRegister(2, M::KERNEL + 0x2000);
m.Start(M::USER_CPSR, M::USER_CODE);
m.Run();
m.ExpectDataAbort(M::USER_CODE, 0x0d, M::KERNEL + 0x2000, M::USER_CPSR);
});
ForEachEngine([](M& m) {
m.MapSection(0x920, 0, 2, 0);
m.Bus().WriteARM(0x2000, 0x12345678);
m.Code(0x1000, {0xe5921000, 0xe5823000}); // ldr r1, [r2]; str r3, [r2]
m.Core().SetRegister(2, 0x92002000);
m.Core().SetRegister(3, 0xabcdef01);
m.Start(M::USER_CPSR, M::USER_CODE);
m.Run();
EXPECT_EQ(m.Core().GetRegister(1), 0x12345678u);
m.ExpectDataAbort(M::USER_CODE + 4, 0x0d, 0x92002000, M::USER_CPSR);
EXPECT_EQ(m.Bus()[0x2000], 0x12u) << "the faulting store wrote memory";
});
}
TEST(StarletARMCoreMMUFaults, APZeroDependsOnTheSAndRBits)
{
struct Case
{
u32 control_bits;
bool user;
bool write;
bool allowed;
};
constexpr u32 S = 1U << 8;
constexpr u32 R = 1U << 9;
for (const Case c : {Case{0, false, false, false}, Case{S, false, false, true},
Case{S, false, true, false}, Case{S, true, false, false},
Case{R, true, false, true}, Case{R, false, true, false},
Case{S | R, false, false, false}})
{
SCOPED_TRACE(testing::Message() << "control " << std::hex << c.control_bits << " user "
<< c.user << " write " << c.write);
ForEachEngine([&](M& m) {
m.MapSection(0x900, 0, 0, 0);
m.Bus().WriteARM(0x2000, 0x0badf00d);
m.Core().GetCP15State().control |= c.control_bits;
m.Code(0x1000, {c.write ? 0xe5821000u : 0xe5921000u}); // str/ldr r1, [r2]
m.Core().SetRegister(1, 0x11111111);
m.Core().SetRegister(2, 0x90002000);
const u32 code = c.user ? M::USER_CODE : M::CODE;
m.Start(c.user ? M::USER_CPSR : M::SVC_CPSR, code);
m.Run();
if (c.allowed)
{
EXPECT_NE(m.Core().GetCPSR() & 0x1f, M::ABORT_MODE);
EXPECT_EQ(m.Core().GetRegister(1), c.write ? 0x11111111u : 0x0badf00du);
}
else
{
m.ExpectDataAbort(code, 0x0d, 0x90002000, c.user ? M::USER_CPSR : M::SVC_CPSR);
}
});
}
}
TEST(StarletARMCoreMMUFaults, ManagerDomainSkipsAP)
{
ForEachEngine([](M& m) {
m.MapSection(0x900, 0, 0, 5);
m.Core().GetCP15State().domain_access_control = 1 | (3 << 10);
m.Bus().WriteARM(0x2000, 0x600dcafe);
m.Code(0x1000, {0xe5921000, 0xe5823004}); // ldr r1, [r2]; str r3, [r2, #4]
m.Core().SetRegister(2, 0x90002000);
m.Core().SetRegister(3, 0x01020304);
m.Start(M::USER_CPSR, M::USER_CODE);
m.Run(8);
EXPECT_EQ(m.Core().GetCPSR() & 0x1f, 0x10u);
EXPECT_EQ(m.Core().GetRegister(1), 0x600dcafeu);
EXPECT_EQ(m.Bus()[0x2004], 0x01u);
});
}
TEST(StarletARMCoreMMUFaults, FetchFromAnUnmappedSectionIsAPrefetchAbort)
{
ForEachEngine([](M& m) {
m.Code(0x1000, {0xe12fff13}); // bx r3
m.Core().SetRegister(3, 0x90000000);
m.Run();
m.ExpectPrefetchAbort(0x90000000, 0x05, M::SVC_CPSR);
});
}
TEST(StarletARMCoreMMUFaults, UserFetchFromPrivilegedCodeIsAPrefetchAbort)
{
// ARMv5 has no execute permission: a fetch needs read permission, so AP 01 stops User code.
ForEachEngine([](M& m) {
m.Code(0x1000, {0xe12fff13}); // bx r3
m.Core().SetRegister(3, M::CODE + 0x40);
m.Start(M::USER_CPSR, M::USER_CODE);
m.Run();
m.ExpectPrefetchAbort(M::CODE + 0x40, 0x0d, M::USER_CPSR);
});
}
TEST(StarletARMCoreMMUFaults, CachedCodeIsCheckedAgainAtEveryEntry)
{
// The JIT compiles the block in Supervisor mode; entering it again in User mode must fault.
ForEachEngine([](M& m) {
m.Code(0x1040, {0xe3a07001, 0xeafffffe}); // mov r7, #1; b .
m.Start(M::SVC_CPSR, M::CODE + 0x40);
m.Run(8);
EXPECT_EQ(m.Core().GetRegister(7), 1u);
m.Core().SetRegister(7, 0);
m.Start(M::USER_CPSR, M::CODE + 0x40);
m.Run();
m.ExpectPrefetchAbort(M::CODE + 0x40, 0x0d, M::USER_CPSR);
EXPECT_EQ(m.Core().GetRegister(7), 0u);
});
}
TEST(StarletARMCoreMMUFaults, RevokingTheCodeDomainStopsTheNextFetch)
{
ForEachEngine([](M& m) {
m.MapSection(0x930, 0, 1, 3);
m.Core().GetCP15State().domain_access_control = 1 | (1 << 6);
m.Code(0x1000, {0xee034f10, 0xe3a07001}); // mcr p15, 0, r4, c3, c0, 0; mov r7, #1
m.Core().SetRegister(4, 1); // domain 3: no access
m.Start(M::SVC_CPSR, 0x93001000);
m.Run();
m.ExpectPrefetchAbort(0x93001004, 0x39, M::SVC_CPSR);
EXPECT_EQ(m.Core().GetRegister(7), 0u);
});
}
TEST(StarletARMCoreMMUFaults, BlockTransferAbortRestoresTheBase)
{
ForEachEngine([](M& m) {
m.Code(0x1000, {0xe8b0000e}); // ldmia r0!, {r1, r2, r3}
for (u32 reg = 1; reg <= 3; ++reg)
m.Core().SetRegister(reg, 0x11111111 * reg);
m.Core().SetRegister(0, 0x800ffffc); // the second word is in unmapped section 0x801
m.Run();
m.ExpectDataAbort(M::CODE, 0x05, 0x80100000, M::SVC_CPSR);
EXPECT_EQ(m.Core().GetRegister(0), 0x800ffffcu);
for (u32 reg = 1; reg <= 3; ++reg)
EXPECT_EQ(m.Core().GetRegister(reg), 0x11111111 * reg);
});
ForEachEngine([](M& m) {
m.Code(0x1000, {0xe8a00006}); // stmia r0!, {r1, r2}
m.Core().SetRegister(0, 0x800ffffc);
m.Core().SetRegister(1, 0xa1a2a3a4);
m.Core().SetRegister(2, 0xb1b2b3b4);
m.Run();
m.ExpectDataAbort(M::CODE, 0x05, 0x80100000, M::SVC_CPSR);
EXPECT_EQ(m.Core().GetRegister(0), 0x800ffffcu);
// The word stored before the fault stays written, as on ARM926.
EXPECT_EQ(m.Bus()[0xffffc], 0xa1u);
});
}
TEST(StarletARMCoreMMUFaults, LDRTChecksUserPermissionsInAPrivilegedMode)
{
ForEachEngine([](M& m) {
m.Bus().WriteARM(0x2000, 0x13572468);
m.Code(0x1000, {0xe5921000, 0xe4b23000}); // ldr r1, [r2]; ldrt r3, [r2], #0
m.Core().SetRegister(2, M::KERNEL + 0x2000);
m.Run();
EXPECT_EQ(m.Core().GetRegister(1), 0x13572468u);
m.ExpectDataAbort(M::CODE + 4, 0x0d, M::KERNEL + 0x2000, M::SVC_CPSR);
});
}
TEST(StarletARMCoreMMUFaults, AlignmentFaultsNeedTheABit)
{
ForEachEngine([](M& m) {
m.Core().GetCP15State().control |= 2;
m.Code(0x1000, {0xe5921000}); // ldr r1, [r2]
m.Core().SetRegister(2, M::KERNEL + 0x2002);
m.Run();
m.ExpectDataAbort(M::CODE, 0x01, M::KERNEL + 0x2002, M::SVC_CPSR);
});
ForEachEngine([](M& m) {
m.Bus().WriteARM(0x2000, 0x11223344);
m.Code(0x1000, {0xe5921000}); // ldr r1, [r2]: rotated, no fault
m.Core().SetRegister(2, M::KERNEL + 0x2002);
m.Run(4);
EXPECT_EQ(m.Core().GetRegister(1), 0x33441122u);
});
}
TEST(StarletARMCoreMMUFaults, CP15FromUserModeIsUndefined)
{
ForEachEngine([](M& m) {
m.Code(0x1000, {0xee034f10}); // mcr p15, 0, r4, c3, c0, 0
m.Core().SetRegister(4, 0xffffffff);
m.Start(M::USER_CPSR, M::USER_CODE);
m.Run(8);
EXPECT_EQ(m.Core().GetCPSR() & 0x1f, M::UNDEFINED_MODE);
EXPECT_EQ(m.Core().GetRegister(15), 0xffff0004u);
EXPECT_EQ(m.Core().GetCP15State().domain_access_control, 1u);
});
}
TEST(StarletARMCoreMMUFaults, TLBEntriesStaleUntilTheirOwnInvalidation)
{
ForEachEngine([](M& m) {
m.MapSmallPage(0x94000000, 0x3000, 3, 0);
m.Bus().WriteARM(0x3000, 0xcafef00d);
m.Code(0x1000, {
0xe5921000, // ldr r1, [r2]: fills the TLB
0xe5854000, // str r4, [r5]: clears the page's descriptor
0xe5923000, // ldr r3, [r2]: the stale entry still translates
0xee086f37, // mcr p15, 0, r6, c8, c7, 1: another page's MVA
0xe5927000, // ldr r7, [r2]: still stale
0xee082f37, // mcr p15, 0, r2, c8, c7, 1: this page's MVA
0xe5920000, // ldr r0, [r2]: translation fault
});
m.Core().SetRegister(0, 0x5a5a5a5a);
m.Core().SetRegister(2, 0x94000000);
m.Core().SetRegister(4, 0);
m.Core().SetRegister(5, M::KERNEL + M::COARSE);
m.Core().SetRegister(6, 0x95000000);
m.Run(64);
EXPECT_EQ(m.Core().GetRegister(1), 0xcafef00du);
EXPECT_EQ(m.Core().GetRegister(3), 0xcafef00du);
EXPECT_EQ(m.Core().GetRegister(7), 0xcafef00du);
m.ExpectDataAbort(M::CODE + 24, 0x07, 0x94000000, M::SVC_CPSR);
EXPECT_EQ(m.Core().GetRegister(0), 0x5a5a5a5au);
});
}
TEST(StarletARMCoreMMUFaults, DACRChangesNeedNoTLBMaintenance)
{
ForEachEngine([](M& m) {
m.MapSection(0x960, 0, 3, 6);
m.Core().GetCP15State().domain_access_control = 1 | (1 << 12);
m.Bus().WriteARM(0x2000, 0x76543210);
m.Code(0x1000, {0xe5921000, 0xee034f10, 0xe5923000}); // ldr; mcr c3 (r4); ldr r3
m.Core().SetRegister(2, 0x96002000);
m.Core().SetRegister(3, 0x33333333);
m.Core().SetRegister(4, 1);
m.Run();
EXPECT_EQ(m.Core().GetRegister(1), 0x76543210u);
m.ExpectDataAbort(M::CODE + 8, 0x69, 0x96002000, M::SVC_CPSR);
EXPECT_EQ(m.Core().GetRegister(3), 0x33333333u);
});
}
TEST(StarletARMCoreMMUFaults, FCSEFaultsReportTheModifiedAddress)
{
ForEachEngine([](M& m) {
m.Core().GetCP15State().process_id = 0x02000000;
m.Code(0x1000, {0xe5921000}); // ldr r1, [r2]
m.Core().SetRegister(2, 0x00001000);
m.Run();
m.ExpectDataAbort(M::CODE, 0x05, 0x02001000, M::SVC_CPSR);
});
ForEachEngine([](M& m) {
m.Core().GetCP15State().process_id = 0x02000000;
m.MapSection(0x020, 0, 3, 0);
m.Code(0x1000, {0xe5921000}); // ldr r1, [r2]: MVA 0x02001000, this instruction itself
m.Core().SetRegister(2, 0x00001000);
m.Run(4);
EXPECT_EQ(m.Core().GetRegister(1), 0xe5921000u);
});
}
TEST(StarletARMCoreMMUFaults, ThumbDataAbortReturnsToTheInstructionPlusEight)
{
ForEachEngine([](M& m) {
m.Bus().WriteThumb(0x1080, 0x6811); // ldr r1, [r2, #0]
m.Core().SetRegister(2, 0x90000000);
m.Start(M::SVC_CPSR | 0x20, M::CODE + 0x80);
m.Run();
m.ExpectDataAbort(M::CODE + 0x80, 0x05, 0x90000000, M::SVC_CPSR | 0x20);
EXPECT_EQ(m.Core().GetCPSR() & 0x20, 0u);
});
}
TEST(StarletARMCoreMMUFaults, TheJITStillRunsNativeCode)
{
#if defined(_M_X86_64)
MMUFaultMachine m(true);
m.Code(0x1000, {
0xe3a00000, // mov r0, #0
0xe2800001, // add r0, r0, #1
0xe3500010, // cmp r0, #16
0x1afffffc, // bne 0x1004
0xeafffffe, // b .
});
m.Run(96);
EXPECT_EQ(m.Core().GetRegister(0), 16u);
EXPECT_GT(m.Core().GetJitNativeExecutedInstructions(), 0u);
#endif
}
TEST(StarletARMCoreMMUFaults, DisabledFaultsKeepTheIdentityFallbackAndUserCP15)
{
// The IOS LLE model is unchanged unless SetMMUFaultsEnabled(true) is called.
for (const bool jit : {false, true})
{
#if !defined(_M_X86_64)
if (jit)
continue;
#endif
SCOPED_TRACE(jit ? "JIT" : "interpreter");
MMUFaultMachine m(jit);
m.Core().SetMMUFaultsEnabled(false);
m.Bus().WriteARM(0x2000, 0x44556677);
m.Code(0x1000, {0xe5921000, 0xee034f10, 0xeafffffe}); // ldr; mcr c3 (r4); b .
m.Core().SetRegister(2, 0x00002000); // section 0x000: no descriptor
m.Core().SetRegister(4, 0x55555555);
m.Start(M::USER_CPSR, M::USER_CODE);
m.Run(8);
EXPECT_EQ(m.Core().GetRegister(1), 0x44556677u);
EXPECT_EQ(m.Core().GetCP15State().domain_access_control, 0x55555555u);
EXPECT_EQ(m.Core().GetCPSR() & 0x1f, 0x10u);
}
}
} // namespace
} // namespace IOS::LLE