Principe du vérificateur inversé

Valen introduit un vérificateur d'emprunts qui parcourt les fonctions à l'envers, contrairement aux analyseurs classiques qui progressent du haut vers le bas. Cette inversion intervient lors de la quatrième phase du processus de compilation, où le dernier énoncé est examiné en premier, puis chaque instruction précédente est analysée jusqu'au début de la fonction. Le texte indique explicitement que « c’est le seul vérificateur d’emprunts dont la vérification de sécurité se fait à rebours », ce qui constitue le point de départ de l’ensemble de la méthode.

Phases de compilation et regroupement statique

Le compilateur Valen se compose de quatre phases principales. La première phase, non détaillée, est suivie d’une phase de typage statique qui associe des types concrets aux variables ; par exemple, le code

let fuel = 5; → let fuel i64 = 5;
montre la transformation typique. La troisième phase réalise un « groupement statique » en avant, identifiant le lieu exact d’une référence, comme
let first_ref = &s.fuel_cells[0]; // → first_ref &int in s.fuel_cells[]
. Cette information est intégrée au type de la variable et sera exploitée lors de la phase de vérification.

Analyse rétrograde des usages et mutations

La phase finale (phase 4) utilise le résultat du groupement pour détecter les use‑after‑free. Le mécanisme repose sur trois événements : DECLARE (déclaration d’une référence), USE (lecture ou appel) et MUTATE (modification d’un groupe). En parcourant le code à rebours, le vérificateur maintient une liste pending contenant les références USE rencontrées plus bas dans le flux. Lorsqu’une instruction MUTATE apparaît, le compilateur s’assure qu’aucune référence pending ne pointe vers une zone invalidée. L’exemple suivant illustre le processus :

struct Entity { hp int; buffs []int; }
func foo(e &Entity mut) {
  let buff = &e.buffs[0]; // DECLARE buff
  e.add_buff();           // USE e / MUTATE e
  print_int(buff);        // USE buff
}

Le scan commence par print_int(buff), ajoute buff à pending, puis rencontre e.add_buff(). La mutation de e invalide buff, déclenchant une erreur de use‑after‑free. En inversant l’ordre, le vérificateur évite de devoir propager l’information temporelle (ligne de création) dans le type, simplifiant ainsi la représentation des groupes.

Implications et limites

Cette approche rétrograde offre deux avantages concrets : elle simplifie la gestion des boucles où les références peuvent être créées et consommées dans des itérations successives, et elle réduit le coût de propagation des informations de groupe, car chaque instruction ne doit être analysée qu’une fois. Cependant, le texte mentionne un « cinquième phase secrète » qui calcule les attributs LLVM, indiquant que la génération de code final reste dépendante d’une étape supplémentaire non décrite. De plus, le modèle repose sur la capacité du compilateur à identifier correctement les groupes statiques ; toute imprécision dans le phase 3 pourrait entraîner des faux positifs ou des omissions lors du scan inverse. En l’état, la méthode est présentée comme fonctionnelle pour les programmes simples, mais son comportement face à des structures de contrôle complexes (exceptions, closures) n’est pas détaillé dans la source.