Back to flin
flin

L'opcode CreateEntity qui a disparu

Comment un gestionnaire d'opcode manquant dans la machine virtuelle de FLIN a silencieusement cassé toute la création d'entités dans les fonctions -- et le processus de débogage qui l'a trouvé.

Juste A. Gnimavo (Thales) & Claude | March 26, 2026 5 min flin
EN/ FR/ ES
flinbugopcodevmdebuggingwar-story

Il existe une classe particulière de bugs qui vous fait douter de la réalité. Tout semble correct. Le code compile. Le serveur répond. Aucun message d'erreur n'apparaît nulle part. Et pourtant quelque chose de fondamental est cassé, et la seule preuve est une absence -- la chose que vous attendiez ne se produit tout simplement pas.

Le 3 février 2026, nous avons rencontré l'un de ces bugs dans FLIN. La création d'entités à l'intérieur de fonctions avait silencieusement cessé de fonctionner. Les utilisateurs pouvaient cliquer sur « Add Task » dans l'application todo et rien ne se passait. Pas d'erreur. Pas de crash. Pas d'avertissement. Le bouton était cliqué, la requête partait, le serveur répondait avec {"type":"ok"}, et la liste des tâches restait inchangée. Le silence était le symptôme.

La matrice des symptômes

Le premier indice que quelque chose d'architectural était cassé venait du pattern de ce qui fonctionnait versus ce qui ne fonctionnait pas :

OpérationStatutNotes
saveEdit(task) -- modification d'entités existantesFonctionnelChamps mis à jour correctement
toggleTask(task) -- modification de champs d'entitéFonctionnelToggle booléen persisté
deleteTask(task) -- suppression d'entitésFonctionnelEntités supprimées proprement
addTask() -- création de nouvelles entitésCasséRien ne se passait du tout

Modification, toggle, suppression -- tout fonctionnait. Seule la création était cassée. Et la création était la seule opération qui nécessitait de construire un tout nouvel objet entité à partir de rien.

Comprendre le pipeline d'exécution

La VM de FLIN a deux modes d'exécution principaux. La méthode principale execute() exécute le programme bytecode complet du début à la fin. Mais lors du traitement des requêtes d'action -- clics de boutons, soumissions de formulaires -- la VM utilise une méthode différente appelée execute_until_return. Cette méthode exécute le bytecode d'une seule fonction et s'arrête quand cette fonction retourne.

La distinction compte parce que execute_until_return maintient sa propre table de dispatch d'opcodes. C'est, en effet, une mini-VM dans la VM, ne gérant que les opcodes qui peuvent apparaître dans les corps de fonctions. Et c'est là que réside le problème : si un opcode n'est pas dans cette table de dispatch, il n'est pas exécuté. Il tombe dans un cas par défaut qui avance silencieusement le pointeur d'instruction sans rien faire de significatif.

Le gestionnaire manquant

À la Session 269, nous avions ajouté plusieurs gestionnaires d'opcodes à execute_until_return pour supporter le nouveau système d'actions. Chaque opcode nécessaire pour modifier, supprimer et sauvegarder des entités était présent. Mais CreateEntity -- l'opcode qui construit une nouvelle instance d'entité -- n'était pas dans la liste. Il avait été oublié parce que tous nos tests se concentraient sur saveEdit(task), qui modifie des entités existantes en utilisant SetField. Nous n'avions jamais testé addTask() avec le nouveau système d'actions.

Comment l'échec silencieux fonctionne

Le bytecode pour créer et sauvegarder une nouvelle tâche ressemble à ceci :

ip=1742: CreateEntity (0x77) + u16 type_idx + u8 field_count = 4 bytes
ip=1746: StoreLocal (0x21) + u8 slot = 2 bytes
ip=1748: LoadLocal (0x20) + u8 slot = 2 bytes
ip=1750: Save (0x90) = 1 byte
ip=1751: LoadNone + Return

La VM n'a jamais atteint Save à ip=1750 car elle a mal interprété les octets d'opérandes de CreateEntity comme des opcodes. L'entité n'a jamais été créée. Pas d'erreur. Pas de crash. Juste le silence.

Le processus de débogage

Trouver ce bug a nécessité trois techniques de débogage complémentaires : le suivi des offsets de bytecode, le traçage d'exécution de la VM, et un compteur d'opérations sur les entités. Quand le compteur était à zéro après une fonction qui aurait dû sauvegarder une entité, nous avions la preuve définitive que le chemin entier de création et de persistance d'entité était sauté.

La correction

