IOS LLE: checkpoint NAND conformance and diagnostic tooling

Preserve formatted NAND boot and Wii Remote pairing, add DMA/SRAM conformance tests and a Wii comparison probe. Keep P1 profiling tools and native update A/B evidence; reject the unhelpful targeted SRAM shortcut.
This commit is contained in:
Yaya48 committed 2026-09-23 13:30:17 +02:00
1 parent b931671fde
commit c230971f07
64 files changed
+6488 -238

No files matched your search

+45
View File
@@ -30,6 +30,15 @@ CPUThread = True
WiiIOSLLEDumpPath = C:/absolute/path/to/dumps
```
Detailed JIT memory profiling is optional and disabled by default. To collect slow-access
regions, SRAM page counts and sampled hot addresses, set `WiiStarletJITMemoryProfiling = True`
under `[Core]` before starting emulation. The launch script exposes the same choice through
`-ProfileJitMemory` and explicitly disables it when that switch is absent. Aggregate slow-read,
slow-write and instruction counters remain active in both modes. A log field of
`memory-profile=false` means the detailed region counts are unavailable, not that there were no
accesses. The setting is fixed for the core's lifetime and does not alter guest memory, timing,
interrupts, or NAND persistence. It is host instrumentation and is not serialized in savestates.
The selected directory must contain the three matching files from one console:
| File | Exact size | Purpose |
@@ -105,6 +114,29 @@ X2 and immediately submit the next request with X1 before Starlet is scheduled a
## Implemented hardware
### Freshly formatted NAND and controller pairing
Automatic Wii Remote pairing is optional at startup when `/shared2/sys/SYSCONF` or
its `BT.DINF` entry is absent. This permits boot0/BootMii to run after a formatter
without the host creating configuration files or restoring data from an older
SFFS superblock. Native Wii software remains responsible for initializing its NAND;
the emulated remotes use their default identities until pairing is established.
Existing pairing records are still reused or populated as before. Unreadable NAND,
malformed directories/configuration and failed journal writes remain errors.
This removes a host-side startup blocker; it does not guarantee that the formatter
installed a complete, bootable Wii system.
When native first-boot setup creates a new SYSCONF after formatting, a full Dolphin
restart lets the existing startup pairing routine populate its empty BT.DINF.
The write must use the native SFFS authentication layout: the complete 20-byte HMAC
starts at spare byte 1 of cluster page 6, with a second copy split between page 6
bytes 21-32 and page 7 bytes 1-8. Bad-block markers and unrelated spare bytes are
preserved, and ECC is regenerated for the ciphertext. The pairing regression test
checks both HMAC copies and spare/ECC preservation; valid decryption alone does not
prove IOS will accept the modified file.
### Devices and execution
- ARM and Thumb instruction interpretation needed by the observed boot, including banked CPU
modes, user-bank block transfers, SPSRs, high Starlet exception vectors, IRQ/FIQ entry,
ARM/Thumb interworking, ARMv5TE branches/multiplies, and CP15 registers.
@@ -506,6 +538,19 @@ frequency, guest scheduling/idle detection and device-access cost.
| Configuration | `Core/Config/MainSettings.{h,cpp}` |
| Starlet CPU/device unit tests | `UnitTests/Core/IOS/Starlet/ARMCoreTest.cpp` |
## Conformance work
The current priority is behavioral agreement with a real Wii, not a fixed FPS
target. [`Tools/WiiConformanceProbe`](../Tools/WiiConformanceProbe/README.md)
builds one PPC homebrew binary to run against native IOS on both systems and
compares numbered return codes and outputs. Keep the probe binary identical,
record the IOS revision and starting NAND state, and label physical-Wii versus
emulator captures explicitly. Local captures are not automatically treated as
hardware ground truth. When a result differs, add a minimal deterministic unit
test for the relevant IPC, memory, timing, or device behavior before changing
the emulator. Performance optimizations must preserve those observations and
the BootMii → Wii boot path.
## Safe development rules
1. Treat all dumps as private console material and never print keys or ROM/NAND bytes in logs.
@@ -0,0 +1,291 @@
# Protocole P1 — attribuer les ralentissements Wii LLE
Date : 19 septembre 2026. Statut au 22 septembre : **capture noyau locale validée ;
coût du collecteur, cas PPC et chemin critique Wii non qualifiés**.
Aucune nouvelle mise à jour n'est lancée par la rédaction de ce document.
Il remplace la stratégie « modifier, refaire une mise à jour, comparer les FPS ».
## 1. Question et règle de décision
Sur les passages réellement lents, déterminer **quel travail ou quelle attente retarde
la progression de l'émulation**, dans quelles fonctions, et pour combien de temps.
Le résultat attendu n'est pas « un cœur à 100 % », mais une attribution vérifiable.
Une seule exécution de diagnostic est prévue après qualification du dispositif.
Aucun nouveau correctif de performance avant un rapport d'attribution exploitable.
Une capture incomplète ne justifie pas un correctif au hasard : on répare le dispositif,
et on précise quelle information manque avant d'envisager une capture complémentaire.
Une seule cause est ensuite traitée ; la validation porte d'abord sur sa charge locale.
## 2. Pourquoi A–F ne suffisent pas
- Les instantanés espacés d'environ 10 secondes ne décrivent ni les pics ni leurs piles.
- Les FPS échantillonnés ne sont pas une distribution des temps de frame.
- Un nombre d'accès mémoire ou d'instructions JIT ne mesure pas leur coût CPU.
- `ARMCore::RunCycles` contient également `m_bus.AdvanceCycles` : son temps inclusif
ne doit pas être présenté intégralement comme du calcul ARM.
- `AdvanceCycles` appelle les périphériques, dont OHCI et le réseau : additionner
ces temps imbriqués compterait plusieurs fois le même travail.
- `Sampler.cpp` utilise `SuspendThread`/`ResumeThread` ; ses observations historiques
ne deviennent pas une référence de temps sans perturbation.
- Le lecteur F ne résout pas certaines adresses JIT. Une annotation absente n'est
ni zéro travail JIT ni une preuve que tous les autres offsets sont incorrects.
- Les 600–2 700 Mo de journal sont des repères approchés, pas une charge identique.
- La durée entre messages utilisateur n'est pas la durée exacte de la mise à jour.
- Les conditions hôte/serveur diffèrent. F est observé plus lent que E, mais la cause
n'est pas isolée. Répéter simplement F avec un autre réglage ne résout pas cela.
## 3. Geler le scénario, sans toucher aux NAND existantes
Le diagnostic concerne le ralentissement actuel : source actuelle figée correspondant
au comportement F, profilage détaillé des accès désactivé, **sans nouvelle optimisation**.
Créer une build de diagnostic propre, non incrémentale, et ne pas remplacer les builds
figées D/E/F. Conserver le patch et les empreintes des sources, y compris les fichiers
non suivis utilisés, options de compilation, compilateur, EXE, PDB et paramètres.
Vérifier l'identité EXE/PDB ; cela ne suffit pas, à elle seule, à valider un lecteur de types.
Utiliser un nouveau profil depuis la graine pré-mise à jour déjà vérifiée :
SHA256 `07b2f4bc543cfd4203efc448538505e9d503b2727c16aaff8d8c0b286d8b4f0c`.
Conserver les mêmes réglages Wii, réseau NAT, rendu, vitesse 100 %, horloges invitées,
SD et persistance. Noter les différences de chemins et rien d'autre.
Un démarrage du processus n'implique pas un cache disque Windows froid ; ne pas vider
les caches système, changer l'affinité ou désactiver l'antivirus pour fabriquer un résultat.
Relever au départ et pendant la capture : charge des autres processus, CPU logique utilisé,
état d'alimentation, fréquence disponible, mémoire/pression de pagination, disque et GPU.
Ne pas lancer de compilation, benchmark, hachage massif, nettoyage ou autre test en parallèle.
Ne pas fermer des applications utilisateur sans accord. Si un concurrent perturbe une fenêtre,
la signaler et mesurer sa contribution plutôt que l'effacer du rapport.
## 4. Deux couches de mesure sur la même horloge
### A. Profil système Windows, indépendant des layouts C++
WPR, Xperf et WPA sont présents sur cette machine ; `wpr -profiles` a été vérifié.
Employer un profil ETW ciblé, validé avant le test :
- CPU échantillonné avec piles, cible initiale 1 ms, intervalle réellement utilisé consigné ;
- changements de contexte et réveils, pour séparer **Running / Ready / Waiting** par thread ;
- chargement des modules, événements disque/fichiers, fautes matérielles de pagination ;
- métadonnées d'activité réseau, sans contenu des paquets ;
- fréquence/puissance si disponibles. Une donnée absente reste « indisponible ».
Les vues CPU échantillonné et CPU précis répondent à des questions différentes : localisation
du calcul d'une part, ordonnancement/attentes d'autre part. Les combiner, sans confondre une
pile d'attente avec une preuve d'attente disque. [Documentation Microsoft](https://learn.microsoft.com/en-us/troubleshoot/windows-server/support-tools/support-tools-xperf-wpa-wpr)
Ne plus utiliser de suspension de thread ou d'attachement au débogueur pour chronométrer.
Pas de profilage heap complet, trace de chaque accès mémoire, ni capture de paquets par défaut.
Filtrer l'analyse sur le PID/TID exact et conserver les processus concurrents pour expliquer
le temps Ready. Ne pas supposer que le PID suffit après un redémarrage : enregistrer sa date
de création. Aucun arrêt/annulation d'une session ETW tierce ; vérifier les sessions avant démarrage.
### B. Télémétrie native légère, versionnée
Publier les compteurs depuis le code qui les possède ; ne plus les déduire d'offsets PDB
via `ReadProcessMemory`. Un schéma versionné comporte `run_id`, version, QPC/fréquence,
PID/TID, génération du cœur/rechargement IOS et des compteurs monotones.
L'export asynchrone est borné ; les débordements sont comptés. Aucune écriture de log
synchrone à chaque instruction ou accès. Ne pas ajouter de verrou partagé au chemin mémoire.
Récupérer les données sur leur thread propriétaire, sans lecture C++ concurrente non synchronisée.
- Temps invité écoulé et cycles ARM/PPC, séparés du temps hôte QPC.
- Temps de frames réellement produites : histogramme p50/p95/p99 et longues frames,
rapporté au mode vidéo du profil (50 ou 60 Hz). Une médiane de FPS n'est pas ce calcul.
- Agrégats toutes les secondes ; chronologie d'analyse en fenêtres de 250 ms quand
les événements le permettent. Marquer les fenêtres qui n'ont pas assez de données.
- JIT : compilations, invalidations, instructions natives/fallback, accès lents existants,
entrées en attente et sorties de slice, avec identifiant de génération après reset.
- NAND : lectures/programmes/effacements, transactions, octets et erreurs ; séparer
sérialisation/hash, appels d'écriture, `IOFile::Flush` et attente hôte observée.
- Réseau : octets/trames hôte reçus, mise en file NAT, remise USB à IOS, occupation maximale
et âge des files. Le débit reçu ne prouve pas le débit consommé par IOS.
Les chronomètres de régions fréquentes sont échantillonnés et calibrés, jamais posés sur
chaque accès ARM. Les frontières plus rares (rechargement, compilations, gros transferts)
peuvent produire des événements. Éviter un échantillonnage périodique aligné sur la boucle
invitée ; conserver le taux de sélection et distinguer estimation et total exact.
### C. Rendre les échantillons JIT attribuables
Un PDB décrit le C++ compilé, pas automatiquement le code généré à l'exécution.
Exporter, à compilation/invalidation, les intervalles d'adresses **hôte** du JIT ARM et
du JIT PPC, dispatcher/trampolines compris, avec QPC et génération ; pour les blocs,
indiquer PC invité, ARM/Thumb et identité du mapping. Ne pas extraire ni publier le firmware.
Une adresse réutilisée après vidage du cache doit se résoudre selon la date de l'échantillon.
Classer l'IP feuille même si la pile ne traverse pas correctement une transition JIT/C++.
Ne pas inventer de parent de pile manquant. Garder « pile tronquée » et « code non attribué »
visibles ; ajouter une instrumentation ciblée si ces trous empêchent la décision.
Le support `Common::JitRegister` existant côté PPC est un point d'intégration à examiner,
pas une garantie que le JIT ARM ou l'unwinding Windows sont déjà couverts.
## 5. Points d'attribution dans ce dépôt
| Domaine | Points à observer | Preuve recherchée |
|---|---|---|
| Ordonnancement ARM | `Starlet::RunSlice`, `ARMCore::RunCycles` | Coût de dispatch, quanta/s, transitions, temps invité/hôte |
| JIT ARM | `ARMJitX64::Run`, compilation, code généré, helpers | Part CPU feuille, blocs invités chauds, recompilations |
| Mémoire ARM | `ReadMemorySlow`, `WriteMemorySlow`, traduction, bus | CPU exclusif des helpers et classes d'accès, pas leur seul nombre |
| Périphériques | `StarletMemory::AdvanceCycles`, DMA, AES/SHA, OHCI/EHCI | CPU exclusif par fonction ; attente simulée séparée du travail hôte |
| NAND | `CommitNANDPages`, `NANDJournal::Append`, `IOFile::Flush` | CPU de préparation vs appels fichiers vs attente disque réelle |
| Réseau | `SlirpNetwork::Poll`, AX88772, remise OHCI | CPU NAT/USB, files, progression hôte versus progression invitée |
| PPC / rendu | JIT PPC, threads vidéo, présentation | Concurrence réelle et dépendances de l'émulation, pas seulement FPS |
| Hors émulation | Limiteur, audio, logs, collecteur, Windows | Attente voulue, contention, CPU de diagnostic, temps Ready |
Sur un même thread, présenter les **temps exclusifs** des régions imbriquées.
Présenter aussi leurs temps inclusifs, mais dans une autre colonne, non additionnable.
Ne pas additionner le CPU de plusieurs threads comme une durée murale de mise à jour.
Pour les attentes, suivre la dépendance qui retarde le thread utile ; un thread GPU qui
attend l'émulation n'est pas une cause de ralentissement GPU.
`fflush` n'est pas une preuve de vidage physique sur disque : le niveau de persistance
actuel reste inchangé et les attentes sont établies par les événements hôte.
## 6. Qualification obligatoire, sans demander de mise à jour à l'utilisateur
Avant de dire « lance la mise à jour », un contrôle local court doit démontrer :
1. EXE/PDB identifiés ; compteur versionné lu correctement ; horloges alignées ; un reset
ne produit pas de deltas négatifs ; pas de perte silencieuse d'événements.
2. Une boucle JIT connue est correctement attribuée ARM/PPC ; son coût CPU augmente
comme prévu. Un cas d'attente connu est classé Waiting, pas « calcul JIT ».
3. Les scopes imbriqués d'un test ont des temps exclusifs cohérents, sans double comptage.
4. Un cas de journal synthétique et de réseau local, sans service Nintendo, est visible.
5. Mesurer le surcoût ON/OFF du dispositif complet sur la même charge locale, dans le même
binaire et en ordre alterné. Budget initial : **2 %** de temps mural/CPU, bruit affiché.
Ce seuil est une règle de projet, pas une garantie d'ETW. S'il est dépassé ou non résolu
par le bruit, alléger les fournisseurs/samples avant le test. Le contrôle ne garantit
pas le même surcoût sur toutes les phases natives : relever aussi la charge du collecteur.
Pas de nouveau comportement invité, pas de changement de NAT, timers, JIT, NAND ou SVC.
Les boucles SVC/polling éventuelles sont d'abord attribuées à du CPU/PC invité ; aucune
émulation accélérée d'appel IOS ne découle d'un simple comptage d'instructions.
## 7. Une capture native, puis analyse hors ligne
1. Vérifier réserve disque et absence de test/build actif. Enregistrer le manifeste.
2. Armer ETW et la télémétrie **avant** de lancer le profil isolé ; confirmer une fenêtre
visible et des mesures valides. Ne pas se baser sur les messages utilisateur pour armer.
3. L'utilisateur lance une seule mise à jour. Garder 10 s de référence avant l'activité.
Tout marquage manuel reste approximatif ; les phases sont reconstruites à partir des
événements synchronisés, files réseau, travail invité et commits NAND.
4. Téléchargement, calcul et installation peuvent se chevaucher : ne pas leur imposer
trois phases exclusives artificielles. Une file vide ne prouve pas une attente serveur.
5. Garder environ 10 s après la réussite signalée, puis arrêter uniquement la capture.
Ne pas arrêter Dolphin, modifier la NAND ou cliquer à la place de l'utilisateur.
6. Garde-fous initiaux : 15 min de capture, budget disque 4 Gio et réserve 5 Gio ; le pilote
doit arrêter proprement **sa** capture si une limite approche, conserver le préfixe et
déclarer les phases manquantes. Une trace incomplète n'est pas un échec de la mise à jour.
7. Vérifier les événements perdus et le coût du profilage. Ne pas charger WPA ni effectuer
une grosse analyse/hachage pendant la mesure. Fermer ensuite Dolphin avec l'utilisateur
pour les vérifications NAND, comme pour F.
Les traces en mode fichier sont adaptées à ce scénario contrôlé, mais ajoutent des E/S :
les distinguer du journal NAND, relever le volume et ne pas supposer leur coût nul.
Les pertes d'événements rendent certaines conclusions impossibles ; les vérifier explicitement.
[Sessions ETW/WPR — Microsoft](https://learn.microsoft.com/en-us/windows-hardware/test/wpt/sessions)
## 8. Rapport exigé avant tout correctif
Analyser la capture entière et, au minimum, trois fenêtres lentes non chevauchantes de
10 s si elles existent, ainsi qu'une fenêtre rapide de la même exécution. Une fenêtre lente
est définie avant analyse : vitesse invitée moyenne < 90 % pendant au moins 2 s ; analyser
aussi les longues frames indépendamment. Rapporter l'absence de fenêtres admissibles.
Comparer des types d'activité similaires ; ne pas comparer une pause réseau à une installation.
Livrer :
- Chronologie superposant vitesse/frames, CPU Running/Ready/Waiting, NAND et files réseau.
- Top fonctions/piles, part CPU exclusive/inclusive, nombre d'échantillons, contexte invité.
- Temps critique perdu en calcul, Ready et attentes expliquées ; catégories inconnues visibles.
- Coût par unité de travail quand disponible : CPU/transaction, CPU/Mio traité, compilations
par instruction, etc. Un compteur n'a de sens que pour la même opération/sémantique.
- Liste des observations qui réfutent les autres pistes ; distinguer preuve et hypothèse.
Gates de qualité initiaux : zéro perte sur les flux indispensables à l'intervalle analysé,
au moins 95 % des échantillons CPU Dolphin attribués à un domaine, compteurs intègres et
coût de collecte qualifié. Ce sont des critères d'analyse, non des promesses de précision.
Une pile incomplète peut permettre de classer un domaine sans identifier sa cause interne :
on ne valide alors pas un correctif visant une fonction non résolue. Pas de fausse certitude
statistique obtenue en traitant des échantillons temporels corrélés comme des essais indépendants.
Un correctif est admissible seulement si :
1. La cause apparaît dans plusieurs fenêtres lentes avec une signature explicable.
2. Sa part sur le chemin qui retarde l'émulation est chiffrée, avec son incertitude.
3. Un cas local représentatif reproduit cette charge ou cette attente.
4. Le gain global plausible est estimé avant modification. Par exemple, supprimer tout un
poste responsable de 5 % du temps critique ne retire au mieux que 5 % de cette durée.
5. Le correctif garde le comportement matériel et passe les tests ciblés/interpréteur.
Décision pratique : si le CPU est dans les helpers, travailler ces helpers ; s'il est
dans le code invité, examiner les blocs et le travail IOS ; si Ready domine, étudier
la contention hôte ; si les attentes d'E/S dominent, traiter leur dépendance réelle.
Si les files NAT sont pleines mais peu consommées, ne pas accuser le serveur ; si elles
sont vides, confirmer l'attente avec les échanges/états disponibles ou laisser la cause ouverte.
Si aucun poste ne domine, le rapport le dit : on ne fabrique pas un unique responsable.
## 9. Validation minimale après diagnostic
D'abord une validation locale répétable du poste identifié, résultat invité exact et même
charge. Puis une validation native ciblée du scénario corrigé, pas une série de réglages.
La trace de diagnostic peut localiser une cause, mais son temps n'est pas automatiquement
une référence équitable face à une build sans instrumentation. Pour revendiquer un gain
global, conserver un dispositif équivalent et répéter seulement si la variabilité observée
empêche la conclusion. Annoncer cette nécessité et sa raison, pas un nouveau test à l'aveugle.
L'objectif est de réduire les essais utilisateur ; garantir à l'avance qu'un seul essai
prouvera toute causalité serait trompeur.
## 10. Artefacts et limites
Conserver dans un nouveau répertoire local ignoré : manifeste, capture ETL, télémétrie
versionnée, mapping JIT daté, coût de collecte, tableaux de fenêtres, piles chaudes,
rapport des attentes, événements perdus et décision. Le rapport lisible va dans `docs`.
Pas de clés NAND, mémoire brute invitée, mots de passe ou contenu réseau dans les exports.
Les ETL peuvent contenir des chemins et noms de processus : conservation locale, pas de push.
État au 22 septembre 2026 : instrumentation opt-in `DOLPHIN_PERF_TRACE=1`, mapping
temporel JIT ARM/PPC, scopes/counters versionnés, profil WPR ciblé, décodeur ETL et
sonde locale ont été ajoutés. La build isolée passe et la sonde privée a validé
13 contrôles locaux sur 13 sur deux captures, avec zéro perte d'événement.
Sur deux paires alternées, la phase ARM avec trace privée dépasse la phase sans
trace d'environ 1,7 % en moyenne, mais la dispersion est supérieure à cet écart.
Le budget de 2 % n'est donc pas qualifié. Le démarrage/arrêt du logger privé
ajoute environ 0,34 s à ces exécutions courtes ; ce coût ne mesure pas le
surcoût du collecteur WPR pendant une mise à jour Wii.
La session WPR ne démarrait pas dans le jeton Windows de cette tâche
(`0xc5585011`, droit de profilage système absent). Une capture synthétique
administrateur a depuis produit l'ETL `P1-local-004`, sans perte. L'analyse
hors ligne, reproductible sous PowerShell 5.1, valide 9 427 échantillons CPU,
694 context-switches, dix ancres QPC alignées à cinq ticks près, et le sommeil
connu effectivement classé `Waiting`. Le PDB local résout les fonctions C++;
le mapping temporel attribue les IP du JIT ARM. Les 112 événements Xperf
invalides sont des métadonnées `ImageID` hors du PID cible. Voir
[`Tools/PerformanceTrace/README.md`](../Tools/PerformanceTrace/README.md).
Le lot WPR `P1-overhead-001` a terminé ses huit charges synthétiques sans perte
ETW ; seul son calcul final a échoué faute de Python dans le PowerShell
administrateur. Le calcul récupéré hors ligne donne quatre écarts ARM ON/OFF de
+9,7 %, +25,0 %, -0,5 % et +1,0 %, tandis que les quatre bases OFF ont une
étendue de 34,5 %. Cette variabilité interdit d'annoncer un coût fiable du
collecteur ou de qualifier le seuil de 2 %. Le script vérifie maintenant Python
avant toute capture et peut analyser un lot déjà enregistré avec `-AnalyzeOnly`.
Les traces ON montrent en outre un indexeur C++ hôte (`cpptools.exe`) actif
pendant chaque essai et des services Dell intermittents. Le profil a été allégé
des piles `ReadyThread`, tout en conservant leurs événements ; sa nouvelle
version n'est pas encore qualifiée par un lot ON/OFF stable.
Le second lot avec ce profil (`P1-overhead-002`) a conservé zéro perte ETW mais
36,5 % d'étendue entre bases OFF. L'analyse par processeur montre que la sonde
rapide passe 78,7 % de ses échantillons sur les logiques 0–3 contre 28,9 % pour
la sonde la plus lente ; les temps ARM correspondants sont 7,59 et 10,33 s.
L'ETL identifie un i7-1255U hybride avec SMT sur 0–3. Le lanceur Wii fixait
déjà Dolphin à ces processeurs, mais les sondes OFF/ON ne l'étaient pas.
L'affinité a été ajoutée aux sondes et vérifiée localement (masque 15 observé).
Quatre bases fixées gardent toutefois 8,3 % de dispersion ARM ; elles ne
valident pas encore le seuil initial de 2 %. Il serait trompeur de demander un
troisième lot administrateur sans résoudre cette variabilité résiduelle.
Les événements natifs ne sont pas activés dans les profils utilisateur par défaut.
Une build et une sonde locale réussies ne valident pas le budget de 2 % ni le
comportement d'une mise à jour native. Le vérificateur local conserve
`native_update_ready=false` tant que les gates PPC, chemin critique natif et
coût du collecteur n'ont pas été validées.
**Ne pas demander de nouvel essai utilisateur tant que ces gates ne passent pas.**
+182
View File
@@ -0,0 +1,182 @@
# Wii IOS LLE : première trace CPU native de mise à jour
Date : 2026-09-23 (Europe/Paris). Cible : atteindre à terme environ 60 FPS
pendant une mise à jour système, sans raccourci HLE ni régression BootMii/HBC.
Ce document rapporte une **mesure exploratoire**, pas un gain de performance.
## Périmètre et qualité
- NAND : copie isolée `.starlet_update_ab_20260913/P1-trace-002`, créée depuis
le journal pré-mise-à-jour vérifié (31 fichiers, SHA du seed
`07B2F4BC543CFD4203EFC448538505E9D503B2727C16AAFF8D8C0B286D8B4F0C`).
`.starlet_user3` et les anciens essais A–F n'ont pas été modifiés.
- Build diagnostic figée : `.starlet_update_ab_20260913/build-P1-trace-002` ;
exécutable SHA-256 `D809E04E2E8E900DB06D3813A67805E66EADB2BB79924CB8E805CB724360EAD4`.
- ETL source : `.starlet-profile/P1-native-update-001/capture.etl` (3,88 Gio),
session WPR de 600 s. Le champ `EndUTC` du manifest inclut environ 100 s de
fusion de la trace : ce n'est **pas** la fin de la collecte.
- Analyse : `.starlet-profile/P1-native-update-001-update-window/`, Xperf limité
à 2026-09-22 22:57:00–23:00:33 UTC. Les échantillons cibles couvrent en fait
**114,15 s** seulement, jusqu'à l'arrêt de la collecte vers 22:58:52 UTC.
Les premières écritures NAND de mise à jour sont observées vers 22:57:23 UTC.
La fenêtre CPU comprend donc aussi environ 23 s avant cette première écriture.
La fin de la mise à jour, confirmée par l'utilisateur, n'est donc pas profilée.
- TDH : 780 781 événements natifs décodés, zéro perte de buffer/événement et
zéro erreur de décodage. Fusion Xperf : 25 419 ancres QPC, erreur maximale
cinq ticks (QPC 10 MHz), 128 329 échantillons CPU sur 18 threads, 549 607
changements de contexte retenus, zéro événement cible invalide. Les 57
événements Xperf invalides sont hors PID cible.
- Le surcoût du collecteur WPR en mise à jour native n'est pas qualifié par le
lot synthétique précédent ; ces FPS ne constituent pas un A/B sans trace.
## Observations dans la fenêtre CPU retenue
| Mesure | Valeur |
| --- | ---: |
| Images « produced », intervalle médian | 21,16 ms (≈47,3 FPS) |
| Images « produced », p95 | 35,33 ms (≈28,3 FPS) |
| Images « produced », p99 | 68,58 ms (≈14,6 FPS) |
| Thread principal TID 28212 | 95 154 / 128 329 échantillons (74,2 %) |
| Thread GPU TID 13804 | 16 125 / 128 329 (12,6 %) |
| Thread audio TID 22388 | 5 095 / 128 329 (4,0 %) |
| JIT ARM / JIT PPC, échantillons de feuilles | 27 817 / 16 450 |
| Instructions ARM, delta de snapshots | 4 566 790 374 |
| Instructions ARM JIT natives, delta | 4 522 113 258 |
| Accès lents JIT lecture / écriture, deltas | 59 486 963 / 218 257 365 |
| Fallbacks ARM, delta | 42 433 964 |
Les symboles hôte les plus visibles sur le thread principal comprennent
`StarletMemory::Read32` (3 246 échantillons),
`ARMCore::TranslateVirtualAddress` (2 909), `StarletMemory::Write32` (2 613),
`PowerPC::MMU::WriteToHardware` (2 420) et
`StarletMemory::CalculateNANDECC` (1 918). Ce sont des **feuilles** : leur somme
ne décrit pas une pile ni un chemin critique. Le thread GPU occupe aussi du
CPU, mais il ne domine pas ce relevé. Les 218 millions d'écritures lentes
JIT sont un compteur d'appels, pas 218 millions d'accès NAND.
Les compteurs de scopes échantillonnés distinguent NAND, journal, NAT, DMA,
ARM/JIT et périphériques, avec zéro erreur de comptabilité. Ils mesurent du
temps mur de tranches sélectionnées 1/1024, pas une durée CPU exhaustive ; ils
ne justifient donc pas d'affirmer qu'un autre poste est nul. Les événements
`Frame` et `Snapshot` natifs couvrent toute la session alors que les
échantillons CPU Xperf sont filtrés ; les chiffres de FPS ci-dessus viennent
de `cpu-threads-phase2.json`, qui borne **les deux** à la même fenêtre QPC.
## Décision
La première minute de mise à jour est surtout limitée par le thread principal
d'émulation, mais aucun helper unique mesuré ne démontre à lui seul les ~21 %
de réduction de temps par image nécessaires pour passer de la médiane observée
à 60 FPS. Un simple gain de microbenchmark ECC ou TLB ne doit pas être présenté
comme un correctif 60 FPS. Les écritures lentes sont le candidat à qualifier :
le fast path direct SRAM écrit avait déjà régressé BootMii, donc élargir son
masque sans savoir quelles adresses dominent serait risqué.
Prochain essai borné : la nouvelle copie initiale `P1-trace-003` est préparée
et vérifiée. Activer `-ProfileJitMemory` dans cette copie seulement et relever
dans `dolphin.log`
les compteurs RAM/SRAM/MMIO et les adresses les plus fréquentes pendant le même
début de mise à jour. Ce mode ajoute du coût et ne sert qu'à **classer les
adresses**, pas à comparer ses FPS à la présente trace. Ensuite seulement,
choisir une optimisation limitée à une famille d'adresses, ajouter des tests de
fidélité BootMii/HBC/NAND et réaliser un A/B sans instrumentation sur deux NAND
identiques. L'objectif de 60 FPS reste ouvert.
Le lancement du relevé ciblé est manuel depuis un PowerShell interactif, car
une instance créée par l'outil de commande de l'agent s'est retrouvée invisible
sur le bureau utilisateur :
```powershell
cd C:\Users\tpetkovic\Desktop\dolphin
.\Run-Wii-IOS-LLE.ps1 `
-BuildDirectory .starlet_update_ab_20260913/build-P1-trace-003 `
-UserDirectory .starlet_update_ab_20260913/P1-trace-003 `
-Ethernet -ProfileJitMemory
```
Fichiers de résultat locaux : `decoder-summary.json`, `xperf-summary.json`,
`native-report.json`, `cpu-threads-phase2.json` dans le répertoire d'analyse
indiqué ci-dessus. Un export Xperf complet, interrompu car disproportionné,
a été supprimé ; l'ETL source et l'export ciblé sont conservés.
## Relevé ciblé `P1-trace-003` : écritures SRAM
La copie isolée `P1-trace-003` a été démarrée avec `-ProfileJitMemory`. La mise
à jour est allée jusqu'au redémarrage/menu Wii (confirmé par l'utilisateur).
Le journal a cessé de croître à 2 763 867 232 octets. L'analyse en lecture
seule valide ses 651 690 transactions et 165 568 pages finales superposées.
Le SYSCONF est valide ; les 9 contenus du menu v514 et les 15 d'IOS80 v6944
correspondent à leurs empreintes TMD. SHA-256 du journal :
`49b19327a9b0d7c3aa08c4d59e81365aa17b82ef7452939c07281cbd499b5b56`.
Cela ne constitue pas une vérification exhaustive de tous les titres.
Entre les snapshots JIT de 11:57:06.905 (début des écritures de mise à jour)
et 12:07:12.622 (fin des écritures), soit environ 606 s de temps mur :
| Compteur d'accès ARM lents | Delta |
| --- | ---: |
| SRAM | 1 024 033 027 (78,74 % des accès classés) |
| MMIO | 255 829 918 |
| Autres | 20 598 539 |
| RAM | 0 |
| Lectures, toutes régions | 292 651 794 |
| Écritures, toutes régions | 1 007 809 690 |
| Écritures SRAM | 971 531 207 (96,4 % des écritures lentes) |
Les pages d'aperture SRAM `0x00` et `0x19` portent respectivement
556 332 042 et 348 178 857 écritures lentes, soit **93,1 % des écritures
SRAM** de cette fenêtre. L'alias bas n'augmente pas : ce trafic passe par
l'alias haut. Les adresses qui reviennent le plus dans l'échantillonnage
1/256 sont notamment `0xffff97a0` et `0xfffe03c0` en écriture ; les lectures
MMIO comprennent `0x0d800038` et `0x0d80003c`. Le classement par page est
exact dans les compteurs, tandis que les adresses individuelles ne sont que
des échantillons (huit adresses au maximum par snapshot).
**Conséquence :** les écritures SRAM sont un candidat ciblé, pas un gain de
FPS déjà établi. Ce run est instrumenté et ne doit pas être comparé en vitesse
au run natif `P1-trace-002`. Le JIT conserve volontairement les écritures
SRAM sur le bus exact : une ancienne écriture directe, même limitée aux pages
`0x00`/`0x19`, avait bloqué BootMii à son initialisation matérielle. Avant de
réintroduire un fast path, il faut expliquer cette divergence et vérifier
boot0/split/alias/endianness ainsi que le cycle BootMii → menu → HBC. Ensuite,
seul un A/B non instrumenté sur deux copies identiques peut qualifier le gain
sur la mise à jour réelle et la cible de 60 FPS.
## Essai SRAM ciblé : rejeté après A/B natif
Un candidat a court-circuité le bus dans le helper JIT uniquement pour les
stores alignés de 32 bits sur les pages SRAM hautes `0x00` et `0x19`, avec
gardes boot0/split et chemin bus conservé ailleurs. Le test dédié des gardes
et **153 tests** `Starlet*` ont passé. BootMii puis Wii ont démarré dans le
profil isolé `P1-trace-004`. HBC est absent de cette NAND ; ce trajet n'a pas
été testé. Le binaire candidat figé (SHA-256
`B5F3C3BA1D6EE2C86E5A3EB685F3B3D5FCEF2674C51EA137B697903F9E210006`)
reste dans `build-P1-trace-005` pour reproduire l'essai, mais le raccourci a
été **retiré du source courant** après les mesures ci-dessous.
`P1-trace-005` (candidat) et `P1-trace-006` (témoin, exécutable SHA-256
`D809E04E2E8E900DB06D3813A67805E66EADB2BB79924CB8E805CB724360EAD4`)
partaient des mêmes 31 fichiers, d'un journal vérifié de 533 705 344 octets
et de la même configuration, hormis les chemins isolés. Le profilage détaillé
était désactivé dans les deux builds. Une mesure hôte légère a relevé toutes
les 10 s la taille du journal et le temps CPU du processus, sans lire la
mémoire invitée ni injecter d'entrée. Les durées ci-dessous interpolent les
franchissements des **mêmes** seuils de 600 Mo et 2,7 Go ; l'incertitude
temporelle reste de l'ordre de l'intervalle d'échantillonnage.
| Mesure 600 Mo → 2,7 Go | Candidat | Témoin |
| --- | ---: | ---: |
| Temps mur interpolé | 434,7 s | 402,4 s |
| Temps CPU du processus | 501,5 s | 459,8 s |
| Journal final | 2 763 867 232 octets | 2 733 507 520 octets |
Les deux mises à jour ont abouti et les journaux passent la vérification
complète des transactions. Le SYSCONF est valide ; le menu v514 et IOS80 v6944
ont respectivement leurs 9 et 15 contenus cohérents avec les TMD. L'utilisateur
a aussi trouvé le témoin plus fluide. Aucun flux FPS précis n'a été enregistré
pour cet A/B. C'est un seul passage par variante, avec une taille finale de
journal légèrement différente : il ne démontre pas la taille exacte d'une
régression, mais **ne montre aucun gain**. Le nombre d'accès SRAM n'était donc
pas, à lui seul, une justification suffisante pour ce raccourci. Ne pas le
promouvoir. Les captures brutes restent dans `P1-trace-005/Capture` et
`P1-trace-006/Capture` pour réanalyse.
@@ -0,0 +1,802 @@
# Étude des performances de la mise à jour Wii native
Date : 13 septembre 2026. Profil étudié : `.starlet_formatter_test_20260913`.
La réussite de la mise à jour est confirmée par l'utilisateur.
**Méthode à suivre depuis le bilan F du 19 septembre :**
[protocole de diagnostic P1](Wii_LLE_Performance_Diagnostic_Protocol.md).
Les propositions d'essais ci-dessous constituent l'historique, pas une instruction de
relancer une série de mises à jour. L'attribution du coût doit précéder le prochain correctif.
## Conclusion
La priorité est le chemin mémoire du JIT ARM, pas un changement de moteur JIT ou
une désactivation de la persistance NAND. Un premier prototype conservateur réduit
le temps d'une boucle d'écritures SRAM d'environ **9,5 %**. Les lectures SRAM lentes
offrent un potentiel plus important, mais leur élargissement nécessite une validation
matérielle et de la chaîne de démarrage. Le journal NAND peut être beaucoup plus compact,
sans que la réduction de sa taille se traduise proportionnellement en temps gagné.
Lors de l'étude initiale, aucun correctif de production n'était déployé : la build interactive,
la NAND mise à jour et les réglages de vitesse restaient inchangés. Les sections 5 et 6 décrivent
les essais ultérieurs, après compilation des correctifs SRAM. Les prototypes de format de journal
restent des exécutables distincts et ne sont pas utilisés par les builds interactives.
## 1. Mesures pendant la mise à jour
- PID 27108, lancé à 00:52:47, Ethernet AX88772 et NAT actifs.
- À 01:00, 1 226 trames reçues supplémentaires en environ onze secondes ; aucune
erreur de polling NAT. Cela prouve une progression, pas l'absence de toute limitation réseau.
- Thread principal : 76 à 83 % d'un cœur selon les fenêtres mesurées.
- Thread de rendu : environ 88 % des échantillons en attente.
- Vitesse d'émulation : phases à environ 65–76 %, alternant avec des phases à pleine vitesse.
- Environ 99,6 % des instructions ARM comptées par le JIT sont natives. Cette mesure
ne signifie pas que les accès mémoire associés évitent les appels C++.
- Les 21,58 % d'échantillons dans `NtWaitForSingleObject` ne sont pas attribués au disque :
sans piles d'appels, le limiteur de vitesse et d'autres attentes restent des explications possibles.
La capture CPU et les compteurs sont conservés dans
`.starlet-profile/update-profile-20260913-0100.txt` et
`.starlet-profile/update-diagnosis-20260913.md`.
## 2. Journal NAND réel : beaucoup d'octets, mais pas une preuve de domination du disque
Une analyse binaire en lecture seule a vérifié les sommes de contrôle de toutes les
transactions, sans déchiffrer la NAND. Le journal final représente **2 928 558 112 octets**.
Le seuil de départ est sa taille observée avant la session, **533 705 344 octets**.
Les chiffres suivants couvrent donc la session après ce seuil, pas un chronométrage
strict des seuls appels de l'interface de mise à jour.
| Catégorie | Transactions | Octets |
|---|---:|---:|
| Programmation d'une page | 555 504 | 1 193 222 592 |
| Effacement d'un bloc de 64 pages | 8 871 | 1 201 630 176 |
| Total après le seuil | 564 375 | 2 394 852 768 |
Chaque effacement sérialise actuellement 64 pages entièrement remplies de `0xff`,
soit **135 456 octets** avec les métadonnées et la somme de contrôle. Un enregistrement
décrivant exactement le bloc effacé pourrait représenter la même opération en **36 octets**.
L'estimation sur cette session passe ainsi de 2,395 Go à **1,194 Go**, environ **50 % de moins**.
Il n'y a que 7 048 réécritures de page strictement identiques parmi 1 123 248 enregistrements
de page après le seuil, soit environ 0,63 %. La simple suppression des doublons n'est donc
pas le meilleur premier levier sur cette charge.
### Comparaison synthétique avec la bibliothèque de production
Même charge par essai : 128 effacements, chacun suivi de 64 programmations ; 8 320 transactions.
Le code de production `NANDJournal::Append` est appelé directement pour la référence.
Les trois variantes conservent un `fflush` par transaction. Trois répétitions, ordre tournant.
Les opérations synthétiques sont relues indépendamment : sommes de contrôle et pages finales
identiques. Les fichiers synthétiques sont ensuite supprimés individuellement.
| Variante | Temps médian | Taille |
|---|---:|---:|
| Production actuelle | 86,377 ms | 34 934 816 octets |
| Un seul `WriteArray` avec record + checksum | 94,858 ms | 34 934 816 octets |
| Effacements compacts + écriture regroupée | 69,166 ms | 17 601 056 octets |
Le regroupement seul n'est pas un gain démontré. L'encodage compact réduit d'environ
20 % le temps de ce banc d'essai, et de 49,6 % le volume. Les chiffres absolus sont ceux
du cache disque et du processeur de cette machine ; ils n'incluent pas les calculs et
la planification d'IOS, les contrôleurs NAND/AES/ECC, ni les échanges réseau. On ne peut
pas transformer ces pourcentages en promesse de gain global sur la mise à jour.
**Le prototype compact n'est pas un format de journal déployable** : il utilise un marqueur
expérimental non reconnu par le lecteur actuel. Une intégration réclame un format versionné,
une lecture des anciens journaux, une migration sauvegardée et des tests de transactions
interrompues, corruption et retour à une ancienne build. `fflush` est conservé à l'identique ;
ces essais ne prouvent pas une garantie supplémentaire contre une panne électrique de l'hôte.
## 3. Mémoire ARM : origine concrète des appels lents
Dans `ARMJitX64::EmitFastmemAddress` :
- Les lectures SRAM directes sont limitées aux pages logiques `00, 10, 12, 14, 19, 1e`.
- Les écritures SRAM sont toutes rejetées vers le bus exact, après plusieurs vérifications
qui ne peuvent actuellement conduire à une écriture directe.
- Les transferts de listes de registres sont exclus du chemin SRAM direct.
- Des pages très sollicitées dans les compteurs de mise à jour, telles que `04, 16, 15`,
ne font pas partie de la liste des lectures directes.
- Les chemins lents paient les appels généré → C++, la classification du bus, le calcul des
alias SRAM, et l'instrumentation. Il ne suffit donc pas d'afficher « 99,6 % natif ».
Un banc d'essai utilise le véritable `ARMCore`/JIT et le véritable bus `StarletMemory` pour
les données SRAM. Seul le petit programme Thumb est placé dans une RAM synthétique.
Il ne charge aucun dump et n'initialise aucun profil. Les registres et la mémoire finale sont vérifiés.
Pour un même programme de lecture et la même adresse `0xffff4000`, autoriser le pointeur
direct déjà validé prend environ **14 ms**, contre **70 ms** en forçant le bus exact,
pour 8 millions d'instructions : environ **5 fois moins de temps sur cette boucle**.
Les lectures de la page non admise `0xffff6000` et les écritures restent sur le chemin lent.
Ce microbenchmark utilise une MMU désactivée, un programme homogène et pas de périphériques
en activité : le facteur cinq **n'est pas** un facteur cinq sur IOS ou sur la mise à jour.
### Prototype conservateur : rejoindre plus tôt le même chemin lent
Le prototype ajoute seulement `sram_access == Write` au rejet initial SRAM, puisque ces
écritures sont déjà rejetées inconditionnellement plus loin. Il ne permet aucune écriture
directe nouvelle, ne supprime aucun appel au bus et ne modifie pas le comportement d'IOS.
Comparaison alternée **A/B/B/A/A/B**, trois répétitions par exécutable, affinité sur le même
processeur logique 0, 32 millions d'instructions par boucle, compilation initiale exclue :
| Boucle d'écritures SRAM | Médiane, 9 échantillons |
|---|---:|
| Production | 267,285 ms |
| Rejet anticipé | 241,842 ms |
Soit **9,5 % de temps en moins** sur cette boucle. Les 16 millions d'écritures par mesure
passent toujours par le chemin lent et le résultat architectural reste identique.
Les fluctuations de fréquence/ordonnancement subsistent ; les résultats ne constituent
pas un gain global mesuré. Le prototype lié aux tests existants passe **176 tests sur 22 suites**,
y compris les tests NAT avec le runtime présent ; deux benchmarks préexistants sont désactivés.
La validation interactive BootMii → IOS → menu → HBC → rechargement IOS reste à effectuer.
## 4. Ordre proposé pour les correctifs
1. **Rejet anticipé des écritures SRAM** : petite modification, gain local mesuré, même bus,
tests ciblés passés. Valider une build séparée sur toute la chaîne avant de l'adopter.
2. **Lectures SRAM chaudes** : caractériser précisément les pages et leurs alias, puis étendre
la voie directe par classe d'accès avec tests interpréteur/JIT, deux endiannesses,
boot0 actif/inactif, split, trous, frontières et rechargements IOS. Potentiel plus élevé.
3. **Journal compact pour les effacements** : format versionné et récupération testée,
mêmes opérations invitées et même niveau de persistance avant acquittement.
4. **Chronométrage par phase dans une exécution comparable** : temps ARM, attente/limiteur,
appels du bus, contrôle NAND et journal, débit réseau ; comparer des copies d'un même
état pré-mise à jour. Ne pas reformater ni rétrograder la NAND actuelle pour fabriquer ce test.
Ne pas réactiver globalement les écritures SRAM directes : le code documente une précédente
régression BootMii malgré des tests de bornes isolés. Ne pas désactiver les contrôles IOS,
modifier les horloges Wii, ou différer arbitrairement les sauvegardes pour afficher plus de FPS.
## 5. Extension des lectures SRAM mesurées (13 septembre, 23 h)
Le rejet anticipé des écritures a été compilé puis lancé sur le profil mis à jour ; l'utilisateur
a confirmé que ce premier essai fonctionnait. La chaîne complète de rechargements reste un
critère de validation distinct, pas une conséquence des tests unitaires.
La trace arrêtée à 22:52 contient 73 117 662 lectures SRAM lentes. Les pages `04, 16, 15, 1d`
en représentent 47 810 655, soit environ 65,4 %. Avec `1c`, également observée pendant l'étude
de la mise à jour, elles sont désormais admises pour les lectures scalaires directes.
La liste totale est `00, 04, 10, 12, 14, 15, 16, 19, 1c, 1d, 1e`.
Un masque de 32 bits remplace la chaîne de comparaisons. Les états boot0/split sont toujours
lus à l'exécution ; trous, bornes, écritures et listes de registres gardent leur traitement existant.
Ni les horloges, ni les instructions IOS, ni le journal NAND ne sont modifiés par ce correctif.
### Validation hors firmware
Le nouveau `SRAMReadTest.cpp` utilise le vrai `StarletMemory` pour les données, sans initialiser
de dump ou de profil. Il vérifie :
- les lectures ARM/Thumb de mots, octets, demi-mots et valeurs signées, dans les deux endiannesses ;
- 37 908 combinaisons d'alias, pages, alignements/frontières et changements boot0/split,
en comparant les 16 registres et CPSR à l'interpréteur, sans modifier les 96 Kio de SRAM ;
- la réutilisation des blocs compilés lors des changements de mapping matériel ;
- un remappage de page SRAM après une invalidation TLB réellement exécutée par le code invité ;
- zéro appel de lecture de données au bus pour les nouveaux accès alignés autorisés.
Avant le correctif, les tests de chemins rapides échouaient comme attendu sur les compteurs,
alors que les résultats mémoire correspondaient. Après le correctif, **179 tests sur 23 suites**
passent, dont les tests existants de protection boot0, écritures SRAM, NAND, appairage et NAT.
Deux benchmarks préexistants restent désactivés.
### Comparaison A/B
Comparaison alternée A/B/B/A/A/B après la compilation, sur le même processeur logique 0,
avec neuf échantillons par cas, 32 millions d'instructions et compilation initiale exclue :
| Boucle | Avant, médiane | Après, médiane |
|---|---:|---:|
| Lecture page `16` (`0xffff6000`), nouvellement directe | 263,97 ms | 43,44 ms |
| Lecture page `14`, déjà directe | 45,39 ms | 45,31 ms |
| Lecture page `14`, bus forcé | 245,27 ms | 224,46 ms |
| Écriture SRAM, toujours par le bus | 206,77 ms | 215,03 ms |
Le cas ciblé passe de 16 millions à zéro lecture lente et prend environ **6 fois moins de temps**.
Les valeurs finales sont vérifiées pour tous les échantillons. Les cas de contrôle montrent
encore la variabilité de l'hôte ; ne pas interpréter leurs écarts comme des correctifs supplémentaires.
Ce banc homogène sans MMU ni périphériques actifs **ne mesure pas** la durée d'une mise à jour
Wii, ni les FPS de la chaîne complète. Ces gains réels restent à mesurer sur la build interactive.
Le premier chronométrage effectué pendant la compilation est exclu de cette comparaison.
La build précédente et le journal courant (3 080 124 160 octets) ont été sauvegardés dans
`.starlet-profile/before-sram-read-20260913-230752/`. Les SHA-256 de la copie et du journal
actif ont été comparés et sont identiques. Aucun compactage ni retour arrière de la NAND
n'a été réalisé. La nouvelle build reste en validation interactive, pas un jalon final validé.
## 6. Préparation d'une vraie comparaison de mise à jour
À la demande de l'utilisateur, deux profils indépendants ont été préparés dans
`.starlet_update_ab_20260913/A` et `B`. La NAND de départ est le préfixe de 533 705 344 octets
du journal archivé : exactement la taille observée avant la session de mise à jour initiale.
Extraction dans un nouveau fichier uniquement ; aucune troncature/restauration d'un journal actif.
Ses 125 881 transactions, le SYSCONF et les 24 contenus du menu/IOS80 ont été revérifiés.
SHA-256 du préfixe : `07b2f4bc543cfd4203efc448538505e9d503b2727c16aaff8d8c0b286d8b4f0c`.
Les fichiers des deux profils sont identiques, sauf leurs chemins de sortie isolés. SD et runtime
sont copiés ; executables et PDB sont figés séparément. A inclut déjà le rejet anticipé des écritures,
B ajoute les lectures scalaires étendues : cette comparaison isole donc cette seconde optimisation.
Les journaux des profils habituels ont été contrôlés inchangés après préparation.
A a été lancé le 13 septembre à 23:26:56 (PID 1696), Ethernet actif, avec une capture en lecture
seule des performances, compteurs NAT/CPU/I/O et taille du journal toutes les dix secondes.
Le coût de chaque interrogation est enregistré : les premières lectures réussies coûtent environ
0,7 seconde de temps mural, ce qui impose de garder la même instrumentation dans les deux essais
et de ne pas négliger son impact potentiel. La première lecture, trop précoce pendant l'initialisation,
a échoué sans affecter Dolphin ; les suivantes fonctionnent. La capture n'injecte pas d'entrées,
n'écrit pas en mémoire et ne suspend pas les threads.
Cet essai du 13 septembre n'a pas de confirmation de fin. Le 16 septembre, son dossier complet
a été conservé sous `A-interrupted-20260913`, puis A recréé à l'identique depuis B encore intact.
Les empreintes de tous les fichiers du nouveau A correspondent à son manifeste initial.
## 7. Résultats de la paire native A/B du 16 septembre
L'utilisateur confirme la réussite des deux mises à jour. Aucun réglage d'horloge, accès réseau,
contrôle IOS ou format du journal n'a été modifié entre les deux exécutables figés.
| Mesure | A, avant extension des lectures | B, après extension |
|---|---:|---:|
| Durée entre signalements début/fin | 13 min 29 s | 11 min 33 s |
| FPS médians des relevés | 22,79 | 28,33 |
| Vitesse d'émulation médiane | 46,3 % | 55,0 % |
| Plus bas FPS échantillonné | 8,42 | 7,28 |
| Échantillons sous 90 % de vitesse | 68 / 73 | 56 / 63 |
| Accroissement du journal entre premiers/derniers relevés sélectionnés | 2 190 232 512 octets | 2 190 232 512 octets |
| Erreurs de polling NAT | 0 | 0 |
Sur cette paire, **115,5 secondes de moins, soit environ 14,3 % de réduction de durée** sont
observées. Ce n'est ni un gain global de 6×, ni un fonctionnement garanti à 50–60 FPS. Les chutes
à 7–9 FPS demeurent. Les FPS sont des relevés discrets, pas une distribution pondérée par image.
Les heures de début/fin proviennent des signalements et inspections, pas d'un chronométrage
exact du clic et de l'écran de réussite. Pour recouper, les franchissements échantillonnés de
600 Mo à 2700 Mo de journal prennent 685,6 s dans A et 582,6 s dans B (environ 15 % de moins).
Ils ont une granularité d'environ onze secondes et ne désignent pas exactement les mêmes
transactions. Conditions serveur, cache/température/charge hôte et capture restent des facteurs
de variation ; une paire séquentielle ne mesure pas à elle seule l'effet causal exact du correctif.
### Profilage complémentaire pendant la chute de B
À la demande de diagnostic, un échantillonnage des pointeurs d'instruction de 15 secondes a été
ajouté entre 17:16:47 et 17:17:03. Il suspend/reprend brièvement les threads. **Ce surcoût n'était
pas présent sur A**, et reste inclus dans la durée observée de B ; il ne faut pas soustraire
arbitrairement quinze secondes. La fenêtre est marquée dans les résultats.
- Thread d'émulation : 14,000 secondes CPU en 15 secondes, soit environ 93,3 % d'un cœur.
- Allocation identifiée du JIT ARM : 393 / 953 échantillons du thread, environ 41,2 %.
- Accès `StarletMemory`, traduction d'adresses, fonctions mémoire et quelques chemins interprétés
apparaissent également parmi les points chauds.
- Thread de rendu : environ 88,9 % des échantillons en attente, 1,438 seconde CPU en 15 secondes.
- Sur 3,828 secondes couvertes par cinq compteurs de cette fenêtre : environ un million
d'écritures SRAM lentes par seconde. Il s'agit d'accès mémoire ARM, pas d'appels d'écriture disque,
et le nombre d'accès n'est pas une part de temps CPU.
Le frein observé est principalement côté exécution CPU/mémoire, pas le thread de rendu.
Les données ne démontrent pas que le journal NAND domine le temps. Prochaines cibles à mesurer :
écritures SRAM via le bus, traduction, dispatch et chemins de repli, en conservant les protections
et la cohérence. Ne pas réactiver globalement les écritures directes qui avaient régressé BootMii.
### Intégrité à l'arrêt et conservation
Après fermeture confirmée, les journaux ont été relus intégralement : **651 682 transactions valides
pour A, 651 690 pour B**. Les deux SYSCONF sont valides ; les 24 contenus du menu v514 et
de l'IOS80 v6944 correspondent à leurs empreintes TMD. Il ne s'agit pas d'un audit exhaustif
de tous les titres installés. Les journaux finaux font respectivement 2 763 850 048 et
2 763 867 232 octets ; leurs empreintes et preuves sont conservées dans chaque dossier `Capture`.
Les profils habituels, les dumps d'origine et la graine pré-mise à jour n'ont pas été remplacés.
## 8. Optimisation du bus SRAM et des marches de tables (16–19 septembre)
Le profilage de B a conduit à deux changements ciblés, sans nouveau chemin d'écriture directe
dans le JIT et sans modification des horloges, du NAT ou du journal NAND :
- Les écritures SRAM alignées de 16/32 bits sont traitées avant le décodage MMIO. Un accès aligné
reste dans un seul segment ; les protections boot0 et le bit SM sont relus à chaque accès.
Les accès non alignés conservent le chemin précédent, y compris aux limites des banques.
- Le calcul de banque SRAM utilise l'échange du bit d'aperture, suivi du contrôle des 96 Kio
physiques. Les trous ne deviennent pas des alias valides.
- Les marches de tables ordinaires ne demandent plus un pointeur de descripteur inutilisé.
Le JIT continue à obtenir ces pointeurs pour valider ses blocs après maintenance du TLB.
Les lectures de descripteurs et les règles de traduction ne changent pas.
### Vérification hors firmware
**182 tests passent**. Les nouveaux tests confrontent chaque octet de trois apertures au modèle
de mapping indépendant (1 572 864 lectures), puis les écritures larges à un oracle octet par octet
sur toutes les fenêtres miroirs, les limites, les modes SM/boot0 et leur retour à l'état initial.
Ils comparent toute la SRAM après chaque écriture. La traduction est vérifiée en big/little-endian
pour les sections et les tables coarse/fine (pages large, small et tiny). Les tests existants de
DMA, IRQ, MMU, changement de contexte JIT, NAND et réseau restent inclus.
### Mesure synthétique avant/après
Même programme ARM/Thumb, résultats vérifiés, bibliothèque Release, CPU logique 0, exécutables
avant/après distincts. Deux séries alternées, ABBAAB puis BAABBA, donnent chacune neuf mesures
par variante et par cas. Aucun émulateur ni compilateur n'est lancé pendant la mesure.
Le PC montre une forte variabilité ; les durées CPU du thread sont aussi enregistrées mais leur
granularité est limitée. Les valeurs suivantes sont les médianes de durée murale en millisecondes :
| Cas | Avant, série 1 | Après, série 1 | Avant, série 2 | Après, série 2 |
|---|---:|---:|---:|---:|
| 16 millions d'écritures SRAM, alias `ffff9000` | 271,06 | 149,17 | 273,50 | 156,40 |
| Même charge, alias bas `0d419000` | 307,16 | 154,17 | 383,38 | 176,50 |
| Lecture déjà directe, contrôle `ffff4000` | 56,82 | 56,55 | 74,31 | 71,17 |
| Marches de tables forcées, charge interprétée | 174,94 | 124,29 | 318,02 | 136,92 |
Le cas principal d'écriture montre environ **43–45 % de durée en moins sur ce microbanc**.
Les 16 millions d'écritures passent toujours par le bus, avec les mêmes résultats ; aucune n'est
supprimée. La variabilité, surtout pour la traduction, interdit une extrapolation précise.
Ce résultat ne signifie pas 43–45 % de moins sur une mise à jour complète, ni 50–60 FPS garantis.
La validation native de cette nouvelle optimisation est décrite en section 9 ;
les mises à jour A/B de la section 7 concernaient uniquement l'extension des lectures.
Les profils et journaux réels ne sont pas remplacés par ces tests.
### Préparation du test natif C
Le 19 septembre, un troisième profil `C` et un exécutable figé `build-C` sont créés sans écraser A/B.
Tous les fichiers du profil initial correspondent aux empreintes du manifeste initial B, hormis
les chemins de sortie redirigés vers C. Les copies intactes de TimePlayed, SRAM GameCube et SD brute
sont récupérées depuis le profil de test du formateur, sans le modifier. La NAND de départ est à
nouveau vérifiée intégralement. Premier lancement à 11:48 Paris, capture de compteurs active.
Sa fenêtre étant invisible pour l'utilisateur dans le contexte isolé, il est relancé sur le bureau
visible à 11:52, après confirmation que le journal n'avait pas changé. L'utilisateur ferme ensuite
cette instance après le démarrage natif ; elle est relancée à 11:55 en conservant ses changements,
sans réinitialiser la NAND. Le journal avant cette dernière relance est vérifié : 536 348 704 octets,
126 491 transactions valides et contenus menu/IOS80 intacts. Seule la capture du dernier PID 15540
est utilisée pour mesurer la mise à jour.
Les essais ont lieu à des dates différentes, ce qui ajoute des variations possibles de charge
hôte et de conditions serveur.
## 9. Résultat natif C du 19 septembre
L'utilisateur confirme la fin de la mise à jour, puis ferme Dolphin pour la validation finale.
| Mesure | B, extension des lectures | C, bus SRAM et marches de tables optimisés |
|---|---:|---:|
| Durée entre signalements début/fin | 11 min 33 s | 9 min 07 s |
| FPS médians des relevés | 28,33 | 41,61 |
| Plus bas FPS échantillonné | 7,28 | 13,17 |
| Vitesse d'émulation médiane | 55,0 % | 86,8 % |
| Relevés sous 90 % de vitesse | 56 / 63 | 27 / 51 |
| Temps CPU du processus entre relevés sélectionnés | 796,91 s | 604,77 s |
| Erreurs de polling NAT pendant la fenêtre | 0 | 0 |
La durée observée diminue de **146,4 secondes, environ 21,1 %**, avec une nette amélioration des
FPS échantillonnés. En recoupement, le passage échantillonné de 600 Mo à 2700 Mo de journal prend
439,8 s dans C contre 582,6 s dans B, environ 24,5 % de moins. Ces seuils sont espacés d'environ
onze secondes et ne représentent pas exactement les mêmes transactions.
Ce n'est pas une mesure causale isolée : les signalements sont des repères approximatifs,
les journées/charges hôte et conditions serveur diffèrent, C a été redémarré avant la mise à jour,
et B comporte une fenêtre supplémentaire de profilage hôte de quinze secondes. Les relevés FPS
ne sont pas pondérés par image ; 13,17 FPS est le minimum échantillonné, pas le minimum garanti
sur toutes les images. **L'objectif de 50–60 FPS constants n'est pas encore atteint.**
Après fermeture, le journal C est intégralement valide : **651 746 transactions**, 2 763 987 520
octets, 165 120 pages superposées, génération 821 612. SYSCONF est valide et les 24 contenus du
menu v514/IOS80 v6944 correspondent aux empreintes TMD. Les autres titres ne sont pas vérifiés
par ce contrôleur. SHA256 final :
`81b402d72c7c5fbe38e77a234212125488e0442560b5d0ba73c7a2da800fde56`.
La NAND C et ses mesures sont conservées sans remplacer les résultats A/B, la graine initiale
ou les profils habituels. Aucun commit ni push n'a été effectué pour cette nouvelle étape.
## 10. Copies DMA par blocs — candidat D du 19 septembre
Les phases lentes de C restent notamment visibles pendant le transfert et l'installation :
les relevés entre 10:04:51 et 10:06:06 UTC descendent vers 13–17 FPS, alors que le journal progresse.
Le code des périphériques copiait encore les buffers DMA octet par octet. En MEM1/MEM2, chaque
octet repassait par la résolution de la mémoire hôte. Le profil hôte de B avait déjà échantillonné
`GetPointerForRange`, `Read_U8` et les chemins associés ; cela motive une mesure ciblée, mais ne
quantifie pas leur contribution exacte à C.
Le candidat D résout une plage contiguë une seule fois, puis utilise une copie par blocs pour
AES, SHA, les données/spare/ECC NAND, les buffers/descripteurs OHCI et les transferts SD.
Seules les plages entièrement contenues dans MEM1, MEM2 ou les 96 Kio physiques de SRAM basse
sont accélérées. La SRAM DMA ignore toujours SM et le recouvrement boot0 du CPU. Les trous,
miroirs hauts, registres, franchissements de limites et débordements d'adresse restent sur le
chemin octet par octet. Les tampons temporaires restent séparés de la mémoire invitée ; AES
capture toujours toute son entrée avant d'écrire une sortie éventuellement superposée.
Ni les temporisations, ni le format/flush du journal NAND, ni les traitements réseau ne changent.
### Microbanc et validation
Le banc utilise de vraies commandes MMIO du moteur AES en **mode copie, chiffrement désactivé**,
pour isoler le coût DMA, sans NAND, firmware, SD ou réseau. Chaque cas transfère 64 Mio et vérifie
tous les octets de sortie ainsi que les registres source/destination/commande. Ordre ABBAAB,
trois répétitions par exécution, neuf mesures par cas/version, même cœur hôte fixé.
| Banque / taille par commande | Médiane avant | Médiane après |
|---|---:|---:|
| MEM1 / 2 Kio | 1708,93 ms | 27,42 ms |
| MEM1 / 16 Kio | 1669,52 ms | 10,95 ms |
| MEM2 / 2 Kio | 2247,41 ms | 26,41 ms |
| MEM2 / 16 Kio | 2173,30 ms | 10,57 ms |
| SRAM / 2 Kio | 301,39 ms | 28,91 ms |
| SRAM / 16 Kio | 265,90 ms | 12,37 ms |
La variabilité hôte est importante, même à affinité constante. Les temps CPU sont enregistrés,
mais leur granularité de 15,625 ms limite la précision des cas accélérés. Ces chiffres ne sont
**ni une mesure du chiffrement AES, ni une accélération annoncée de la mise à jour complète**.
Le gain natif du candidat D reste à mesurer.
Les **189 tests ciblés passent**. Sept nouveaux tests couvrent : comparaison au bus octet par
octet sur 1 144 combinaisons de plage/mode, canaris et SRAM entière, limites/overflow, pages OHCI
non contiguës, endian des descripteurs, colonnes NAND et spare/ECC séparés, entrée aléatoire NAND,
AES avec entrée/sortie superposées, SHA multiblocs sur les trois banques, SD/SDHC synthétique et
protection des écritures. Ces sept tests passent aussi trois répétitions avec ordre mélangé.
Les tests antérieurs d'AES chiffré/déchiffré, de SHA en SRAM split, de JIT/MMU et de persistance
NAND restent dans la sélection. Les seules suppressions de fichiers des nouveaux tests concernent
leurs images SD temporaires uniques ; aucun profil utilisateur n'est utilisé.
Le test natif D est préparé dans un nouveau profil depuis la graine initiale contrôlée, avec
les mêmes fichiers initiaux que B (chemins de sortie isolés). A/B/C et les profils habituels ne
sont pas remplacés. Build figée SHA256 :
`1ab2746044256533185836a49e7644817f8f2d7f523c6a9c0a6199773574151c`.
Le premier essai D est interrompu pendant l'absence de l'utilisateur ; il n'est pas utilisé
pour comparer la durée complète. Sa NAND de 855 883 744 octets et ses logs sont conservés dans
`D`, avec un marqueur `Capture/interrupted.json`. À sa demande, le 19 septembre au soir, un
nouvel essai est lancé dans `D-repeat-20260919` avec **la même build-D**, depuis les fichiers
initiaux et la graine de NAND vérifiés à nouveau. Ses captures et repères début/fin sont distincts
du premier essai. Aucune NAND existante n'est supprimée ni remplacée.
## 11. Résultat natif D répété, 19 septembre au soir
Le nouvel essai `D-repeat-20260919`, PID 11548, part de la graine initiale vérifiée avec la build D
inchangée. L'utilisateur signale le départ à 18:23:04,771 UTC, puis la fin à 18:31:37,196 UTC en
précisant qu'elle remonte peut-être à deux minutes. **Le délai entre ces deux signalements n'est
donc pas retenu comme durée de mise à jour**, et les relevés après stabilisation sont exclus du
bilan actif. Le premier essai D interrompu reste conservé séparément et n'entre pas dans ce bilan.
Les compteurs réseau atteignent leur valeur terminale au relevé de 18:29:51,774 UTC. Le journal
atteint sa taille terminale au relevé suivant, 18:30:02,220 UTC, puis reste stable jusqu'au
signalement. Ce repère situe la fin de l'activité observée environ **6 min 47 à 6 min 57 après le
signalement de départ**, soit environ sept minutes. Il ne prouve pas l'instant exact d'apparition
de l'écran de réussite ; le signalement de départ est lui aussi approximatif. Le signalement
final arrive environ 95 secondes après le premier relevé entièrement stabilisé.
Pour comparer sans utiliser ce signalement final tardif, les trois essais sont recalculés sur
les mêmes seuils échantillonnés de taille de journal : 600 Mo vers 2700 Mo, unités décimales.
| Portion de progression comparable | B | C | D répété |
|---|---:|---:|---:|
| Durée échantillonnée | 9 min 43 s | 7 min 20 s | 5 min 49 s |
| Nombre de relevés | 54 | 42 | 34 |
| FPS médians | 27,84 | 39,03 | 47,32 |
| Plus bas FPS échantillonné | 7,28 | 13,17 | 18,40 |
| Vitesse d'émulation médiane | 53,2 % | 82,2 % | 93,1 % |
| Temps CPU du processus | 687,09 s | 511,27 s | 354,88 s |
| Erreurs de polling NAT | 0 | 0 | 0 |
Sur cette portion, D demande **environ 20,7 % de temps réel et 30,6 % de temps CPU de moins que C**.
Les seuils ne désignent pas exactement les mêmes transactions : C commence à 644 516 788 octets
et finit à 2 728 169 380, D à 610 411 264 et 2 700 960 352. Les instantanés sont espacés d'environ
10–11 secondes, les conditions hôte/serveur diffèrent et il ne s'agit que d'un essai par version.
Le coût médian d'un instantané est de 697 ms dans cette portion de C et 555 ms dans D. Les
différences observées ne constituent donc pas un effet causal précisément isolé du seul patch.
Sur toute la fenêtre du signalement de départ au premier plateau terminal, D a 40 relevés :
47,64 FPS médians, 18,40 FPS au plus bas échantillonné, vitesse médiane 96,2 %, 14 relevés sous
90 %. Les baisses persistantes se concentrent notamment vers la fin des transferts. Les valeurs
FPS restent des instantanés, non pondérés par image. **50–60 FPS constants ne sont pas acquis.**
Après confirmation de fermeture, le journal complet est vérifié : **644 569 transactions**,
2 733 507 520 octets, 165 440 pages superposées, génération 821 569, SYSCONF valide. Les neuf contenus
du menu v514 et quinze contenus d'IOS80 v6944 correspondent aux empreintes TMD ; les autres titres
ne sont pas contrôlés par ce vérificateur. SHA256 final :
`4018d66489f2bfda3577a82a206abf52b152cf7a83e9c1bced0f4f4f49ec665e`.
NAND et résultats restent dans le profil de répétition, sans suppression ni remplacement des
essais précédents. Aucun commit/push supplémentaire n'est effectué.
## 12. Candidat E : lectures SRAM et écritures scalaires spécialisées
Le profil du dernier essai D relève notamment 116 619 902 lectures SRAM lentes et
806 261 862 écritures SRAM. Parmi les lectures, les pages 01, 13, 1a et 02 représentent
respectivement environ 49,99, 12,35, 11,71 et 7,53 millions d'accès. Ce sont des compteurs
d'accès, **pas une attribution du temps CPU**. Ils motivent une optimisation ciblée,
mesurée séparément avant de relancer une mise à jour native.
Le candidat E apporte quatre changements :
- Les lectures scalaires du JIT peuvent utiliser le chemin direct existant pour ces quatre
pages supplémentaires. Les contrôles vivants boot0/split, trous et alignement restent actifs.
Les transferts de listes de registres et les écritures SRAM restent sur le bus matériel.
- Les lectures CPU alignées de 16/32 bits en SRAM résolvent une fois la banque et l'overlay ROM,
au lieu de refaire le décodage octet par octet. Les cas timer/IPC existants gardent la priorité.
`HW_VERSION` est lu par mot depuis son registre vivant, sans valeur forcée.
- Un nouveau test avec une ROM non nulle révèle un défaut préexistant aux limites : une lecture
large non alignée pouvait ignorer une partie de l'overlay boot0 ou dépasser son tableau à la
frontière d'un miroir. Le chemin large n'est désormais retenu que si toute sa plage est
contiguë dans la même banque ; sinon, le bus résout chaque octet.
- Les helpers d'écriture lente du JIT sont spécialisés à la compilation pour 1, 2 ou 4 octets.
La largeur connue de l'instruction n'est plus transmise puis testée à chaque appel. Les mêmes
appels `ARMBus::Write8/16/32`, compteurs, conversions d'endian et effets matériels subsistent.
Aucun timer, délai d'interruption ou engagement de persistance NAND n'est modifié.
La première variante limitée aux lectures accélérait les cas ciblés, mais ralentissait le
témoin d'écriture d'environ 7 % dans les deux ordres de comparaison. Elle n'est pas la version
retenue pour l'essai natif. La spécialisation des écritures corrige ce recul et donne les
résultats finaux ci-dessous.
### Validation et mesures isolées
Les **192 tests de 25 suites passent** ; les 13 tests DMA/lectures SRAM passent aussi trois fois
en ordre mélangé. Le nouveau test mémoire compare 41 472 lectures larges à l'oracle octet par
octet, avec ROM/SRAM non nulles, changements vivants de mapping et frontières de miroirs.
Les nouveaux tests d'écritures natives comparent JIT et interpréteur en ARM/Thumb, petit/grand
endian et différents mappings : registres, CPSR, SRAM entière et octets voisins doivent coïncider.
Le banc Thumb utilise le vrai `StarletMemory`, sans firmware ni NAND utilisateur, avec 64 millions
d'instructions et 32 millions d'accès mémoire par cas. Les builds avant/après sont alternées en
ABBAAB puis BAABBA, neuf échantillons par cas/version dans chaque série, sur le même cœur hôte.
Le tableau donne les médianes murales ; les registres, valeurs et compteurs sont aussi vérifiés.
| Cas | D, ordre 1 | E, ordre 1 | D, ordre inverse | E, ordre inverse |
|---|---:|---:|---:|---:|
| Lecture page 01 | 329,96 ms | 63,35 ms | 341,51 ms | 63,78 ms |
| Lecture page 02 | 331,58 ms | 63,80 ms | 341,56 ms | 64,18 ms |
| Lecture page 13 | 334,95 ms | 68,83 ms | 338,39 ms | 69,31 ms |
| Lecture page 1a | 333,46 ms | 69,76 ms | 336,22 ms | 69,52 ms |
| Lecture forcée par le bus | 318,91 ms | 258,61 ms | 323,60 ms | 260,80 ms |
| Lecture déjà directe, témoin | 67,92 ms | 68,24 ms | 68,96 ms | 68,75 ms |
| Écriture SRAM par le bus, témoin | 221,92 ms | 201,56 ms | 224,79 ms | 203,47 ms |
| Lecture VERSION et mot suivant | 1417,86 ms | 874,05 ms | 1458,66 ms | 887,22 ms |
Les lectures nouvellement directes suppriment les 32 millions d'appels au bus de chaque cas.
Le cas de lecture forcée et le témoin d'écriture conservent chacun leurs 32 millions d'appels.
Le témoin de lecture déjà directe est pratiquement inchangé ; celui d'écriture demande environ
9 % de temps en moins. La mesure VERSION inclut aussi le mot suivant, pas uniquement VERSION.
Les conditions hôte peuvent varier ; une courte répétition de tests intervient vers la fin de
la série inverse. Les temps CPU sont enregistrés mais leur granularité limite leur précision.
**Ces gains synthétiques ne prédisent ni la durée totale de mise à jour ni des FPS constants.**
### Essai natif préparé
Le profil E repart des mêmes fichiers initiaux que B et de la même graine NAND, contrôlés avant
la réécriture des seuls chemins isolés. Le journal initial comporte 125 881 transactions ;
SYSCONF et les 24 contenus du menu/IOS80 sont vérifiés. Les profils A/B/C/D, la répétition D et
les profils habituels sont conservés. Build E figée SHA256 :
`9cca944ecece148b1ffa0cc77a160d5762804d79fc3817f24189e5954d9935fb`.
Le gain natif de E reste à mesurer avec la même capture que D.
## 13. Résultat natif E, 19 septembre au soir
L'utilisateur confirme la réussite de la mise à jour E, PID 3308, à 19:17:49 UTC.
Le départ signalé à 19:10:31 UTC est tardif par rapport aux premiers échanges réseau et
écritures du journal. Les valeurs observées avant le signalement final atteignent leur
dernier état à 19:17:41,598 UTC, soit environ 7 min 11 après le signalement de départ.
Ce délai **n'est pas la durée exacte de la mise à jour**. L'activité reprend après le
signalement final ; cette activité ultérieure est exclue du bilan de mise à jour.
La comparaison principale reprend les mêmes seuils échantillonnés de journal que pour D :
| Portion de journal 600 Mo vers 2700 Mo | D répété | E |
|---|---:|---:|
| Durée échantillonnée | 348,91 s | 350,34 s |
| Nombre de relevés | 34 | 34 |
| FPS médians | 47,32 | 48,70 |
| Plus bas FPS échantillonné | 18,40 | 20,13 |
| Vitesse d'émulation médiane | 93,05 % | 95,25 % |
| Relevés sous 90 % de vitesse | 14 | 16 |
| Temps CPU du processus | 354,88 s | 353,86 s |
| Coût médian d'un instantané | 555,37 ms | 597,96 ms |
| Erreurs de polling NAT | 0 | 0 |
Le temps réel augmente de 0,4 % et le temps CPU diminue de 0,3 % : **aucune accélération
globale convaincante n'est démontrée**. La légère amélioration des FPS médians et minimaux
ne suffit pas à établir un gain reproductible. Les seuils sont franchis à des transactions
différentes : D couvre 610 411 264 à 2 700 960 352 octets, E 655 167 424 à 2 732 944 480.
Les conditions hôte/serveur diffèrent, les mesures restent espacées d'environ 10–11 secondes,
et il ne s'agit que d'un essai par version. Les gains du banc ciblé ne se traduisent donc
pas par un nouveau gain comparable à C vers D dans cet essai natif.
La fenêtre du signalement de départ au dernier état pré-signalement contient 41 relevés :
49,25 FPS médians, 20,13 au minimum, vitesse médiane 96,6 %, 16 relevés sous 90 % et 409,77 s
de temps CPU. Les relevés après le signalement final ne sont pas inclus. Le journal atteint
2 733 507 520 octets avant ce signalement puis continue à évoluer lors de l'activité suivante.
Après confirmation de fermeture, la capture se termine à 19:52:07,295 UTC. Le journal final
de **2 798 712 064 octets contient 659 844 transactions valides**, 165 568 pages superposées
et une génération de système de fichiers de 821 657. SYSCONF est valide ; les neuf contenus
du menu v514 et quinze contenus d'IOS80 v6944 correspondent aux empreintes TMD. Les autres
titres ne sont pas contrôlés par ce vérificateur. Empreinte SHA256 finale :
`5002e06eb368fbc9b55c4e7d1c377391c734a2ccdad8a7ea7df3b3dac352fb16`.
Le préfixe enregistré juste avant le signalement de fin est également vérifié en lecture
seule, **sans tronquer ni restaurer le journal** : 2 733 507 520 octets, 644 569 transactions,
165 440 pages et génération 821 569. SYSCONF et les mêmes 24 contenus sont valides. Son SHA256
est `bae4e8b6f7060139f21a1968c67a5d38199ee96109e873b3467ae59d15f37d17`.
La différence avec le journal final provient des écritures ultérieures, exclues du bilan de
mise à jour. L'ensemble reste conservé dans le profil E ; aucune NAND ni build antérieure
n'est remplacée. Aucun commit/push supplémentaire n'est effectué.
## 14. Candidat F : profilage mémoire détaillé à la demande
Après E, la durée de mise à jour ne baisse pas de façon convaincante. Le candidat F isole
donc le coût du diagnostic, sans modifier les transactions invitées. Les chemins mémoire
lents incrémentaient systématiquement plusieurs compteurs de région/page, et alimentaient
une table de hachage d'adresses tous les 256 accès, même en usage normal.
`WiiStarletJITMemoryProfiling` est désormais un réglage hôte optionnel, désactivé par défaut.
Le choix est fixé à la construction du cœur et le JIT sélectionne un helper spécialisé lors
de la génération de code : aucun test dynamique du réglage n'est ajouté par accès. Le mode
normal conserve les **totaux** de lectures/écritures lentes et les compteurs d'instructions ;
il évite la classification, les compteurs détaillés et la table d'adresses. Le mode diagnostic
les conserve. Les logs identifient explicitement `memory-profile=false` pour que des compteurs
détaillés indisponibles ne soient pas interprétés comme une absence d'accès.
Le script de lancement expose `-ProfileJitMemory`. Le réglage n'est pas sérialisé dans l'état
invité, demeure identique après reset et rechargement des caches, et ne modifie ni endian,
protection boot0, mapping SRAM, interruptions, horloges ni persistance NAND. Les lectures et
écritures continuent à appeler les mêmes méthodes du bus. Les classements de pages/adresses
ne sont calculés dans les logs que lorsque le profilage détaillé est activé.
### Comparaison dans un même exécutable
Le banc reprend le vrai bus `StarletMemory`, sans firmware ni profil utilisateur. Un **même
exécutable figé** est lancé avec le profilage activé/désactivé, en ordre ON/OFF/OFF/ON/ON/OFF,
puis dans l'ordre inverse. Chaque série comporte neuf échantillons par mode/cas, trois tours
par processus, affinité identique et compilation initiale exclue. Chaque mesure exécute
64 millions d'instructions et 32 millions d'accès ; les valeurs finales et totaux d'accès
sont vérifiés. Aucun build ni test unitaire ne tourne pendant ces deux séries.
| Cas, médianes murales | ON, ordre 1 | OFF, ordre 1 | ON, inverse | OFF, inverse |
|---|---:|---:|---:|---:|
| Lecture SRAM déjà directe, page 01 | 58,85 ms | 59,01 ms | 59,92 ms | 59,15 ms |
| Lecture SRAM forcée sur le bus | 256,89 ms | 234,06 ms | 262,07 ms | 234,36 ms |
| Lecture SRAM déjà directe, page 14 | 64,12 ms | 63,94 ms | 64,90 ms | 64,15 ms |
| Écriture SRAM sur le bus | 202,66 ms | 167,61 ms | 205,49 ms | 168,06 ms |
| Lecture VERSION et mot suivant | 964,43 ms | 919,85 ms | 968,57 ms | 928,59 ms |
Le coût diminue d'environ **17–18 % pour les écritures SRAM lentes**, **9–11 % pour les
lectures SRAM forcées sur le bus** et **4–5 % sur le cas MMIO**. Les témoins déjà directs
restent proches. Les temps CPU sont enregistrés, avec la limite habituelle de granularité.
La variabilité hôte demeure ; ces chiffres comparent les deux modes du candidat F dans le
même binaire, pas des temps historiques de builds différents. **Ils ne démontrent pas encore
un gain de durée sur une mise à jour native.**
### Validation
Les **195 tests de 27 suites passent**, couvrant Starlet, IPC, lecteurs TMD, systèmes de
fichiers et CoreTiming. Les quatre tests de lectures/écritures SRAM réelles sont désormais
paramétrés pour tourner avec et sans profilage : ARM/Thumb, deux endiannesses, mappings
vivants, frontières et invalidations TLB restent comparés à l'interpréteur. Un nouveau test
vérifie dans les deux modes les résultats SRAM/MMIO, les totaux exacts de 256 lectures et
256 écritures par phase, l'échantillonnage détaillé et son absence lorsqu'il est désactivé,
puis recommence après reset. Les 20 tests ciblés SRAM/DMA/IPC passent aussi trois répétitions
dans un ordre mélangé. Deux benchmarks historiques restent désactivés.
Build compilée SHA256 :
`bdfb27dbf23065c7894640323f615220140f6cda2fe250814cb59cc22f858a9d`.
Exécutable du banc ON/OFF SHA256 :
`0e20fda45f75464cba4414c4884ddca13a77c0adc221a91622f7a45aa18692a3`.
La validation native reste à faire. Au moment de la préparation, environ 4,2 Go restent sur
C:, insuffisants pour un nouveau profil depuis la graine, la croissance prévue du journal
et la réserve de sécurité du protocole. Aucun essai natif F n'est lancé et aucune ancienne
NAND n'est supprimée pour contourner ce manque d'espace.
L'espace est ensuite libéré indépendamment du test : environ 81 Gio sont disponibles avant
préparation. À la demande de l'utilisateur, le profil F est créé depuis les mêmes fichiers
initiaux vérifiés que B ; les chemins de sortie sont les seules modifications du profil.
Le journal initial de 533 705 344 octets, ses 125 881 transactions, SYSCONF et les 24 contenus
menu/IOS80 sont vérifiés. La build F et ses symboles sont figés avec les empreintes testées.
L'instance F démarre le 19 septembre à **21:17:04 UTC**, PID 32008, capture PID 20080, avec
le profilage mémoire détaillé désactivé. Aucun résultat de performance natif F n'est encore
conclu ; les signalements de départ et fin de mise à jour sont attendus dans `F/Capture`.
À **21:21:25 UTC**, le collecteur est remplacé (PID 33736) sans arrêter Dolphin :
le lecteur initial échoue sur l'annotation facultative d'une adresse interne du JIT.
Le lecteur local `LiveStateF` signale explicitement `JIT_METADATA_UNAVAILABLE` et poursuit
les lectures obligatoires FPS/CPU, E/S, NAT et registres de contrôle AES. La cause exacte
de l'adresse invalide dans les symboles incrémentaux n'est pas établie. Ce changement
concerne uniquement l'observateur, pas l'émulation ni les compteurs du candidat.
Les nouvelles captures terminent avec `SNAPSHOT_EXIT=0` ; les anciennes captures en échec
sont conservées, mais doivent être exclues de la comparaison native. Les mesures valides
sont dans `metrics-20260919-232125-32008.txt`, avec le coût de chaque capture enregistré.
`reader-fix.json` et `launch-before-reader-fix.json` conservent la trace du remplacement.
En attente du lancement de la mise à jour, les lectures montrent environ 50 FPS à vitesse
100 % ; cela ne mesure pas encore le gain pendant une mise à jour.
### Résultat natif F : succès fonctionnel, gain non validé
L'utilisateur signale le départ, relevé à **21:26:45 UTC**, puis la réussite, relevée à
**21:35:53 UTC**. Ces repères ne sont pas les instants exacts de clic ou d'affichage ;
l'activité avait déjà commencé avant le signalement du départ. La comparaison conserve
les mêmes seuils échantillonnés du journal (600 à 2 700 Mo), sans inclure l'activité après
le signalement de fin.
| Mesure sur ces seuils | D répété | E | F |
|---|---:|---:|---:|
| Temps mural | 348,91 s | 350,34 s | 442,04 s |
| Temps CPU du processus | 354,88 s | 353,86 s | 522,23 s |
| FPS médian des échantillons | 47,32 | 48,70 | 40,985 |
| FPS minimal échantillonné | 18,40 | 20,13 | 11,08 |
| Vitesse médiane | 93,05 % | 95,25 % | 80,3 % |
| Échantillons sous 90 % | 14/34 | 16/34 | 25/42 |
| Coût médian d'une capture | 555,37 ms | 597,96 ms | 749,90 ms |
| Erreurs du polling NAT supplémentaires | 0 | 0 | 0 |
F prend **26,2 % de temps mural et 47,6 % de temps CPU supplémentaires par rapport à E**.
Les quatre sous-plages de progression sont également plus lentes. Ce résultat ne valide
donc pas l'optimisation sur la mise à jour réelle, malgré le gain des microbenchmarks.
Il ne suffit pas à attribuer la régression à la désactivation des compteurs : les essais
ne sont pas alternés dans les mêmes conditions hôte/serveur, et les seuils ne correspondent
pas exactement aux mêmes transactions. Le coût du lecteur est aussi plus élevé ; les
compteurs CPU du processus Dolphin n'incluent pas le CPU du lecteur externe.
L'absence d'erreur de polling NAT n'exclut pas une attente du serveur.
La prochaine expérience utile serait une comparaison native ON/OFF alternée du **même
binaire F**, depuis la même graine, avec un lecteur fiable commun et des conditions hôte
relevées. Elle n'est pas lancée automatiquement à la clôture de F. Aucun correctif
supplémentaire de l'émulation n'est appliqué pendant cet essai.
Après fermeture demandée à l'utilisateur, le collecteur se termine à **21:37:36 UTC**.
La dernière capture échoue pendant la disparition du processus et est exclue des calculs.
Le journal final est conservé : **2 783 208 736 octets**, **656 226 transactions valides**,
165 440 pages superposées, génération 821638, SYSCONF valide. Les 24 contenus du menu v514
et d'IOS80 v6944 correspondent à leurs empreintes TMD ; les autres titres ne sont pas
vérifiés par ce contrôle. Empreinte SHA256 du journal :
`6d0341773c613aab63194153f44541b12d75541d0cab53b94b2c014544e9aab0`.
Les anciennes NAND et le profil habituel sont inchangés.
## Reproduction / fichiers de travail
- `.starlet-profile/StudyUpdateJournal.py` : analyse du journal réel en lecture seule.
- `.starlet-profile/update-journal-study-20260913.json` : totaux et vérification des transactions.
- `.starlet-profile/UpdateStudyBench.cpp` et `BuildUpdateStudy.ps1` : banc d'essai séparé.
- `.starlet-profile/ARMJitStudy.cpp` : copie expérimentale du JIT, pas le fichier de production.
- `.starlet-profile/update-study-bench-20260913.txt` : première comparaison des journaux et des accès.
- `.starlet-profile/update-study-arm-ab-summary-20260913.json` : échantillons A/B d'écriture.
- `.starlet-profile/update-study-tests-with-nat-20260913.txt` : résultat des 176 tests du prototype ARM.
- `.starlet-profile/CompareSRAMReads.ps1` et `sram-read-ab-samples.json` : comparaison des lectures.
- `.starlet-profile/sram-read-all-tests.txt` : les 179 tests après extension des lectures.
- `.starlet-profile/PrepareUpdateAB.ps1`, `RunUpdateAB.ps1`, `CaptureWiiUpdate.ps1` : essais isolés.
- `.starlet_update_ab_20260913/manifest.json` : provenance, empreintes des deux builds et des NAND.
- `.starlet_update_ab_20260913/A/Capture` et `B/Capture` : mesures de chaque exécution.
- `.starlet-profile/SummarizeUpdateCapture.ps1` : synthèse des fenêtres horodatées.
- `.starlet_update_ab_20260913/journal-progress-comparison.json` : repères automatiques de progression.
- `.starlet-profile/CompareBusWrites20260919.ps1` : comparaisons alternées du nouveau bus.
- `.starlet-profile/bus-write-ab-samples-20260919.json` et `bus-write-ab-samples-20260919-repeat.json` : mesures brutes.
- `.starlet-profile/tests-bus-writes-final-20260919.log` : validation finale des 182 tests.
- `.starlet-profile/PrepareUpdateC20260919.ps1`, `.starlet_update_ab_20260913/manifest-C-20260919.json`
et `C/Capture` : provenance et enregistrement du troisième essai natif.
- `.starlet-profile/DMAStudy.cpp`, `BuildDMAStudy.ps1`, `DMABefore20260919.exe`,
`DMAAfter20260919.exe` : banc de copie MMIO et exécutables comparés.
- `.starlet-profile/dma-ab-samples-20260919.json`, `dma-ab-summary-20260919.json` : 108 mesures DMA.
- `.starlet-profile/tests-dma-final-20260919.log`, `tests-dma-repeat-20260919.log` : tests du candidat D.
- `.starlet-profile/PrepareUpdateD20260919.ps1`, `.starlet_update_ab_20260913/manifest-D-20260919.json`
et `D/Capture` : préparation et mesures du quatrième essai natif.
- `.starlet-profile/PrepareUpdateDRepeat20260919.ps1`, `AnalyzeUpdateDRepeat20260919.ps1` et
`.starlet_update_ab_20260913/D-repeat-20260919/Capture` : répétition depuis la graine, repères
d'activité, comparaison des seuils, vérification NAND et compte rendu final `completion.json`.
- `.starlet-profile/ReadStudyE.cpp`, `BuildReadStudyE.ps1`, `CompareReadStudyESized.ps1` :
banc réel de lectures/écritures scalaires et comparaison alternée de D/E.
- `.starlet-profile/read-e-sized-ab-samples-20260919.json` et
`read-e-sized-ab-samples-20260919-reverse.json` : mesures finales dans les deux ordres.
- `.starlet-profile/tests-E-sized-final-20260919.log`, `tests-E-sized-repeat-20260919.log` :
tests complets et répétitions ciblées du candidat E.
- `.starlet-profile/PrepareUpdateE20260919.ps1`, `.starlet_update_ab_20260913/manifest-E-20260919.json`
et `E/Capture` : provenance, profil et captures du cinquième essai natif.
- `.starlet-profile/AnalyzeUpdateE20260919.ps1`, `E/Capture/completion-report.json`,
`journal-progress-comparison.json`, `delta-vs-D.json` : repère de réussite et bilan natif E.
- `E/Capture/final-journal-verification.txt`, `update-completion-prefix-verification.txt`
et `completion.json` : contrôles après fermeture, empreintes et compte rendu final E.
- `.starlet-profile/ReadStudyF.cpp`, `ReadStudyF20260919.exe`, `CompareMemoryProfileF.ps1` :
même banc réel avec choix ON/OFF au démarrage, sans changer de binaire.
- `.starlet-profile/memory-profile-F-20260919.json`, `memory-profile-F-20260919-reverse.json` :
180 mesures brutes du candidat F.
- `.starlet-profile/tests-F-expanded-20260919.log`, `tests-F-repeat-20260919.log` et
`build-F-memory-profile-20260919.log` : tests, répétitions et compilation.
- `.starlet-profile/PrepareUpdateF20260919.ps1`, `.starlet_update_ab_20260913/manifest-F-20260919.json`
et `F/Capture/launch.json` : provenance et lancement du sixième essai natif isolé.
- `.starlet-profile/AnalyzeUpdateF20260919.ps1`, `F/Capture/report-window-summary.json`,
`journal-progress-comparison.json`, `journal-progress-phases.json`, `deltas-vs-D-E.json` :
comparaison native, limitée aux captures complètes du lecteur corrigé et à la fenêtre du test.
- `F/Capture/completion-report.json`, `final-journal-verification.txt`,
`update-completion-prefix-verification.txt`, `completion.json` : fin signalée et contrôles NAND.
Les prototypes et données de mesure restent dans les répertoires locaux ignorés.
Ce rapport est la seule nouvelle documentation destinée au dépôt ; les modifications
préexistantes du correctif d'appairage Wiimote sont conservées.