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 <[email protected]>
This commit is contained in:
Yaya48andClaude Opus 5.5 committed 2026-10-06 22:16:53 +02:00
1 parent ba5b6de0d0
commit 3d12571127
6 files changed
+209 -7

No files matched your search

+91
View File
@@ -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<s64>(static_cast<s16>(top ? value >> 16 : value));
};
const auto saturate = [this](s64 value) {
if (value > std::numeric_limits<s32>::max())
{
m_cpsr |= CPSR_Q;
return static_cast<u32>(std::numeric_limits<s32>::max());
}
if (value < std::numeric_limits<s32>::min())
{
m_cpsr |= CPSR_Q;
return static_cast<u32>(std::numeric_limits<s32>::min());
}
return static_cast<u32>(static_cast<s32>(value));
};
const auto accumulate = [this](s64 sum) {
if (sum != static_cast<s32>(sum))
m_cpsr |= CPSR_Q;
return static_cast<u32>(static_cast<s32>(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<s32>(ReadRegisterOperand(rm));
s64 n = static_cast<s32>(ReadRegisterOperand(rn));
if ((op & 2) != 0)
n = static_cast<s32>(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<s32>(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<s64>(static_cast<s32>(m)) * half(s, y)) >> 16;
if (x) // SMULWy
m_registers[rd] = static_cast<u32>(static_cast<s32>(product));
else // SMLAWy
m_registers[rd] = accumulate(product + static_cast<s32>(ReadRegisterOperand(rn)));
break;
}
case 2:
{
const u64 accumulator =
(static_cast<u64>(ReadRegisterOperand(rd)) << 32) | ReadRegisterOperand(rn);
const u64 result = accumulator + static_cast<u64>(half(m, x) * half(s, y));
m_registers[rn] = static_cast<u32>(result);
m_registers[rd] = static_cast<u32>(result >> 32);
break;
}
default:
m_registers[rd] = static_cast<u32>(static_cast<s32>(half(m, x) * half(s, y)));
break;
}
}
void ARMCore::ExecuteMultiplyLong(u32 instruction)
{
const bool signed_multiply = (instruction & (1U << 22)) != 0;
+2
View File
@@ -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);
+3 -1
View File
@@ -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;
}
@@ -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<ARMCore*, 2> cores = {&interpreter, &jit};
jit_bus.WriteARM(0, test.instruction);
jit_bus.WriteARM(4, 0xeafffffe); // b .
#else
std::array<ARMCore*, 1> cores = {&interpreter};
#endif
interpreter_bus.WriteARM(0, test.instruction);
interpreter_bus.WriteARM(4, 0xeafffffe); // b .
for (ARMCore* core : cores)
{
core->SetCPSR(static_cast<u32>(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);
+15 -6
View File
@@ -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"
]
}
]
}
+21
View File
@@ -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.