From 3d12571127ab28e9350936c08e9c50781ef135c9 Mon Sep 17 00:00:00 2001 From: Yaya48 Date: Tue, 6 Oct 2026 22:16:53 +0200 Subject: [PATCH] IOS LLE: execute ARMv5TE enhanced DSP instructions (StarletOS phase 11 I1) SMLAxy, SMLAWy, SMULWy, SMLALxy, SMULxy, QADD, QSUB, QDADD and QDSUB were decoded as compare data processing that writes no register; the ARM926EJ-S implements them. The interpreter now executes them with the Q flag and the JIT leaves them to it. Adds ARMv5TEEnhancedDSPInstructions. Co-Authored-By: Claude Opus 5.5 --- Source/Core/Core/IOS/Starlet/ARMCore.cpp | 91 +++++++++++++++++++ Source/Core/Core/IOS/Starlet/ARMCore.h | 2 + Source/Core/Core/IOS/Starlet/ARMJitX64.cpp | 4 +- .../Core/IOS/Starlet/ARMCoreTest.cpp | 77 ++++++++++++++++ docs/StarletOS_Checkpoint.json | 21 +++-- docs/StarletOS_Handoff.md | 21 +++++ 6 files changed, 209 insertions(+), 7 deletions(-) diff --git a/Source/Core/Core/IOS/Starlet/ARMCore.cpp b/Source/Core/Core/IOS/Starlet/ARMCore.cpp index 274e5bb1ea..a17d6c337e 100644 --- a/Source/Core/Core/IOS/Starlet/ARMCore.cpp +++ b/Source/Core/Core/IOS/Starlet/ARMCore.cpp @@ -1423,6 +1423,16 @@ void ARMCore::ExecuteARM(u32 instruction) return; } + // ARMv5TE enhanced DSP instructions (ARM Architecture Reference Manual, ARMv5TE): SMLAxy, + // SMLAWy, SMULWy, SMLALxy, SMULxy, then QADD, QSUB, QDADD, QDSUB. They sit in the + // data-processing space with a compare opcode and S clear, which ARMv5 reserves for such + // miscellaneous instructions; decoded as data processing they would write no register. + if ((instruction & 0x0f900090) == 0x01000080 || (instruction & 0x0f900ff0) == 0x01000050) + { + ExecuteEnhancedDSP(instruction); + return; + } + if ((instruction & 0x0f8000f0) == 0x00800090) { ExecuteMultiplyLong(instruction); @@ -2327,6 +2337,87 @@ void ARMCore::ExecuteMultiply(u32 instruction) SetNZ(result); } +void ARMCore::ExecuteEnhancedDSP(u32 instruction) +{ + // StarletOS phase 11 (I1). The ARM926EJ-S implements these; the Q flag (CPSR bit 27) is sticky + // and set on signed overflow of an accumulation or on saturation. + const auto half = [](u32 value, bool top) { + return static_cast(static_cast(top ? value >> 16 : value)); + }; + const auto saturate = [this](s64 value) { + if (value > std::numeric_limits::max()) + { + m_cpsr |= CPSR_Q; + return static_cast(std::numeric_limits::max()); + } + if (value < std::numeric_limits::min()) + { + m_cpsr |= CPSR_Q; + return static_cast(std::numeric_limits::min()); + } + return static_cast(static_cast(value)); + }; + const auto accumulate = [this](s64 sum) { + if (sum != static_cast(sum)) + m_cpsr |= CPSR_Q; + return static_cast(static_cast(sum)); + }; + const u32 op = (instruction >> 21) & 3; + const u32 rm = instruction & 0xf; + + if ((instruction & 0xf0) == 0x50) + { + // QADD (0), QSUB (1), QDADD (2), QDSUB (3): Rd = sat(Rm +/- [sat(2 *)] Rn). + const u32 rn = (instruction >> 16) & 0xf; + const u32 rd = (instruction >> 12) & 0xf; + const s64 m = static_cast(ReadRegisterOperand(rm)); + s64 n = static_cast(ReadRegisterOperand(rn)); + if ((op & 2) != 0) + n = static_cast(saturate(2 * n)); + m_registers[rd] = saturate((op & 1) != 0 ? m - n : m + n); + return; + } + + // SMLAxy (0), SMLAWy/SMULWy (1), SMLALxy (2), SMULxy (3). Rd (RdHi) is bits 19-16, Rn (RdLo) + // bits 15-12, Rs bits 11-8; x (bit 5) picks Rm's half, y (bit 6) Rs's half. + const u32 rd = (instruction >> 16) & 0xf; + const u32 rn = (instruction >> 12) & 0xf; + const u32 rs = (instruction >> 8) & 0xf; + const bool x = (instruction & (1U << 5)) != 0; + const bool y = (instruction & (1U << 6)) != 0; + const u32 m = ReadRegisterOperand(rm); + const u32 s = ReadRegisterOperand(rs); + switch (op) + { + case 0: + m_registers[rd] = + accumulate(half(m, x) * half(s, y) + static_cast(ReadRegisterOperand(rn))); + break; + case 1: + { + // The top 32 bits of the 48-bit product of Rm and a halfword of Rs. + const s64 product = (static_cast(static_cast(m)) * half(s, y)) >> 16; + if (x) // SMULWy + m_registers[rd] = static_cast(static_cast(product)); + else // SMLAWy + m_registers[rd] = accumulate(product + static_cast(ReadRegisterOperand(rn))); + break; + } + case 2: + { + const u64 accumulator = + (static_cast(ReadRegisterOperand(rd)) << 32) | ReadRegisterOperand(rn); + const u64 result = accumulator + static_cast(half(m, x) * half(s, y)); + m_registers[rn] = static_cast(result); + m_registers[rd] = static_cast(result >> 32); + break; + } + default: + m_registers[rd] = static_cast(static_cast(half(m, x) * half(s, y))); + break; + } +} + void ARMCore::ExecuteMultiplyLong(u32 instruction) { const bool signed_multiply = (instruction & (1U << 22)) != 0; diff --git a/Source/Core/Core/IOS/Starlet/ARMCore.h b/Source/Core/Core/IOS/Starlet/ARMCore.h index 3b493f7951..e2ae487f3b 100644 --- a/Source/Core/Core/IOS/Starlet/ARMCore.h +++ b/Source/Core/Core/IOS/Starlet/ARMCore.h @@ -315,6 +315,8 @@ private: void TraceWatchedBlockTransfer(u32 address, u32 size, bool load) const; void ExecuteMultiply(u32 instruction); void ExecuteMultiplyLong(u32 instruction); + // ARMv5TE signed halfword multiplies and saturating arithmetic. + void ExecuteEnhancedDSP(u32 instruction); void ExecuteBranch(u32 instruction); void ExecuteCoprocessor(u32 instruction); void ExecutePSRTransfer(u32 instruction); diff --git a/Source/Core/Core/IOS/Starlet/ARMJitX64.cpp b/Source/Core/Core/IOS/Starlet/ARMJitX64.cpp index ace927e24d..bee36fcbdb 100644 --- a/Source/Core/Core/IOS/Starlet/ARMJitX64.cpp +++ b/Source/Core/Core/IOS/Starlet/ARMJitX64.cpp @@ -1164,7 +1164,9 @@ bool ARMJitX64::CanEmitARMDataProcessing(u32 instruction) const if ((instruction & 0x0fff0ff0) == 0x016f0f10 || (instruction & 0x0f8000f0) == 0x00800090 || (instruction & 0x0fc000f0) == 0x00000090 || (instruction & 0x0fb00ff0) == 0x01000090 || (instruction & 0x0fbf0fff) == 0x010f0000 || (instruction & 0x0fb0fff0) == 0x0120f000 || - (instruction & 0x0fb0f000) == 0x0320f000 || (instruction & 0x0e000090) == 0x00000090) + (instruction & 0x0fb0f000) == 0x0320f000 || (instruction & 0x0e000090) == 0x00000090 || + // ARMv5TE enhanced DSP multiplies and saturating arithmetic: interpreter (StarletOS I1). + (instruction & 0x0f900090) == 0x01000080 || (instruction & 0x0f900ff0) == 0x01000050) { return false; } diff --git a/Source/UnitTests/Core/IOS/Starlet/ARMCoreTest.cpp b/Source/UnitTests/Core/IOS/Starlet/ARMCoreTest.cpp index 65194ad2bb..9a48f571a1 100644 --- a/Source/UnitTests/Core/IOS/Starlet/ARMCoreTest.cpp +++ b/Source/UnitTests/Core/IOS/Starlet/ARMCoreTest.cpp @@ -1755,6 +1755,83 @@ TEST(StarletARMCore, ARMJitCompilesLongMultiplyFamily) #endif } +TEST(StarletARMCore, ARMv5TEEnhancedDSPInstructions) +{ + // StarletOS phase 11 (I1): encodings from GNU as (-march=armv5te); results from the ARM + // Architecture Reference Manual. r0 = Rm, r1 = Rs or Rn, r7 = accumulator, r3/r4 results. + struct Case + { + u32 instruction; + u32 r0; + u32 r1; + u32 r3; + u32 r4; + u32 r7; + u32 expected_r3; + u32 expected_r4; + bool expected_q; + }; + constexpr std::array cases = { + // smlabb r4, r0, r1, r7: a 88-byte entry 9 of a table (Revolution's RAM layer). + Case{0xe1047180, 9, 88, 0, 0, 0x13780000, 0, 0x13780318, false}, + Case{0xe1640180, 0xffff0003, 0x0002fffe, 0, 0, 0, 0, 0xfffffffa, false}, // smulbb + Case{0xe16401a0, 0xffff0003, 0x0002fffe, 0, 0, 0, 0, 2, false}, // smultb + // smlatt r4, r0, r1, r7: 0x3fff0001 + 0x7fffffff overflows, wraps and sets Q. + Case{0xe10471e0, 0x7fff0000, 0x7fff0000, 0, 0, 0x7fffffff, 0, 0xbfff0000, true}, + Case{0xe1247180, 0x00020000, 0x00000003, 0, 0, 10, 0, 16, false}, // smlawb + Case{0xe12401e0, 0xfffe0000, 0x00030000, 0, 0, 0, 0, 0xfffffffa, false}, // smulwt + // smlalbb r3, r4, r0, r1: 0x00000000_ffffffff + 1 carries into RdHi. + Case{0xe1443180, 1, 1, 0xffffffff, 0, 0, 0, 1, false}, + Case{0xe1014050, 0x7fffffff, 1, 0, 0, 0, 0, 0x7fffffff, true}, // qadd + Case{0xe1214050, 0x80000000, 1, 0, 0, 0, 0, 0x80000000, true}, // qsub + Case{0xe1414050, 1, 0x40000000, 0, 0, 0, 0, 0x7fffffff, true}, // qdadd + Case{0xe1614050, 0, 3, 0, 0, 0, 0, 0xfffffffa, false}, // qdsub + }; + + for (const Case& test : cases) + { + TestBus interpreter_bus; + ARMCore interpreter(interpreter_bus); +#if defined(_M_X86_64) + TestBus jit_bus; + ARMCore jit(jit_bus); + std::array cores = {&interpreter, &jit}; + jit_bus.WriteARM(0, test.instruction); + jit_bus.WriteARM(4, 0xeafffffe); // b . +#else + std::array cores = {&interpreter}; +#endif + interpreter_bus.WriteARM(0, test.instruction); + interpreter_bus.WriteARM(4, 0xeafffffe); // b . + for (ARMCore* core : cores) + { + core->SetCPSR(static_cast(ARMCore::Mode::System)); + core->SetRegister(0, test.r0); + core->SetRegister(1, test.r1); + core->SetRegister(3, test.r3); + core->SetRegister(4, test.r4); + core->SetRegister(7, test.r7); + core->SetRegister(15, 0); + } +#if defined(_M_X86_64) + jit.SetJitEnabled(true); +#endif + for (ARMCore* core : cores) + { + ASSERT_EQ(core->RunCycles(2), 2u); + EXPECT_EQ(core->GetRegister(4), test.expected_r4) << std::hex << test.instruction; + if (test.instruction == 0xe1443180) + EXPECT_EQ(core->GetRegister(3), test.expected_r3); + EXPECT_EQ((core->GetCPSR() & ARMCore::CPSR_Q) != 0, test.expected_q) + << std::hex << test.instruction; + // The DSP instruction must not touch the condition flags (it is not a compare). + EXPECT_EQ(core->GetCPSR() & (ARMCore::CPSR_N | ARMCore::CPSR_Z | ARMCore::CPSR_C | + ARMCore::CPSR_V), + 0u); + } + } +} + TEST(StarletARMCore, FCSESwitchPreservesTaggedTLBTranslations) { TestBus bus(0x10000); diff --git a/docs/StarletOS_Checkpoint.json b/docs/StarletOS_Checkpoint.json index 6300272b79..7f15e5f432 100644 --- a/docs/StarletOS_Checkpoint.json +++ b/docs/StarletOS_Checkpoint.json @@ -53,18 +53,18 @@ }, { "path": "Source/Core/Core/IOS/Starlet/ARMCore.cpp", - "sha256": "262946f1fb95250d1aa1ca73c9bc27ef5f83511731bf84c765a41b04fe7ff70a", - "bytes": 76503 + "sha256": "2559d69e1ec654734f788312cf5f3dca4de63e22e0880626306e8d15a4585648", + "bytes": 80026 }, { "path": "Source/Core/Core/IOS/Starlet/ARMCore.h", - "sha256": "fa80b0d697e19f0aee178be4b13097ad5466f0bbd0cb9b3e7a74876c045b731f", - "bytes": 14647 + "sha256": "c3263bebbbdb7cd1119cc74ad328a63ad969136c1755ccf8e06e6549f23da3aa", + "bytes": 14758 }, { "path": "Source/Core/Core/IOS/Starlet/ARMJitX64.cpp", - "sha256": "30ac3e6f74edd40a76406394ed40d66a1ca5b6b9a17b9699a1534d255320108f", - "bytes": 109425 + "sha256": "724382f4f66442d51382d3249bea68620d7417a6c7f48b0c19efa60e942a183c", + "bytes": 109615 }, { "path": "Source/Core/Core/IOS/Starlet/ARMJitX64.h", @@ -189,6 +189,15 @@ "Source/Core/Core/IOS/Starlet/StarletMemory.cpp", "Source/Core/Core/IOS/Starlet/StarletMemory.h" ] + }, + { + "recordedAt": "2026-10-06", + "purpose": "StarletOS Phase 11 proposal I1: the ARM interpreter executes the ARMv5TE enhanced DSP instructions (SMLAxy, SMLAWy, SMULWy, SMLALxy, SMULxy, QADD, QSUB, QDADD, QDSUB, Q flag), previously decoded as compare data processing that wrote no register; the JIT leaves them to the interpreter; test ARMv5TEEnhancedDSPInstructions", + "files": [ + "Source/Core/Core/IOS/Starlet/ARMCore.cpp", + "Source/Core/Core/IOS/Starlet/ARMCore.h", + "Source/Core/Core/IOS/Starlet/ARMJitX64.cpp" + ] } ] } diff --git a/docs/StarletOS_Handoff.md b/docs/StarletOS_Handoff.md index c18e384ca3..94c49684fb 100644 --- a/docs/StarletOS_Handoff.md +++ b/docs/StarletOS_Handoff.md @@ -212,3 +212,24 @@ the one it had just written, with no other Starlet store or device DMA seen. - `ARMCore.cpp` keeps its CRLF bytes. 95 Starlet tests passed. Companion smoke run (P11-H06D, 150 s): no DIFF, no AUDIT, 9 LDM with identical virtual and physical addresses. + +## ARMv5TE enhanced DSP instructions — 2026-10-06 + +Companion proposal I1 (approved). The companion's Revolution kernel computes +table addresses with `smlabb`; the ARM926EJ-S executes it, but +`ARMCore::ExecuteARM` had no case for the ARMv5TE enhanced DSP instructions, +so `smlabb` fell into data processing as `TST` (no destination written, flags +changed). The kernel then read the wrong RAM-layer entry and refused a +creation -106 ("system files corrupted" in the System Menu), in the fork only. + +- `ARMCore::ExecuteEnhancedDSP`: SMLAxy, SMLAWy, SMULWy, SMLALxy, SMULxy, + QADD, QSUB, QDADD, QDSUB, with the sticky Q flag (ARM Architecture + Reference Manual semantics), decoded before the generic decoders. +- `CanEmitARMDataProcessing` excludes these encodings, so the JIT leaves them + to the interpreter. +- Test `StarletARMCore.ARMv5TEEnhancedDSPInstructions`: eleven encodings from + GNU as (`-march=armv5te`), interpreter and JIT, results, Q and NZCV + untouched. 96 selected Starlet tests passed. +- The companion's images use, besides these (16 `smlabb`, 4 `smulbb`), only + `blx`, `clz`, `ldrd`, `strd` among the ARMv5TE additions; those were + already handled.