AArch32 (32-bit titles, MK8D): CPU core, status and plan¶
Branch claude/exciting-davinci-unv0aa, Linux cloud session. MK8D does not run yet: only the CPU core exists. Nothing here is wired into
the runtime build (runtime/Makefile does not compile src/a32/), so the Mac build is unchanged.
Why this is a separate feature¶
MK8D is AArch32 (NPDM says 32-bit; its NSO has 32-bit ELF dynamic entries, DT_REL, ARM/Thumb code). Apple Silicon cannot execute AArch32 and the whole pipeline (rewriter, loader, kernel, services) assumes AArch64 guest code at host addresses. So 32-bit titles need: a CPU core (done, below), a 32-bit loader/packager, a guest-address bias, an A32 svc ABI, HLE call marshalling (NVN etc.) and memory layout.
Done and verified (Linux)¶
runtime/src/a32/ portable C: a32.h (CPU state, hooks, a32_run), decoders (a32_decode_arm.c, a32_decode_thumb.c, one shared
A32Insn form), executor (a32_exec.c: integer, branches, loads/stores, exclusives, CP15 TLS/counters, IT blocks), VFP (a32_vfp.c,
a32_vfp_t.h: VFPv4 + half precision, ARM NaN/flush-to-zero/rounding semantics, vmov/vdup core transfers).
Per-cpu decode cache keyed by pc and validated by the raw instruction bits.
Verification: runtime/tests/a32/fuzz.py (needs pip install unicorn) runs one random instruction on this core and on Unicorn/QEMU from the
same random state and compares r0-r15, flags, memory, D registers, FPSCR. Results this session: ARM integer families, Thumb-2, and VFP
data-processing/load-store/transfers show 0 mismatches except UNPREDICTABLE encodings (filtered in unpredictable()). Not compared: sticky
UFC/IXC/IDC bits. Unicorn's CPU model lacks ARMv8 AArch32 additions, so these are unverified: vsel, vmaxnm/vminnm, vrint*, vcvt{a,n,p,m},
lda/stl/ldaex/stlex (use qemu-arm user mode, installed via apt, with a clang-built test program).
Tooling in the container: clang targets ARM (clang -target armv7a-none-eabihf), lld, llvm-mc (use it to disassemble/enumerate NEON encodings).
Not done¶
- NEON:
a32_neon.cis a stub (returns UNIMPL). A large draft (3-same, 3-diff, scalar, shifts, modified immediate, two-reg-misc, vext/vtbl/vdup, all viaa32_neon_f32, which does not exist yet) is inruntime/src/a32/wip/a32_neon.c.wip; its structure load/store part is unfinished scaffolding. The two-register-misc opcode map was enumerated with llvm-mc and is encoded in the draft. - IT blocks,
lda/stl, v8 VFP, exclusives across threads: written, only checked single-step or not at all. Add whole-program tests: compile C with clang for armv7a/thumbv7a, run underqemu-arm, compare with this core (hooks: svc, exit). - Everything outside the CPU core (below).
Plan to MK8D¶
- NEON (finish draft, fuzz
neon_dp,neon_lsfamilies; add NEON structure loads/stores). - Program-level tests against
qemu-arm(IT, calls, floats, atomics). - SVC ABI: Horizon A32 register layout. Algorithm (from Atmosphere
svc_codegen_impl_layout.hpp, checked against libnx stubs): stage 1 is AAPCS over the C prototype with outs counted as pointer args, 4 argument registers on A32, 64-bit values aligned to even registers, overflow to the stack; stage 2 maps register args to the same svc register, stack args to the first free register (64-bit = two chunks), outputs: Result in r0, other outs in the first free output registers. Examples: CreateThread in r1,r2,r3 + priority r0, core r4, handle out r1; WaitSynchronization handles r1, n r2, timeout r0:r3, index out r1; WaitProcessWideKeyAtomic timeout r3:r4; WaitForAddress value r2:r3, timeout r4:r5; GetInfo id1 r0:r3, out r1:r2. Signatures: Atmospheresvc_definition_macro.hpp. Adapter converts to the A64 register layout and callssvc_dispatch. Needs custom handling: QueryMemory (struct), GetInfo address results (subtract bias), GetThreadContext3. - Guest address bias: A32 guest memory is a 4 GiB window at host
BIAS(>= 4 GiB, so raw A32 addresses are < 2^32). Makeguest_ptr()add the bias to addresses < 2^32 (IPC descriptors then work unchanged); the svc adapter pre-biases pointer args for handlers that dereference raw (futex words). A32 TLR must live in the guest window (tpidruro = guest address; TSDruntime_keys[2]= biased pointer). - Memory: A32 process layout (Horizon 32-bit: ASLR ~0x200000..0x40000000, heap 0x40000000.., alias, stack region); replace the memory svc handlers for A32 instead of editing memory.c.
- Loader/packager: Elf32 dynamic parsing in
napple-build(tables.rs), R_ARM_RELATIVE/GLOB_DAT/JUMP_SLOT/ABS32; skip the AArch64 rewriter;napple-compatalready flags 32-bit NPDM as RISK. Test with a clang-built ARM shared object wrapped as an NSO. - Threads:
threads.cthread_main -> an A32 entry (a32_run loop) instead ofruntime_thread_enter; main.c boot calls nn::init::Start via the core. - HLE imports: NVN functions are host functions returned by
nvnBootstrapLoader. For A32 return addresses in[hle_lo,hle_hi); thehlehook marshals AAPCS args (r0-r3 + stack; 64-bit pairs; pointers + bias) into the host function. The signatures are all ingfx/src/nvnapi/nvn_protos.h(generated typedefs): generate a signature table from it. Structs with pointers differ between ABIs (e.g. NVNshaderData) and need custom shims. Float ABI (hard vs soft) of Horizon A32 is unknown: assume hard-float, make it a switch. - Performance: this is an interpreter (decode cache). MK8D at speed will need an A32->A64 translator over the same
A32Insn(AOT in napple-build or a JIT on macOS; iOS allows only AOT). Do not start before the interpreter boots the game.