domingo, 12 de fevereiro de 2012

Formato de Arquivo Lua

Nesse artigo vamos descrever a arquitetura interna de um arquivo compilado na linguagem Lua. Conhecendo a sua estrutura interna ajuda na construção de ferramentas como máquinas virtuais (Virtual Machines), programas case (Computer-Aided Software Engineering), otimizadores, interpretadores, etc. A versão utilizada é a 5.1.

Na figura a seguir, temos a visão macro de um arquivo compilado lua. Note que esse arquivo é composto por uma estrutura chamada de "Header" que significa cabeçalho, aonde estarão descritas informações gerais sobre a versão usada, tamanho das instruções, etc. E a outra estrutura, "Função" é a função nível Top ou Root (raiz) de um arquivo lua. Essa é a principal função desse arquivo lua compilado, quando a máquina virtual lua começa a execução do arquivo compilado, é essa função a primeira a ser executada e por padrão, essa função sempre existirá.



Note também que na figura está descrito "1 Header" que indica que existe apenas 1 (uma) estrutura do tipo Header. E da mesma maneira, "1 Função" indica que existe apenas 1 (uma) estrutura do tipo Função.

Vamos agora descrever a primeira estrutura do arquivo lua (Header). Na figura a seguir, temos os campos da estrutura Header que ocupa 12 bytes no início do arquivo lua.


Na próxima figura, descreveremos a estrutura "Função". Essa estrutura pode conter subfunções em forma de árvores na qual um pai tem N filhos mas o filho só pode ter um pai. E as subfunções seguem a mesma estrutura (campos, listas, etc) da função de nível top. Exceto para o campo "source name" que na função top terá o nome do arquivo fonte lua (extensão .lua) que originou o arquivo compilado. Para as demais subfunções, esse campo tem tamanho 0 (zero).


Nessa figura vemos que a função possuí estruturas simples como bytes e compostas (delimitadas por um retângulo ou "box"). Na figura a seguir, descreveremos a estrutura composta "Size_t" que será usada para definir o tamanho da estrutura "String" no arquivo compilado lua.


Nessa estrutura "Size_t" temos apenas N bytes para representar o tamanho de qualquer objeto. Esse "N" é fixo e está definido no campo "Tamanho do size_t" na estrutura Header. O padrão é 4 bytes, então N=4 bytes.

Na próxima figura, temos a estrutura "String" que armazena o objeto do tipo "string" (cadeia de caracteres) na linguagem lua. O campo do tipo "Size_t" armazena o tamanho da string lua. E o campo do tipo "N Bytes" armazena os caracteres e mais o caracter NUL (ASCII 0) no fim do array de bytes.


A próxima estrutura descrita será o "Integer" que é usado nas estruturas "Função", "Lista de Instruções", "Lista de Constantes", "Lista de Funções", "Lista de posições de linha de código", "Lista de variáveis locais", "Variável Local" e "Lista de upvalues". O campo do tipo "N Bytes" tem o tamanho definido no campo "Tamanho do int" na estrutura Header. Esse tamanho geralmente é de 4 bytes, fazendo N=4 bytes. Essa estrutura armazena o número inteiro usado em todo arquivo compilado lua.


A próxima subestrutura da estrutura "Função" é a lista de instruções lua (Lista de instruções). É nessa lista que temos todas as instruções lua para executar a própria função. A próxima figura mostra o conteúdo dessa lista de instruções. O campo do tipo "Integer" armazena o número de instruções presentes na lista. E o campo do tipo "N Instruções" armazena as instruções a serem usadas na execução da função lua.


Na figura a seguir, estará descrito o conteúdo da instrução lua.
A instrução lua apenas armazena um array de bytes cujo comprimento está definido no campo "Tamanho da instrução" da estrutura Header, geralmente o tamanho usado é 4 bytes, então N=4 bytes.


A próxima figura descreve a "Lista de constantes", essa lista armazena todas as constantes usadas durante a codificação do programador no arquivo fonte lua. Essas constantes são de 4 tipos básicos: nil, boolean, number e string. O primeiro campo do tipo "Integer" define o número de constantes contidas nessa lista. 


A constante em si será mostrada na próxima figura. O primeiro campo "1 byte" define o tipo da constante e o tamanho do próximo campo (a constante em si) estará em função do tipo definido no primeiro campo. Por exemplo, para a constante do tipo 0 (nil), o segundo campo (Const) não existirá (ou de tamanho zero). Para a constante do tipo 1 (boolean), o segundo campo (Const) será de tamanho 1 byte. Para a constante do tipo 3 (lua number), o segundo campo será do tamanho do número lua definido no Header, geralmente de 8 bytes. E por último, para a constante do tipo 4 (string), o tamanho é definido pelo comprimento da própria string.


O próximo campo da estrutura "Função" é a "Lista de funções" e nessa estrutura está contida todas as subfunções que a função possui. O campo do tipo "Integer" define o número de subfunções e o campo "N Funções" define N subfunções dentro da função.



Campos opcionais da estrutura "Função" que são usados para depuração (debug).

Os campos "Lista de posições de linha de código", "Lista de variáveis locais" e "Lista de upvalues" são estruturas usadas para realizar depuração (debug) no código e essas mesmas podem ser descartadas através do uso de um parâmetro (-s) no compilador lua (luac).

A estrutura "Lista de posições de linha no código" armazena todas as posições no código fonte para serem usadas na depuração (mais detalhes na próxima figura).


A estrutura "Lista de variáveis locais" armazena as variáveis locais (identificadores e escopo) para serem usadas na depuração (debug).


A estrutura "Variável local" armazena o nome (identificador) da variável local (definida no código fonte pelo programador) e o escopo. Esse "escopo" é definido pelo PC (Program Counter) de início do escopo da variável (segundo campo) e pelo PC de fim do escopo da variável.


A estrutura "Lista de upvalues" armazena todos os nomes das variáveis upvalues usadas no programa para depuração (debug).



Referências