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 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
Thales & Claude zerosuite

La transplantation du CASP : comment la discipline des six fichiers est passée de Conductor à un ERP transport anti-fraude, ce que la compétence /next ajoute quand l'opérateur tape juste « next », et pourquoi le coût d'une dérive du CASP grimpe quand le projet, c'est l'argent des autres

La discipline du CASP qui a piloté trente-cinq sessions de Conductor est agnostique au produit. Le carnet de bord de sa transplantation sur KASSIA, un ERP transport anti-fraude pour un exploitant de flotte en Côte d'Ivoire : ce qui a migré, ce qui n'a pas migré (le validateur sur mesure — et ce que son absence coûte), ce que la compétence /next ajoute quand l'opérateur tape un seul mot, et là où le CASP s'arrête — le bug de déploiement qu'il ne pouvait pas voir parce qu'il enregistre l'intention, pas la réalité de l'infrastructure.

23 min Jun 8, 2026
kassiaerp-kassia-transport-logistiquezerosuiteCASP +15