Compilador JIT simples para SmallLua

O objetivo desse trabalho é implementar um compilador JIT simples para SmallLua, que compila o bytecode de uma função SmallLua para código de máquina equivalente sempre que a função é executada mais de uma vez. O compilador estará ativo apenas para a representação “nanbox” de números e ponteiros, e precisa funcionar apenas na arquitetura Linux/x64.

Pode-se estender a estrutura Proto presente no interpretador para representar também funções compiladas, adicionando dois campos: o primeiro é um contador de quantas vezes funções com aquele protótipo foram executadas no modo interpretado, e que vai ser usado para disparar o compilador JIT. O segundo é um ponteiro de função para o código compilado dela.

A função compilada recebe como argumentos um ponteiro para o estado do interpretador (registrador RDI), um ponteiro para o topo da pilha de frames de chamada (RSI), o índice do primeiro argumento da função na pilha do interpretador (RDX), um ponteiro para a estrutura Function da função (RCX), um ponteiro para o vetor de constantes (R8) e o um ponteiro para o vetor de upvalues em aberto (R9). Os registradores são os usados na convenção de chamada Linux/x64.

Quando o interpretador chama uma função compilada ele deve salvar os dados da chamada atual na pilha de chamadas do mesmo modo que na chamada de uma função interpretada, crescer a pilha de dados, alocar o vetor de upvalues e finalmente chamar a função. Como a função compilada não tem acesso às variáveis locais do interpretador é ele que vai restaurar os valores que foram salvos da pilha de chamadas que seria feito no opcode RET (o ajuste da pilha para remover os argumentos e a função chamada e deixar o valor de retorno, assim como o fechamento dos upvalues e a liberação do vetor de upvalues em aberto, deve ser feito pelo código compilado de RET).

Quando uma função compilada chama outra função compilada ela salva os dados atuais na pilha de chamadas, cresce a pilha de dados, aloca o vetor de upvalues em aberto e chama a função. Após a chamada os valores salvos na pilha são restaurados.

Quando uma função compilada chama uma função interpretada ele deve chamar o compilador JIT nessa função, depois chamar ela normalmente. Lembre-se de salvar o valor dos registradores que são caller saves (incluindo os seis registradores dos argumentos) sempre que precisar chamar uma função primitiva ou uma função auxiliar do interpretador. Esse salvamento pode ser na pilha do processador, não precisa ser na pilha de chamadas do interpretador.


Última Atualização: 2016-12-05 17:40