INF1018 - Software Básico - 2013.1

Segundo Trabalho: Gerador de Código

O objetivo deste trabalho é desenvolver, em C, uma função compila que implementa um pequeno gerador de código (um "micro-compilador") dinâmico para uma linguagem de programação bastante simples, chamada Limitada.
A função compila deverá ler um arquivo texto contendo o código fonte de uma função escrita em Limitada e retornar um ponteiro para uma função (criada pelo gerador) que contém o código que executa a função Limitada.

Os trabalhos podem ser feitos em grupos de até dois alunos. Leia com atenção o enunciado do trabalho e as instruções para a entrega. Em caso de dúvidas, não invente. Pergunte!


A Linguagem Limitada

A linguagem Limitada contém apenas três tipos de instruções: atribuição, desvio condicional e retorno.

A sintaxe da linguagem Limitada pode ser definida formalmente como abaixo:

func::cmd '\n' | cmd '\n' func
cmd::att | if | ret
att::var '=' varc op varc
if::'if' varc num
ret::'ret' varc
goto::'goto' snum
var::'v' digito | 'p' digito
varc::var | '$' snum
op::'+' | '-' | '*'
num::digito | num digito
snum::[-] num
digito::0' | '1' | '2' | '3' | '4' | '5' | '6' | '7'| '8' | '9'


Exemplos

O exemplo a seguir mostra a implementação da função f(a,b,c) = a * (b + c) na linguagem Limitada (os comentários não fazem parte da linguagem).
v0 = p1 + p2	//1: i = b + c
v0 = p0 * v0	//2: i = a * i
ret v0		//3: retorna i

O próximo exemplo implementa uma função que testa se dois números são iguais, retornando 0 caso sejam iguais e 1 caso sejam diferentes:

v0 = p0 - p1    //1: v0 é zero se os dois parâmetros forem iguais
if v0 4
ret $0
ret $1

O próximo exemplo implementa a função fat(n) em Limitada:

v0 = $0 + $1
if p0 4
ret v0
v0 = v0 * p0
p0 = p0 - $1
goto 2


O que fazer

Desenvolva, em C, uma função chamada compila que leia um arquivo de entrada contendo o código fonte de uma função na linguagem Limitada, gere o código de máquina IA32 correspondente, e retorne um ponteiro para função (será um ponteiro para uma área de memória contendo o código gerado).

O arquivo de entrada terá no máximo 100 linhas, com um comando Limitada por linha.

O protótipo de compila é o seguinte:

typedef int (*funcp) ();
funcp compila (FILE *f);
O único parâmetro de compila é o descritor de um arquivo texto já aberto para leitura, de onde deve ser lido o código fonte Limitada.


Execução

Você deve criar um arquivo contendo a função compila e outro arquivo com uma função main para testá-la. O arquivo texto contendo a função Limitada a ser compilada deve ter sempre o nome "programa.ltd".

Sua função main deverá abrir o arquivo programa.ltd e chamar a função compila, passando o arquivo aberto como argumento. Em seguida, sua função main deverá chamar a função retornada por compila, passando os parâmetros apropriados, e imprimir o valor de retorno dessa função. Esse retorno é um valor inteiro, que pode ser exibido com código de formação ("%d\n"). A função criada por compila é a tradução da função Limitada lida do arquivo de entrada.

Para testar a chamada de uma função Limitada com diferentes parâmetros, sua função main deve receber argumentos passados na linha de comando. Para ter acesso a esses argumentos (representados por strings), a sua função main deve ser declarada como

int main(int argc, char *argv[])
sendo argc o número de argumentos fornecidos na linha de comando e argv um array de ponteiros para strings (os argumentos). Note que o primeiro argumento para main (argv[0]) é sempre o nome do seu executável!. Os parâmetros que deverão ser passados para a função criada por compila serão o argumento 1 em diante.


Implementação

A função compila deve alocar um bloco de memória para escrever o código gerado. O valor de retorno de compila será um ponteiro para essa área. (Para simplificar, você pode considerar o número máximo de linhas de arquivo na hora de alocar o bloco de memória para código.)

O código gerado por compila deverá ser um código de máquina IA-32, e não um código fonte assembly. Ou seja, você deverá descobrir o código de máquina que corresponde às instruções de assembly que implementam cada uma das instruções de nossa linguagem, usando o programa objdump e possivelmente a documentação das instruções da Intel, disponível na página do curso. Por exemplo, para descobrir o código gerado por movl %eax, %ecx, você pode criar um arquivo meuteste.s contendo apenas essa instrução, traduzi-lo com o gcc para gerar um arquivo meuteste.o, e usar objdump -d meuteste.o para ver o código de máquina gerado.

Lembre-se que as instruções assembly ocupam um número variável de bytes na memória.

Não é necessário fazer o tratamento de erros do arquivo de entrada, você pode supor que o código nele estará sempre correto. Vale a pena colocar alguns testes (ver programa exemplo abaixo) só para facilitar a própria depuração do seu código, mas as entradas usadas como testes na correção do trabalho sempre estarão corretas.

Para ler e interpretar cada linha da linguagem Limitada, teste se a linha contém cada um dos formatos possíveis. Veja um esboço de código C para fazer a interpretação de código aqui. (Lembre-se que você terá que fazer adaptações pois, dentre outros detalhes, essa interpretação não deve ser feita na main!!)

IMPORTANTE: Esse programa não é trivial. Implemente sua solução passo a passo, testando separadamente cada passo implementado! Por exemplo:

  1. Compile um arquivo assembly contendo uma função simples usando:
    minhamaquina> gcc -m32 -c code.s
    
    (para apenas compilar e não gerar o executável) e depois veja o código de máquina gerado usando:
    minhamaquina> objdump -d code.o
    
    Construa um programa inicial que aloque espaço e coloque esse código "colado" do compilador, bem conhecido, para testar um esqueleto inicial.

  2. Implemente e teste a tradução de uma função Limitada que contenha apenas uma instrução de retorno (primeiro, retornando uma constante, em seguida, retornando um parâmetro de entrada).

  3. Implemente e teste cada operação aritmética por vez.

  4. E assim por diante...

Prazo

O trabalho deve ser entregue até meia-noite do dia 24 de junho por correio eletrônico.
Será descontado um ponto por dia de atraso.



Entrega