IOS: add x64 Starlet JIT with guarded fastmem

This commit is contained in:
2026-08-28 13:22:30 +02:00
parent b85667435c
commit 9389e38f75
14 changed files with 4835 additions and 19 deletions
+99 -7
View File
@@ -63,8 +63,8 @@ Dolphin CoreTiming (Broadway clock domain, 729 MHz)
+-- WFI idle: 72,900 Broadway cycles --> 24,300 Starlet cycles
|
v
ARMv5TE interpreter
+ software TLB/I-cache
ARMv5TE interpreter or x64 JIT
+ software TLB/I-cache
|
+-------------------------------+------------------------------+
| | |
@@ -118,6 +118,13 @@ X2 and immediately submit the next request with X1 before Starlet is scheduled a
timer, NAND, OHCI, and Wiimote clocks once per 4,096-cycle scheduler slice instead of once per
interpreted instruction; external IRQ delivery remains bounded to about 16.9 microseconds and
WFI polling to 100 microseconds.
- An x86-64 Starlet JIT translates the observed ARM and Thumb integer, branch, interworking and
memory-transfer subset. Its inline generation-tagged TLB and direct fastmem paths cover ordinary
MEM1/MEM2 accesses plus a measured, direction-specific subset of single SRAM reads. Every SRAM
write, register-list transfer, TLB miss, MMIO access, protected boot0 overlay, invalid SRAM
aperture or unsupported instruction remains an architectural side exit: registers are flushed,
the exact interpreter/device operation runs, and the dispatcher re-samples IRQ/FIQ, IPC yield,
CP15 and translation state before another native block executes.
- Big-endian Starlet address space, 96 KiB of physical SRAM (64 KiB bank A plus 32 KiB bank B)
exposed through the hardware's unusual 128 KiB windows, plus shared MEM1/MEM2 access.
- Raw NAND reads, chip identification/status, Wii ECC generation, ECC-enabled page programming
@@ -135,7 +142,9 @@ X2 and immediately submit the next request with X1 before Starlet is scheduled a
read, write-enable/disable, word write/erase, and whole-array write/erase commands in COW memory.
- Hollywood's Starlet GPIO bank, including enable/output/direction/input, ownership, straps,
interrupt level/mask, and write-one-to-clear interrupt flags. POWER and EJECT have their idle
levels and the EEPROM MISO pin remains connected to the existing serial model.
levels and the EEPROM MISO pin remains connected to the existing serial model. Writes to the
public BootMii diagnostic byte on GPIO bits 23:16 are logged as stage codes with the Starlet PC;
this observes the firmware's own progress/panic channel without modifying its control flow.
- Immediate AHM memory-flush acknowledgement, the indirect DDR/SEQ/BIST register banks used by
boot1 training, and the hardware-controlled boot0 ROM overlay/SRAM-bank swap.
- PPC/ARM IPC mailboxes, Starlet-side access to both control registers, and Broadway
@@ -305,6 +314,18 @@ An isolated boot probe using the local, mutually matching dumps has executed thi
`IPC_BOOT2_RUN(1, 2)` immediately. Cold-boot BootMii and HBC -> IOS254 -> MINI -> BootMii both
remain in the original interactive menu, and keyboard-backed GameCube navigation was validated
without modifying `armboot.bin`, `ppcboot.elf`, or `bootmii.ini`.
22. Directional JIT profiling separated slow SRAM reads from writes and counted them per 4 KiB
aperture page. During the IOS-to-Menu phase, about 71% of slow SRAM accesses were writes;
pages `0x00` and `0x19` dominated those writes, while page `0x1e` dominated the remaining
reads. Single reads from the previously validated `0x00`/`0x12`/`0x14`/`0x19` pages and the
newly measured `0x1e` page now use guarded split-bank fastmem. Direct writes to `0x00`/`0x19`
passed synthetic bounds and canary tests but reproducibly stopped BootMii at its public
`hardware setup begin` GPIO marker, so every SRAM write was restored to the exact bus path.
The resulting build passed all 61 targeted Starlet/JIT tests and a fresh cold-boot regression:
BootMii completed hardware setup, the Wii selection re-entered NAND boot2, original IOS released
Broadway 54.1 seconds later, and the populated System Menu rendered without a crash or firmware
patch. The still-dominant exact writes are a measured coherency problem, not permission to skip
their bus semantics.
The probe never prints ROM, NAND, key, or firmware instruction bytes. The committed unit suite
covers ARM-to-Thumb loads into PC, high Starlet exception vectors, privileged `LDM ... ^` user-bank
@@ -354,10 +375,12 @@ The next firmware stages need substantially more hardware fidelity:
enumeration, Bluetooth HCI/ACL, and one paired remote's L2CAP/HID input path are implemented.
- Remaining IOS reload/reset edge cases, device timing, and scheduler accuracy needed by
timing-sensitive original exploits.
- Performance: the Menu's original IOS idle path now runs at full speed through safe RAM/SRAM poll
fast-forwarding. ARM-heavy boot and transient driver workloads still use the interpreter and can
take longer than real hardware; a block cache or ARM JIT remains the next performance frontier
for sustained workloads that do not enter the scheduler's idle loop.
- Performance: the Menu's original IOS idle path runs at full speed through safe RAM/SRAM poll
fast-forwarding, and the x64 Starlet JIT now executes most ordinary ARM/Thumb instructions
natively. Boot and transient driver workloads still cross many architectural side exits for
MMIO, CP15 operations and the not-yet-native instruction subset. Those exits must be reduced by
proven instruction translations or register-specific MMIO stubs, never by treating every device
access as a resumable C helper.
Until those items are implemented, this is an end-to-end experimental LLE implementation rather
than a drop-in replacement for Dolphin's mature IOS HLE mode.
@@ -389,11 +412,80 @@ than a drop-in replacement for Dolphin's mature IOS HLE mode.
- [ARM926EJ-S Technical Reference Manual](https://developer.arm.com/documentation/ddi0198/latest/)
for ARMv5TE, CP15, banked-register, exception, and interworking semantics.
## JIT correctness and BootMii performance model
BootMii is two distinct Starlet programs before its Broadway UI appears. The boot2-style loader
performs Hollywood setup, mounts the external SD controller and loads `/bootmii/armboot.bin`.
MINI then configures the ARM926 MMU/caches and IRQs, initializes NAND, IPC and SDHC, loads
`/bootmii/ppcboot.elf`, writes the Broadway EXI reset stub, releases the reset lines and finally
sleeps in its IPC loop. MINI has no Wi-Fi stack. Consequently, Wi-Fi SDIO traffic observed later
belongs to Nintendo IOS/System Menu startup and is not a valid optimization target for a black
screen inside BootMii.
The public loader also gives deterministic stage telemetry. `debug_output()` writes one byte to
GPIO bits 23:16: `42/43/44` cover entry, stack and BSS setup; `F0/F1` enter and finish loader setup;
`F2/F3` select and hand off the SD payload; `C8/C9` fall back and hand off to NAND boot2; `E3/E4`
are ELF-loader panics. A panic then alternates its error byte with zero around repeated 500 ms
timer delays. Therefore a trace fixed at the loader's `udelay` loop for millions of scheduler
slices is a panic, not evidence that SDHC or the GUI merely needs more time.
The JIT follows the same invariant used by Dolphin's Broadway JIT and QEMU TCG: a slow access can
resume inside a translated block only when all guest architectural state at that instruction is
recoverable and the helper cannot change scheduling, interrupt, translation or code-cache state.
Dolphin flushes registers before its safe slow loads/stores and checks memory exceptions; QEMU
records a host-PC to guest-PC/state map so faults restore the precise instruction boundary. The
Starlet JIT does not yet have that per-instruction recovery metadata. Generic MMIO therefore exits
to the dispatcher. Future performance work can specialize proven side-effect-free registers
(for example read-only status or timer reads), while complex writes, IRQ acknowledgements, IPC and
reset transitions must remain exits.
The ARM926 manual is also explicit about the boundaries relevant here: wait-for-interrupt drains
the write buffer and sleeps until IRQ/FIQ/debug; disabling and re-enabling the MMU preserves TLB
contents; D-cache clean, write-buffer drain, I-cache invalidation and TLB invalidation are separate
CP15 operations. Collapsing all `c7` maintenance into a code-cache flush is both inaccurate and
slow, while ignoring the instruction-cache operations breaks self-modifying boot code.
### IOS syscall execution and evaluated acceleration strategies
IOS has two superficially similar software-exception ABIs. Normal kernel calls are deliberately
undefined ARM words of the form `0xE6000010 | (syscall_number << 5)`. The Undefined vector saves
the complete thread context, extracts bits 12:5, switches to System mode and dispatches through the
IOS-version-specific syscall table. ARM/Thumb `SVC 0xAB` is a separate RealView semihosting ABI;
production IOS retains essentially only debug-string output (`r0 = 4`, string in `r1`). Replacing
either path with host-side IOS HLE would skip the original scheduler, message queues, permission
checks and exploit-relevant kernel behavior, so LLE keeps the guest handlers. The JIT only enters
the architecturally correct Undefined/Supervisor exception directly, avoiding a redundant generic
instruction-decoder fallback.
The acceleration options were evaluated as follows:
| Method | Expected value here | Decision |
|---|---|---|
| Per-instruction interpreter | Reference accuracy, very low throughput | Keep as the exact fallback/oracle only |
| IOS syscall HLE | Very fast for ordinary titles | Reject for original IOS and exploit compatibility |
| QEMU TCG/Unicorn ARM core | Mature ARM system translation | Valuable reference, but integrating Dolphin memory, Hollywood MMIO, CoreTiming, precise cache invalidation and dual-CPU IPC would duplicate most of the current machine model |
| LLVM/whole-function recompilation | Strong global optimization | Excessive compile latency and difficult precise MMIO/exception recovery during boot and self-modifying code |
| Custom basic-block JIT | Shares Dolphin memory and timing directly | Selected; continue with measured native coverage and exact side exits |
| Native ARM predication | Removes common IOS conditional-ALU fallbacks | Implemented for every already-validated data-processing form |
| Native Undefined/SVC entry | Removes decoder fallback at each software exception | Implemented without bypassing the guest kernel handler |
| Direct block chaining | Removes dispatcher lookup on hot edges | Next high-value CPU optimization, provided TLB/I-cache invalidation unlinks every affected edge |
| Wider register allocation / traces | Reduces repeated guest-register loads and stores | Medium-term; add only after branch/memory profiles identify stable hot traces |
| Register-specific MMIO fast paths | Can remove very hot safe status reads | Only after sampled effective-address traces; generic MMIO continuation is forbidden |
This ordering follows the same split documented by
[QEMU TCG](https://github.com/qemu/qemu/blob/master/docs/devel/tcg.rst): RAM/ROM accesses use cached
host offsets, MMIO calls device code, and direct block chains must be removable when translated
pages change. The current microbenchmark reaches roughly two billion simple translated ARM
instructions per host second, while complete IOS traces are orders of magnitude slower. The raw
x86 emitter is therefore not the limiting component; remaining work is native coverage, side-exit
frequency, guest scheduling/idle detection and device-access cost.
## Code map
| Area | Files |
|---|---|
| ARM CPU | `Core/IOS/Starlet/ARMCore.{h,cpp}` |
| x86-64 ARM/Thumb JIT | `Core/IOS/Starlet/ARMJitX64.{h,cpp}` |
| Starlet scheduler/lifetime | `Core/IOS/Starlet/Starlet.{h,cpp}` |
| Address space and devices | `Core/IOS/Starlet/StarletMemory.{h,cpp}` |
| Bluetooth pairing and Wii Remote HID | `Core/IOS/USB/Bluetooth/{BTBase,WiimoteDevice}.{h,cpp}` |