INF1018 - Software Básico - 2012.1

Segundo Trabalho: Gerador de Código

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

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


A Linguagem SB

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

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

func::cmd '\n' | cmd '\n' func
cmd::att | if | ret
att::var '=' varc op varc
if::'if' var num num num
ret::'ret' varc
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 SB (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 retorna o maior de três parâmetros não negativos:

v0 = p0 - p1	//1: v0 é menor que 0 se p0 < p1
if v0 3 3 7 	//2: trata os 3 casos (p0 < p1, p0 = p1, p0 > p1)
v0 = p1 - p2 	//3: v0 é menor que 0 se p1 < p2
if v0 5 5 6 	//4: trata os 3 casos (p1 < p2, p1 = p2, p1 > p2)
ret p2 		//5: p2 é o maior parâmetro
ret p1 		//6: p1 é o maior parâmetro
v0 = p0 - p2 	//7: v0 é menor que 0 se p0 < p2
if v0 5 5 9 	//8: trata os 3 casos (p0 < p2, p0 = p2, p0 > p2)
ret p0 		//9: p0 é o maior parâmetro


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 SB, 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 SB 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 SB.


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 SB a ser compilada deve ter sempre o nome "SB".

Sua função main deverá abrir o arquivo contendo o código SB 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 SB lida do arquivo de entrada.

Para testar a chamada de uma função SB 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, por exemplo, usando o programa objdump.

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 SB, 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 SB 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 25 de junho por mensagem eletrônica.
Será descontado um ponto por dia de atraso.



Entrega