INF1018 - Software Básico (2016.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") para uma linguagem de programação bastante simples, chamada SB.

A função compila deverá ler um arquivo texto contendo o código fonte de uma função escrita em SB e retornar um ponteiro para o início da região de memória que contém o código de máquina que corresponde à tradução da função SB.

Deve ser implementada também uma função libera, que libera a memória alocada para armazenar o código criado por compila.


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 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 '=' varpc op varpc
if::='if' var num num num
ret::='ret' varpc
var::='v' num
varpc::=var | 'p' num | '$' snum
op::='+' | '-' | '*'
num::=digito | num digito
snum::=[-] num
digito::=0' | '1' | '2' | '3' | '4' | '5' | '6' | '7'| '8' | '9'


Exemplos

Veja a seguir alguns exemplos de funções SB.

Atenção: os comentários não fazem parte da linguagem!



Implementação e Execução

O que fazer

Você deve desenvolver 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 correspondente, e retorne um valor do tipo "ponteiro para função"; este valor será o endereço da área de memória que contém o código gerado.

O arquivo de entrada terá no máximo 50 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.

Você deverá também desenvolver uma função que libere a área de memória alocada por compila, com o protótipo

void libera (void *p);

Implementação

Para cada instrução SB imagine qual uma tradução possível para assembly. Além disso, lembre-se que a tradução de uma função SB deve começar com o prólogo usual (preparação do registro de ativação, incluindo o espaço para variáveis locais) e terminar com a finalização padrão (liberação do registro de ativação antes do retorno da função). O código gerado deverá seguir as convenções de C/Linux quanto à passagem de parâmetros, valor de retorno e salvamento de registradores.

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 será feita na main!

O código gerado por compila deverá ser um código de máquina x86-64, 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 a tradução das instruções da linguagem SB. Para isso, você pode usar o programa objdump e, se necessário, uma documentação das instruções da Intel.

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 (usando a opção -c) para gerar um arquivo objeto meuteste.o, e usar o comando

objdump -d meuteste.o
para ver o código de máquina gerado.

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 alocada. Lembre-se que as instruções de máquina 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 SB estará sempre correto. Vale a pena colocar alguns testes 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.

Estratégia de Implementação

Este trabalho 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 bem simples usando:
    minhamaquina> gcc -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 uma versão inicial da função compila, que aloque uma área de memória, coloque lá esse código "colado" do compilador, bem conhecido, e retorne o endereço da área alocada.

    Crie uma função main e teste essa versão inicial da função (leia o próximo item para ver como fazê-lo). Teste também a sua função de liberação de memória (chamada pela main!)

  2. Ainda sem começar a traduzir uma função SB lida de um arquivo, você pode implementar a "montagem" dinâmica de um código que contenha o prólogo e a finalização da função. Novamente, teste essa implementação.

  3. Comece agora a implementação de atribuições e operações aritméticas e da instrução de retorno. Pense em que informações você precisa extrair para poder traduzir as instruções (quais são os operandos, qual é a operação, onde armazenar o resultado da operação).

    Implemente e teste uma operação por vez. Experimente usar constantes, parâmetros, variáveis locais, e combinações desses tipos como operandos.

    Lembre-se que é necessário alocar espaço (na pilha) para as variáveis locais!

  4. Deixe para implementar a instrução if apenas quando todo o resto estiver funcionando!

    Pense em que informações você precisa guardar para traduzir completamente essa instrução (note que há desvios envolvidos nessa tradução.

Testando o gerador de código

Você deve criar um arquivo contendo as funções compila e libera e outro arquivo com uma função main para testá-la.

Sua função main deverá abrir um arquivo texto que contém um "programa fonte" na linguagem SB (i.e, uma função SB) e chamar compila, passando o arquivo aberto como argumento. Em seguida, sua main deverá chamar a função retornada por compila, passando os parâmetros apropriados, e imprimir o valor de retorno dessa função (um valor inteiro). A função criada por compila é a tradução da função SB lida do arquivo de entrada.

Não esqueça de compilar seu programa com

gcc -Wall -Wa,--execstack -o seuprograma seuprograma.c

para permitir a execução do código de máquina criado por compila!

Uma sugestão para testar a chamada de uma função SB com diferentes parâmetros, é sua função main 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, convertidos para valores inteiros. Para fazer essa conversão você pode usar a função atoi.


Entrega

Deverão ser entregues via Moodle dois arquivos:

  1. Um arquivo fonte chamado compila.c , contendo as funções compila e libera (e funções auxiliares, se for o caso).
    • Esse arquivo não deve conter a função main.
    • Coloque no início do arquivo, como comentário, os nomes dos integrantes do grupo da seguinte forma:
      /* Nome_do_Aluno1 Matricula Turma */
      /* Nome_do_Aluno2 Matricula Turma */
      
  2. Um arquivo texto, chamado relatorio.txt, contendo um pequeno relatório.
    • O relatório deverá explicar o que está funcionando e o que não está funcionando. Não é necessário documentar sua função no relatório. Seu código deverá ser claro o suficiente para que isso não seja necessário.
    • O relatório deverá conter também alguns exemplos de funções da linguagem SB que você usou para testar o seu trabalho. Mostre tanto as funções SB traduzidas e executadas com sucesso como as que resultaram em erros (se for o caso).
    • Coloque também no relatório o nome dos integrantes do grupo
Indique na área de texto da tarefa do Moodle o nome dos integrantes do grupo. Apenas uma entrega é necessária (usando o login de um dos integrantes do grupo) se os dois integrantes pertencerem à mesma turma.


Prazo


Observações