El NYI de LuaJIT que envenenó en silencio un bucle caliente sin relación

Fuentes: The LuaJIT NYI That Silently Poisoned an Unrelated Hot Loop

LuaJIT, el compilador JIT del lenguaje de scripting Lua —usado en juegos como Factorio y World of Warcraft y en herramientas como Neovim u OpenResty—, presume de velocidad, pero puede estrangular el rendimiento de forma inadvertida cuando el código activa uno de sus NYI (Not Yet Implemented): operaciones que el compilador no logra traducir a código máquina optimizado. Este artículo analiza dos trampas concretas y propone cómo blindar la integración continua contra ellas.

El detonante fue una bench de grug-for-lua, una implementación Lua del lenguaje de modding grug. Con idéntico código y entradas, el mismo benchmark reportaba 6.000 millones de iteraciones en unas ejecuciones y apenas 300 millones en otras, una diferencia de 20 veces. El culpable: una llamada inocua a unpack dentro de un pcall que provoca que LuaJIT liste en negro una función totalmente ajena al bucle caliente.

LuaJIT es un JIT de trazado: registra los caminos más calientes del programa y los compila. Cuando tropieza con un NYI —unpack es uno de ellos, marcado como stitch 2.1— aplica una costura que permite reanudar el trazado tras la instrucción incompatible. El problema no está en unpack, sino en que al volver a través del pcall el grabador no recuerda estar dentro de un FRAME_PCALL, aborta con el error “NYI: return to lower frame” y, tras varios intentos, marca la función como blacklisted. El bucle caliente posterior, que jamás toca pcall ni unpack, queda entonces atrapado en el intérprete y sufre un slowdown de 14 veces.

El artículo muestra un ejemplo mínimo reproducible con el que se reproduce la divergencia y propone como solución reemplazar unpack por wrappers generados por número de argumentos, evitando el NYI de raíz.