INF1715 - Compiladores
Hisham Muhammad
2014.1 - Segundas (RDC512) e quintas (L450), 09:00 às 11:00
h@hisham.hm
LabLua, (21)3527-1500 ramal 4523
Índice
Material das aulas
11/02 | - Aula 0 | - W.O. |
13/02 | - Aula 1 | - Apresentação da disciplina |
18/02 | - Aula 2 | - Análise léxica, expressões regulares, Lex |
20/02 | - Aula 3 | - Análise léxica manual; introdução a gramáticas |
25/02 | - Aula 4 | - Análise sintática: gramáticas |
27/02 | - Aula 5 | - Análise sintática top-down: LL(1), First, Follow |
06/03 | - Aula 6 | - Análise sintática top-down: analisador preditivo recursivo |
11/03 | - Aula 7 | - Análise sintática bottom-up: parsers LR, análise shift-reduce |
13/03 | - Aula 8 | - Análise sintática bottom-up: Yacc (GNU Bison), conflitos shift-reduce |
18/03 | - Aula 9 | - Análise sintática bottom-up: conflitos reduce-reduce, parsing GLR |
20/03 | - Aula 10 | - Ações semânticas na árvore sintática |
25/03 | - Aula 11 | - Árvore sintática abstrata: design e construção |
27/03 | - Aula 12 | - Construindo uma árvore sintática abstrata na prática |
01/04 | - Aula 13 | - Tabelas de símbolos: declarações de variáveis e tipos |
08/04 | - Aula 14 | - Verificação de tipos: regras de derivação |
15/04 | - Aula 15 | - Construindo uma tabela de símbolos na prática |
24/04 | - Aula 16 | - Sistemas de tipos: conversibilidade de tipos, coerção, overflow, regras para Mini-0 |
29/04 | - Aula 17 | - Representações intermediárias: introdução, código de três endereços |
06/05 | - Aula 18 | - Código de três endereços para Mini-0 |
13/05 | - Aula 19 | - Discussão sobre o trabalho |
15/05 | - Aula 20 | - Gramática do formato de código intermediário |
20/05 | - Aula 21 | - Construindo código intermediário na prática |
22/05 | - Aula 22 | - Geração de código: Blocos básicos |
27/05 | - Aula 23 | - Geração de código: Informação de próximo uso, seleção de registradores (pt.1) |
29/05 | - Aula 24 | - Geração de código: Seleção de registradores (pt.2) |
03/06 | - Aula 25 | - Geração de código: Assembly x86: Introdução, Mecanismos de controle |
05/06 | - Aula 25 | - Geração de código: Assembly x86: Procedimentos, Variáveis locais; dicas para usar gcc 32-bits no Ubuntu |
Existe um repositório da disciplina no Github com
os exemplos passados em aula. Para baixar a primeira vez, digite:
git clone https://github.com/hishamhm/inf1715
Isto vai gerar um diretório inf1715. Para baixar atualizações posteriores, entre no
diretório inf1715 e digite:
git pull
Avaliação
A avaliação da disciplina se dará através de uma série de trabalhos passados
ao longo do curso, que podem ser individuais ou em dupla.
Linguagem de implementação: Os trabalhos podem ser implementados
nas linguagens C, Lua ou Python. Recomenda-se fazer os trabalhos em C, que
será a linguagem utilizada nas aulas. No caso de trabalhos em linguagens
dinamicamente tipadas (Lua ou Python), as estruturas de dados devem estar
documentadas tal qual seriam na declaração de structs em C (isto é, devem
haver comentários explicando a hierarquia de estrutura das estruturas de dados
construídas, sejam em tabelas Lua ou em listas, dicionários e classes Python).
Consulte o professor se quiser fazer em alguma outra linguagem (ela pode ou
não ser liberada).
Github: Todos os alunos devem criar contas no site github.com e os trabalhos devem ser armazenados
como repositórios nas suas contas.
O registro do desenvolvimento do trabalho é parte da avaliação. O trabalho não
pode ser armazenado no repositório em um único commit: a série de commits deve
representar o processo de desenvolvimento (não se preocupe com comitar somente
código "pronto", a ideia é justamente registrar o trabalho incompleto em andamento).
Trabalhos em dupla: No caso de trabalho em dupla, cada aluno deve
ter sua própria conta e fazer seus commits como o seu próprio usuário, de modo
que no histórico do repositório fique registrado quais partes foram
implementadas por cada aluno; um único repositório deve ser o repositório
"master" onde ambos os alunos da dupla devem fazer commits.
Atenção: Se o funcionamento da turma em relação aos trabalhos for
satisfatório ao desenvolver os trabalhos dessa forma, não haverá necessidade
de realizar prova ao final do semestre.
Trabalho 1 - Análise Léxica
Você deve fazer um analisador léxico para programas escritos na linguagem
Mini-0, especificada abaixo.
- O analisador deve reconhecer todos os tipos de tokens da linguagem Mini-0
e classificá-los.
- O programa deve receber como primeiro argumento o o nome de arquivo do
programa Mini-0 a ser processado (se usar Flex, leia sobre a variável
yyin).
- Você deve gerar uma estrutura de dados que representa um token. Esta
estrutura deve armazenar o tipo do token, o valor que ele representa caso haja
algum (que pode ser numérico ou string; note que os tokens 15 e
0x0f representam o mesmo valor; escapes em strings devem ser
traduzidos também) e o número da linha no programa de entrada onde ele ocorre
(a primeira linha do programa é a linha 1).
- Valores não reconhecidos devem gerar tokens de erro.
- O programa deve listar na saída os tokens lidos e os seus dados.
- Você deve incluir casos de teste que exercitem todos os tipos de token,
inclusive erros.
Crie um repositório chamado
inf1715 na sua conta do Github. Para facilitar sua vida, clique em
"Initialize this repository with a README". Depois disso você pode fazer:
git clone https://github.com/seu_username/inf1715
cd inf1715
mkdir trab1
cd trab1
...e mãos à massa!...
A cada passo que você der no trabalho, faça um commit para ir registrando o histórico.
Não se preocupe que ele esteja incompleto, a ideia é exatamente essa. Não economize commits.
A maneira mais simples é assim, onde você já entra uma mensagem de log para registro:
git commit -a -m "Expressão regular para comentários" # por exemplo
Note que ao fazer git commit você só salvou o histórico localmente.
Envie o seu código em andamento periodicamente para o servidor, com o comando:
git push
Quando você terminar o trabalho, marque a versão no servidor como a final:
git tag trab1
git push --tags # se você não passar esta flag, a tag não vai pro servidor!
Quaisquer dúvidas na operação com git (ou sobre o trabalho de maneira geral), contacte o professor!
Prazo: 26/02/2014, 23:59. Será considerado como o trabalho entregue
o conteúdo do seu repositório com a tag trab1. Não deixe para o
último minuto (isto é, não assuma que você vai estar com internet funcionando
neste horário exato; Murphy é implacável.)
Trabalho 2 - Análise Sintática
Você deve fazer um parser para programas escritos na linguagem
Mini-0, especificada abaixo.
- O parser deve aceitar programas em Mini-0 que correspondam à
gramática livre de contexto abaixo (mais as especificações de prioridade
descritas abaixo) e rejeitar os que não correspondam.
- A estrutura da sua gramática não precisa (e não irá) corresponder exatamente à
estrutura abaixo (embora deva corresponder, é claro, à linguagem abaixo).
A especificação abaixo foi escrita para legibilidade
humana, não para processamento de máquina, e portanto tem ambiguidades.
- Você pode implementar uma gramática top-down escrevendo um parser
recursivo, ou uma gramática bottom-up usando o GNU Bison.
- O programa deve receber como primeiro argumento o o nome de arquivo do
programa Mini-0 a ser processado.
- Em caso de erros sintáticos (detectados na fase de lexing ou de parsing),
o programa deve informar na saída de erro (stderr) o erro encontrado
e o código de saída do programa deve ser diferente de zero.
- Em caso de parsing bem-sucedido, o programa deve reportar sucesso na
saída padrão (stdout) e o código de saída deve ser zero.
- Você deve incluir casos de teste que exercitem todas as regras da sua
gramática, inclusive erros.
Assim como no Trabalho 1, o programa deve ser desenvolvido no seu repositório
inf1715 do Github.
Quando você terminar o trabalho, marque a versão no servidor como a final:
git tag trab2
git push --tags # se você não passar esta flag, a tag não vai pro servidor!
Quaisquer dúvidas na operação com git (ou sobre o trabalho de maneira geral), contacte o professor!
Prazo: 04/04/2014, 23:59. Será considerado como o trabalho entregue
o conteúdo do seu repositório com a tag trab2. Novamente, não deixe para o
último minuto!
Trabalho 3 - Árvore Sintática Abstrata
Você deve construir uma árvore sintática abstrata para programas escritos na linguagem
Mini-0, especificada abaixo.
- A AST deve ser uma estrutura de dados em memória que representa a estrutura
do programa. Ela deve conter informação suficiente para ser capaz de reconstruir
um programa de funcionamento equivalente e descartar informação meramente sintática,
como vimos em aula.
- Os nós da AST devem armazenar informação de número de linha, escolhendo um
número de linha representativo da regra da gramática onde o nó foi gerado
(por exemplo, armazenando o número da primeira linha da função em nós do tipo
"função"
- Você deve documentar os tipos de nó que existem na sua AST, o número de
nós filhos que o tipo pode ter, e quais os tipos dos filhos aceitos. Por exemplo,
"nós do tipo 'while' têm 2 filhos: o primeiro filho do tipo 'expr' representando
a expressão da condição, e o segundo do tipo 'block' representando o bloco de
código interno à construção"
- No caso de execução bem-sucedida, seu programa deve exibir na saida padrão
a AST gerada de forma "legível". Isto é, você deve escrever uma função "pretty-printer" que exiba
a AST, com a estrutura de nós organizada através de indentação. Você não
deve exibir nesta saída códigos numéricos internos do seu programa, mas
sim nomes textuais para os tipos de nó.
- O programa deve receber como primeiro argumento o o nome de arquivo do
programa Mini-0 a ser processado.
- Em caso de erros sintáticos (detectados na fase de lexing ou de parsing),
o programa deve informar na saída de erro (stderr) o erro encontrado
e o código de saída do programa deve ser diferente de zero.
- Em caso de parsing bem-sucedido, o programa deve exibir a AST na
saída padrão (stdout) da forma especificada acima
e o código de saída deve ser zero.
- Você deve incluir casos de teste que exercitem todas as regras da sua
gramática, inclusive erros. Lembre-se de demonstrar regras de precedência.
Assim como nos trabalhos anteriores, o programa deve ser desenvolvido no seu repositório
inf1715 do Github.
Quando você terminar o trabalho, marque a versão no servidor como a final:
git tag trab3
git push --tags # se você não passar esta flag, a tag não vai pro servidor!
Quaisquer dúvidas na operação com git (ou sobre o trabalho de maneira geral), contacte o professor!
Prazo: 16/04/2014, 23:59. Será considerado como o trabalho entregue
o conteúdo do seu repositório com a tag trab3. Novamente, não deixe para o
último minuto!
Trabalho 4 - Tabela de Símbolos e Verificação de Tipos
Você deve realizar a verificação de tipos de programas escritos na linguagem
Mini-0 especificada abaixo, utilizando para isso
uma tabela de símbolos como estrutura de dados interna.
- O programa deve realizar a verificação de tipos e anotar os nós da AST
com informação de tipos derivada dos tipos declarados das variáveis do programa,
verificando que todos os usos estão consistentes segundo as regras de derivação de Mini-0.
- As regras de derivação de Mini-0 serão discutidas em detalhe na aula do dia 24/04.
- No caso de execução bem-sucedida, seu programa deve exibir na saida padrão
a AST gerada de forma "legível", como no Trabalho 3, acrescida da informação de tipagem que você anotou neste trabalho.
- Você deve incluir um Makefile contendo pelo menos duas regras: "clean",
que apaga todos os arquivos gerados (executáveis, objetos .o e arquivos gerados
pelo Flex ou Bison), e uma regra default que causa a compilação do seu programa,
de modo que se qualquer arquivo for editado e o make for chamado novamente, os arquivos necessários serão recompilados, gerando um executável atualizado.
- O programa deve receber como primeiro argumento o o nome de arquivo do
programa Mini-0 a ser processado.
- Em caso de erros sintáticos (detectados na fase de lexing ou de parsing)
ou erros de tipos (detectados na verificação de tipos), o programa deve
informar na saída de erro (stderr) o erro encontrado e o código de
saída do programa deve ser diferente de zero.
- Em caso de parsing bem-sucedido, o programa deve exibir a AST na
saída padrão (stdout) da forma especificada no Trabalho 3, acrescida
da anotação de tipos dos nós, e o código de saída deve ser zero.
- Você deve incluir casos de teste que exercitem todas as regras da sua
gramática, inclusive todos os casos de erro possíveis que seu código gera.
Os testes devem exercitar todas as operações de verificação de tipos que
você implementou, tanto para o caso de sucesso como para o caso de erro.
Assim como nos trabalhos anteriores, o programa deve ser desenvolvido no seu repositório
inf1715 do Github.
Quando você terminar o trabalho, marque a versão no servidor como a final:
git tag trab4
git push --tags # se você não passar esta flag, a tag não vai pro servidor!
Quaisquer dúvidas na operação com git (ou sobre o trabalho de maneira geral), contacte o professor!
Prazo: 30/04/2014, 23:59. Será considerado como o trabalho entregue
o conteúdo do seu repositório com a tag trab4. Novamente, não deixe para o
último minuto!
Trabalho 5 - Código intermediário
Você deve gerar código intermediário a partir de programas escritos na linguagem
Mini-0 especificada abaixo, utilizando para isso
a árvore sintática abstrata anotada que você produziu nos trabalhos anteriores.
- O programa deve produzir o código intermediário fazendo o caminhamento
da AST e produzindo uma estrutura de dados que armazena o código intermediário
simbolicamente (isto é, onde cada instrução do código intermediário é uma
estrutura).
- O programa não deve produzir o dump do texto de saída
à medida que caminha a árvore; ele deve gerar a representação textual do
código intermediário rodando uma função que percorre a estrutura de dados
citada o item anterior ao final da execução.
- O programa deve receber como primeiro argumento o o nome de arquivo do
programa Mini-0 a ser processado.
- No caso de execução bem-sucedida, seu programa deve salvar em disco
o arquivo de saída no formato de código intermediário com o mesmo nome do
arquivo de entrada acrescido da extensão ".ir" (Exemplo: ao rodar
seu compilador com a entrada mini0 meuprograma.m0, se o programa
não contiver erros ele deve gerar um arquivo meuprograma.m0.ir)
- O formato de arquivo do código intermediário deve ser compatível com a
gramática especificada no programa cte disponível
no repositório da disciplina.
- Em caso de erros sintáticos (detectados na fase de lexing ou de parsing)
ou erros de tipos (detectados na verificação de tipos), o programa deve
informar na saída de erro (stderr) o erro encontrado e o código de
saída do programa deve ser diferente de zero. Este trabalho não irá gerar
novos erros (a não ser talvez falha ao abrir o arquivo de saída para escrita).
- Você deve incluir um Makefile contendo pelo menos duas regras: "clean",
que apaga todos os arquivos gerados (executáveis, objetos .o e arquivos
gerados pelo Flex ou Bison), e uma regra default que causa a compilação do seu
programa, de modo que se qualquer arquivo for editado e o make
for chamado novamente, os arquivos necessários serão recompilados, gerando um
executável atualizado.
- Você deve incluir casos de teste que exercitem todos os tipos de nós
da AST que seu programa é capaz de gerar. Recomendo verificar a cobertura
dos seus testes com gcov ou luacov.
Assim como nos trabalhos anteriores, o programa deve ser desenvolvido no seu repositório
inf1715 do Github.
Quando você terminar o trabalho, marque a versão no servidor como a final:
git tag trab5
git push --tags # se você não passar esta flag, a tag não vai pro servidor!
Quaisquer dúvidas na operação com git (ou sobre o trabalho de maneira geral), contacte o professor!
Prazo: 27/05/2014, 02:00. Será considerado como o trabalho entregue
o conteúdo do seu repositório com a tag trab5. Novamente, não deixe para o
último minuto!
Trabalho 6 - Geração de Assembly Nativo
Neste trabalho você vai fazer o backend do compilador, que receberá como entrada
código no formato de representação intermediária (conforme gerado pelo
Trabalho 5) e produzirá como saída código assembly x86.
Esta saída poderá então ser montada e linkada gerando um binário executável.
- O programa deve produzir o código Assembly x86 (32 bits) com sintaxe AT&T, compatível
com o GNU Assembler (mesmo formato usado na disciplina de Software Básico).
- Como ponto de partida, se você implementar em C, deve utilizar como base
o parser de código de
três endereços que eu publiquei no repositório da disciplina. Este programa lê o
código na representação intermediária e gera uma estrutura de dados correspondente.
Durante a leitura, eu já fiz a coleta de variáveis locais e as indexei numericamente
para comparação fácil (ver a documentação do código fonte em ir.h e ir.c).
- O seu trabalho deve estender esse programa, executando os seguintes passos:
- Repartir as instruções de cada função em blocos básicos (Algoritmo 8.5, Seção 8.4.1 - "Basic Blocks", de Aho et. al (2006), o livro do Dragão)
- Para cada bloco básico:
- Fazer o levantamento de variáveis "vivas" e "próximo uso" (Algoritmo 8.7, Seção 8.4.2 - "Next-Use Information")
- Fazer a seleção de operações, usando uma função de seleção de registradores (Seção 8.6.2 - "The Code-Generation Algorithm")
- Implementar a função de seleção de registradores (Seção 8.6.3 - "Design of the function getReg")
- Gerar o arquivo de saída, com o código gerado para cada função. (A geração do arquivo de saída pode ser concomitante com o processamento dos blocos básicos.)
- Você deve incluir casos de teste que cubram todo o código que você escreveu.
Recomendo verificar a cobertura dos seus testes com gcov ou luacov.
Critérios que serão empregados para avaliação:
- Repartição em blocos básicos - 1 ponto
- Levantamento de variáveis vivas ("próximo-uso") - 2 pontos
- Endereços de parâmetros e locais na pilha - 1 ponto
- Alocação de registradores - 2 pontos
- Geração de código referente às instruções - 3 pontos
- Tratamento de globais e strings nas instruções de mov - 1 ponto
- Análise e geração de instruções de spill - 1 ponto
(sim, o total dá 11)
Uma implementação do trabalho em Haskell
está disponível como "pseudocódigo de referência".
Assim como nos trabalhos anteriores, o programa deve ser desenvolvido no seu repositório
inf1715 do Github.
Quando você terminar o trabalho, marque a versão no servidor como a final:
git tag trab6
git push --tags # se você não passar esta flag, a tag não vai pro servidor!
Quaisquer dúvidas na operação com git (ou sobre o trabalho de maneira geral), contacte o professor!
Prazo: 20/06/2014, 23:59. Será considerado como o trabalho entregue
o conteúdo do seu repositório com a tag trab6. Novamente, não deixe para o
último minuto!
Linguagem Mini-0
A linguagem Mini-0 é uma linguagem bastante pequena, mas com um conjunto razoável de recursos
para manipulação básica de inteiros, caracteres, booleanos e vetores. Ela oferece estruturas
de controle if/else/end e while/loop; variáveis; funções com parâmetros e retorno de valores.
Além disso, nossa implementação gerará código objeto compatível com C, o que permitirá que
programas Mini-0 possam chamar funções das bibliotecas padrão de C.
Léxico
- Comentários são como em C99: delimitados por /* e */, sem aninhamento, ou // até o fim da linha.
- Strings são escritos entre aspas duplas "..." e podem conter os escapes \\, \n, \t e \".
- Numerais são apenas inteiros, e podem ser escritos em decimal ou em hexadecimal. Números hexadecimais começam com 0x e aceitam tanto dígitos em maiúsculas ("A" a "F") como em minúsculas ("a" a "f").
- Quebras de linha devem ser reconhecidas pois são relevantes na gramática (indicadas como NL na sintaxe abaixo; NL pode representar uma ou mais quebras de linha, com qualquer quantidade de espaços em branco)
- Palavras reservadas: if else end while loop fun return new string int char bool true false and or not
- Identificadores: uma letra ou underscore seguido de zero ou mais letras, números ou underscores
- Operadores e pontuação: ( ) , : > < >= <= = <> [ ] + - * /
Sintaxe
Descrição da sintaxe em formato EBNF, sem especificar precedência de operadores. Terminais estão listados em MAIÚSCULAS (quando
representam tipos de tokens) ou como 'literais' quando representam a si mesmos. Em notação EBNF, [ x ]
significa que x é opcional; { x } significa que x pode aparecer zero ou mais vezes.
programa → { NL } decl { decl }
decl → funcao | global
nl → NL { NL }
global → declvar nl
funcao → 'fun' ID '(' params ')' [ ':' tipo ] nl
bloco
'end' nl
bloco → { declvar nl }
{ comando nl }
params → /*vazio*/ | parametro { ',' parametro }
parametro → ID ':' tipo
tipo → tipobase | '[' ']' tipo
tipobase → 'int' | 'bool' | 'char' | 'string'
declvar → ID ':' tipo
comando → cmdif | cmdwhile | cmdatrib | cmdreturn | chamada
cmdif → 'if' exp nl
bloco
{ 'else' 'if' exp nl
bloco
}
[ 'else' nl
bloco
]
'end'
cmdwhile → 'while' exp nl
bloco
'loop'
cmdatrib → var '=' exp
chamada → ID '(' listaexp ')'
listaexp → /*vazio*/ | exp { ',' exp }
cmdreturn → 'return' exp | 'return'
var → ID | var '[' exp ']'
exp → LITNUMERAL
| LITSTRING
| TRUE
| FALSE
| var
| 'new' '[' exp ']' tipo
| '(' exp ')'
| chamada
| exp '+' exp
| exp '-' exp
| exp '*' exp
| exp '/' exp
| exp '>' exp
| exp '<' exp
| exp '>=' exp
| exp '<=' exp
| exp '=' exp
| exp '<>' exp
| exp 'and' exp
| exp 'or' exp
| 'not' exp
| '-' exp
Observações
- O tipo string é sinônimo de []char
- A execução de um programa consiste na execução de sua função main
- As expressões em if e while devem ter tipo bool
- A expressão new [exp]tipo cria um novo array com um número exp de
elementos do dado tipo. A expressão deve ter tipo inteiro.
- Operadores relacionais têm resultado do tipo bool
- and e or devem usar "curto-circuito".
- A prioridade dos operadores é igual à de C.
- Em qualquer expressão, uma variável char tem seu valor
automaticamente promovido para int
- Para mantermos a compatibilidade entre as implementações, em caso de
qualquer dúvida sobre a linguagem, consulte o professor para explicitarmos
melhor a especificação para todos!
Bibliografia
Estou usando as edições mais novas dos livros na organização das aulas, mas as edições antigas
talvez sejam mais facilmente encontradas nas bibliotecas da PUC e se mantêm geralmente relevantes.
- Crafting a Compiler;
Charles Fischer, Ron K. Cytron, Richard LeBlanc.
Benjamin/Cummings, 2009.
- edição antiga: Crafting a Compiler with C;
Charles Fischer, Richard LeBlanc.
Benjamin/Cummings, 1991.
- Compilers: Principles, techniques, and Tools, 2nd edition (o famoso "Livro do Dragão");
Alfred V. Aho, Monica S. Lam, Ravi Sethi, Jeffrey D. Ullman.
Addison Wesley, 2006.
- edição antiga: Compilers: Principles, techniques, and Tools;
Alfred V. Aho, Ravi Sethi, Jeffrey D. Ullman.
Addison Wesley, 1986.
- Lex - A Lexical Analyzer Generator;
M. E. Lesk, E. Schmidt.
- Yacc: Yet Another Compiler-Compiler;
Stephen C. Johnson.