La correction elle-même était substantielle mais mécanique -- ajouter le gestionnaire CreateEntity à execute_until_return :

rustOpCode::CreateEntity => {
    let type_idx = self.read_u16(code);
    let field_count = self.read_u8(code) as usize;
    let entity_type = self.get_identifier(chunk, type_idx)?;

    // Auto-register entity schema if not already registered
    if !self.database.has_entity_type(&entity_type) {
        use crate::database::EntitySchema;
        let schema = EntitySchema::new(&entity_type);
        let _ = self.database.register_entity(schema);
    }

    let mut entity = EntityInstance::new(entity_type.clone());

    // Pop field values and names from the stack
    for _ in 0..field_count {
        let value = self.pop()?;
        let name = self.pop()?;
        if let Value::Object(id) = name {
            if let Ok(s) = self.get_string(id) {
                entity.fields.insert(s.to_string(), value);
            }
        }
    }

    let id = self.alloc(HeapObject::new_entity(entity));
    self.push(Value::Object(id))?;
}

Leçons pour les concepteurs de langages

Ce bug illustre plusieurs principes qui s'appliquent à toute machine virtuelle ou implémentation d'interpréteur.

Premièrement, les échecs silencieux sont la classe de bugs la plus dangereuse. Deuxièmement, les tables de dispatch parallèles doivent rester synchronisées. Troisièmement, testez le flux complet, pas seulement les composants.

L'opcode CreateEntity a été manquant pendant peut-être un jour avant que nous le remarquions. Il a fallu trente minutes pour le corriger une fois le problème identifié, mais ces trente minutes d'investigation étaient une leçon magistrale en débogage systématique.


Ceci est la partie 156 de la série « Comment nous avons construit FLIN », documentant comment un CEO à Abidjan et un CTO IA ont conçu et construit un langage de programmation à partir de zéro.

Navigation de la série : - Arc précédent : bibliothèque standard et écosystème FLIN - [156] L'opcode CreateEntity qui a disparu (vous êtes ici) - [157] Le bug d'itération de la boucle for

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles

Thales & Claude deblo

Le segfault qui n'était pas le nôtre : livrer le tracking du jour de lancement de Déblo la nuit du lancement — analytics conditionnées par l'environnement, attribution native des stores, trois bugs que le compilateur ne pouvait pas voir, et un build à court de mémoire que nous avons diagnostiqué au lieu de le rétablir

Le 1er juillet 2026 — jour de lancement — le risque n'a jamais été le texte. C'était les campagnes payantes qui partaient à l'aveugle. Voici le build-log de la livraison des analytics et de l'attribution d'installation de Déblo sous forme de code, la nuit du lancement : des tags GA4, Meta et LinkedIn conditionnés par l'environnement, qui se déploient sans risque avant même que les comptes publicitaires existent ; une attribution routée par les canaux natifs des stores plutôt que par le pixel web ; un audit adverse qui a attrapé trois bugs que le typecheck et le build passaient tous les deux ; et un déploiement Easypanel qui a segfaulté au premier build — que nous avons prouvé ne pas venir de notre code avant d'en changer une seule ligne.

18 min Jul 1, 2026
deblolaunch-dayclaude-opus-4.8claude-code +26
Thales & Claude thales

Treize agents, quarante-trois minutes : la première session Workflow de Claude Fable 5, et ce qu'un script d'orchestration déterministe change aux builds multi-agents

Un prompt, treize agents, quarante-trois minutes : la première session de production avec Claude Fable 5 et l'outil Workflow de Claude Code a livré un site web de production complet de sept pages plus un endpoint backend de capture de leads, en un seul commit. Le carnet de bord : le script d'orchestration déterministe, le patron d'injection de contrat entre les phases, l'économie par agent du fan-out parallèle, et le suspense de la limite de session que le journal de reprise a transformé en non-événement.

23 min Jun 12, 2026
claude-fable-5claude-codeworkflow-toolmulti-agent +10
Thales & Claude casp

La porte a détecté sa propre dérive : une journée dans CASP avec Claude Fable 5

Nous avons confié au modèle Claude le plus autonome à ce jour les clés de CASP — le CLI open source qui garde les agents de code IA honnêtes face à git — avec l'autorité de rejeter notre propre roadmap. Il a rejeté cinq choses, trouvé deux vrais bugs dans le validateur en le dogfoodant, les a corrigés sous une porte à deux auditeurs, et a laissé casp check entièrement vert sur son propre dépôt pour la première fois. CASP 0.3.0 en est le résultat.

16 min Jun 10, 2026
caspzerosuiteworkflowai-cto +9