Back to flin
flin

El opcode CreateEntity que desapareció

Cómo un manejador de opcode faltante en la máquina virtual de FLIN rompió silenciosamente toda la creación de entidades en funciones -- y el proceso de depuración que lo encontró.

Thales & Claude | March 30, 2026 4 min flin
EN/ FR/ ES
flinrust

Hay una clase particular de error que te hace cuestionar la realidad. Todo parece correcto. El código compila. El servidor responde. No aparecen mensajes de error en ninguna parte. Sin embargo, algo fundamental está roto, y la única evidencia es una ausencia -- lo que esperabas que sucediera simplemente no sucede.

El 3 de febrero de 2026, encontramos uno de estos errores en FLIN. La creación de entidades dentro de funciones había dejado de funcionar silenciosamente. Los usuarios podían hacer clic en "Agregar tarea" en la aplicación de tareas y no pasaba nada. Sin error. Sin fallo. Sin advertencia. El botón se pulsaba, la solicitud se disparaba, el servidor respondía con {"type":"ok"}, y la lista de tareas permanecía sin cambios. El silencio era el síntoma.

La matriz de síntomas

La primera pista de que algo arquitectónico estaba mal vino del patrón de lo que funcionaba versus lo que no:

OperaciónEstadoNotas
saveEdit(task) -- editar entidades existentesFuncionandoCampos actualizados correctamente
toggleTask(task) -- modificar campos de entidadFuncionandoToggle booleano persistido
deleteTask(task) -- eliminar entidadesFuncionandoEntidades eliminadas correctamente
addTask() -- crear nuevas entidadesRotoNo pasaba nada en absoluto

Editar, alternar, eliminar -- todo funcionando. Solo la creación estaba rota. Y la creación era la única operación que requería construir un nuevo objeto de entidad desde cero.

El manejador faltante

En la Sesión 269, habíamos agregado varios manejadores de opcodes a execute_until_return para soportar el nuevo sistema de acciones. Cada opcode necesario para editar, eliminar y guardar entidades estaba presente. Pero CreateEntity -- el opcode que construye una nueva instancia de entidad -- no estaba en la lista. Había sido pasado por alto porque todas nuestras pruebas se enfocaban en saveEdit(task), que modifica entidades existentes usando SetField. Nunca probamos addTask() con el nuevo sistema de acciones.

Cómo funciona el fallo silencioso

Cuando la VM encontraba el bytecode para crear y guardar una nueva tarea:

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 no tenía manejador para CreateEntity en execute_until_return. El caso por defecto avanzaba el puntero de instrucciones solo un byte, pero CreateEntity es una instrucción de cuatro bytes. Así que el IP avanzaba al medio de los operandos de la instrucción. El siguiente byte se interpretaba como un opcode. Era basura, pero el caso por defecto lo manejaba avanzando de nuevo. El IP vagaba por el bytecode hasta que eventualmente golpeaba algo que causaba un retorno temprano.

La entidad nunca se creaba. El opcode Save nunca se alcanzaba. Sin error. Sin fallo. Solo silencio.

La corrección

La corrección fue sustancial pero mecánica -- agregar el manejador CreateEntity a 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());

    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))?;
}

Lecciones para diseñadores de lenguajes

Este error ilustra varios principios que aplican a cualquier implementación de máquina virtual o intérprete.

Primero, los fallos silenciosos son la clase más peligrosa de errores. Un mensaje de error, por críptico que sea, al menos te dice que algo salió mal. Los fallos silenciosos no te dan nada.

Segundo, las tablas de despacho paralelas deben permanecer sincronizadas. Cualquier vez que tengas dos rutas de código que manejen el mismo conjunto de operaciones, agregar algo a una y olvidar la otra es una inevitabilidad.

Tercero, probar el flujo completo, no solo los componentes. Teníamos pruebas extensas para la creación de entidades. Teníamos pruebas para el sistema de acciones. Pero no teníamos una prueba que creara una entidad dentro de una función llamada por el sistema de acciones.


Esta es la Parte 156 de la serie "Cómo construimos FLIN", que documenta cómo un CEO en Abidjan y un CTO de IA diseñaron y construyeron un lenguaje de programación desde cero.

Navegación de la serie: - Arco anterior: biblioteca estándar y ecosistema de FLIN - [156] El opcode CreateEntity que desapareció (estás aquí) - [157] El error de iteración del bucle for

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles