Architecture

Les décisions qui portent le reste, et le raisonnement derrière chacune — y compris celles qu'il a fallu réécrire pour comprendre.

L'essentiel de wisq est sans surprise. Voici les parties qui ne le sont pas : là où l'approche évidente est fausse, et où savoir pourquoi épargne une journée à la personne suivante.

Du Rust, et une application hybride

Tout est en Rust sauf l'application mobile, qui est hybride. Le démon hôte et l'interpréteur RISC-V sont en Rust — un programme sans interface, et une boucle sur un tableau d'octets ; ni l'un ni l'autre n'a de raison d'embarquer un runtime de langage. L'application fait exception parce qu'elle le doit : l'interface, le modèle tactile et le client de bureau distant reposent sur UIKit et Network.framework, qui appartiennent à la plateforme qu'ils visent. C'est donc une coquille Swift autour d'un cœur Rust, et la couture entre les deux est une ABI C de sept fonctions.

Pour toute nouveauté, la question est de savoir à laquelle des deux formes elle ressemble, pas quel langage est plus agréable à écrire.

Pas de JIT, et il n'y en a jamais eu

iOS n'accorde de mémoire exécutable qu'aux applications signées en développement. Tout émulateur sur l'App Store est donc un interpréteur, et ceux qui prétendent le contraire ne sont pas sur l'App Store. Ce n'est pas une limite que wisq contourne — c'est sa prémisse. Le travail est allé à rendre l'interpréteur rapide.

ChangementEffet
Registres hors du tableau Swift2,7× — l'optimiseur rechargeait le tampon après chaque appel opaque
Extension de signe sans branchement+8 % — loads et stores font 47 % d'un boot Linux
RAM invitée mappée au lieu d'effacéeconstruction 33–194 ms → moins de 0,1 ms
QoS explicite sur le fil d'émulationévite que l'interpréteur soit posé sur un cœur d'efficience

Trois autres tentatives ont été mesurées puis abandonnées : sortir les opcodes froids hors ligne coûtait 9 %, l'écriture arrière inconditionnelle 3 %, et une table de répartition plus dense ne rapportait rien. Une mesure négative vaut autant qu'une positive, et les deux sont dans l'historique.

Le format de pixels est négocié pour le rendu, pas pour le réseau

Un client RFB peut demander au serveur la disposition de pixels qu'il veut. Le choix tentant est celui qui envoie le moins d'octets ; le bon est celui que le téléphone peut remettre à la pile graphique sans y toucher. wisq demande du 32 bits petit-boutiste avec le rouge en 16, le vert en 8 et le bleu en 0, ce qui arrive en mémoire sous la forme B, G, R, X — exactement ce que Core Graphics lit en byteOrder32Little avec la première composante ignorée.

CPIXEL n'est pas TPIXEL

ZRLE encode les couleurs en CPIXEL, trois octets dans l'ordre négocié — B, G, R pour le format ci-dessus. Tight les encode en TPIXEL, qui est toujours R, G, B quel que soit ce qui a été négocié. Intervertir les deux produit une image parfaitement lisible et parfaitement fausse de couleur, sans la moindre erreur nulle part. C'est le moyen le plus simple de perdre un après-midi dans ce dépôt.

Les flux zlib vivent aussi longtemps que la session

Les encodages compressés partagent leurs dictionnaires d'un rectangle à l'autre : un flux est ouvert une fois et alimenté pendant toute la vie de la connexion. Un octet mal analysé ne corrompt pas un rectangle, il corrompt toutes les images suivantes. D'où des flux neufs à chaque reconnexion plutôt que réutilisés, et un décodeur éprouvé contre des fixtures produites par un zlib de référence plutôt que par lui-même.

La console est incrémentale

La façon évidente de rendre une console série est de garder chaque octet et de re-dériver le texte visible à chaque arrivée. C'est un travail proportionnel à tout l'historique par morceau — quadratique dans la sortie. Mesuré : 2 000 lignes coûtaient 36,7 secondes de traitement, contre 0,22 aujourd'hui. L'émulateur restait rapide pendant que l'interface fondait, ce que l'utilisateur lit comme une VM lente.

Être incrémental a aussi corrigé un vrai défaut : l'analyseur d'échappements garde maintenant son état entre les morceaux, donc une séquence coupée entre deux écritures est reconnue au lieu d'être imprimée telle quelle.

Le modèle tactile est le produit

Un bureau dessiné sur un téléphone est inutilisable si le pointeur est le bout du doigt. wisq dessine un curseur virtuel sur sa propre couche, décalé du doigt, avec de l'inertie — les petits boutons deviennent atteignables et l'on voit ce qu'on s'apprête à toucher. Appui et relâchement sont espacés de 50 ms et ordonnés, parce que les invités perdent les clics qui arrivent dans la même milliseconde.

Du déterminisme là où il est possible

L'horloge virtuelle de la machine locale avance avec les instructions exécutées, pas avec le temps réel. La même image de noyau démarre donc à l'identique sur chaque appareil et en intégration continue, ce qui rend un démarrage utilisable comme test — et permet à un banc de comparer honnêtement deux interpréteurs, car les comptes d'instructions doivent coïncider avant que les débits veuillent dire quelque chose.