Compiladores - Da Teoria à Prática

Page 1

9cm x 24cm

16,7cm x 24cm

C

Implementação de tipos de dados abstratos em C; algoritmos e estruturas de dados essenciais para escrever programas de média e elevada complexidade; novas secções sobre árvores, caminhos e circuitos.

M

Y

CM

MY

CY

CMY

K

Aprenda as regras e boas práticas na análise, conceção e desenvolvimento de aplicações orientadas pelos objetos, através de vários projetos de software e exercícios analisados e implementados em Java.

A geração de código-máquina é precedida de uma fase de análise do programa para garantir a correção do mesmo e construir uma estrutura que o represente. Num compilador, o processo de análise permite compreender muitas das limitações das linguagens de programação. Por outro lado, a geração de código permite compreender como os compiladores utilizam os processadores e a forma como a evolução dos processadores tem feito evoluir os compiladores. O livro aborda os diversos passos do desenvolvimento de um compilador, incluindo: A análise determinista linear com autómatos finitos para linguagens regulares e autómatos de pilha para uma análise ascendente e descendente; A realização de verificações semânticas e a construção da árvore sintática do programa analisado; A linearização das instruções para a geração de código direto para máquinas de pilha; A seleção e o escalonamento das instruções, bem como a reserva de registos, para máquinas de registos uniformes;

Com a leitura deste livro : Conheça o funcionamento interno de um compilador, quer ao nível da análise das linguagens a compilar, quer no que respeita à geração de código; Compreenda quais as razões de algumas limitações das linguagens compiladas; Acompanhe o desenvolvimento de um compilador simples, em C e em Java, com geração de código para bytecodes Java, MSIL .net, Pentium e arm; Domine os compiladores de compiladores lex, yacc, antlr e burg.

A otimização do código resultante, com base na análise do fluxo de controlo e de dados. Este livro reúne os aspetos mais importantes a ter em conta: como conceber uma boa experiência de jogo, o que caracteriza um jogo, a teoria de jogos, a indústria, como ser empreendedor, etc.

Uma obra que ajuda estudantes e profissionais a compreenderem os sistemas de gestão de bases de dados relacionais. Com apresentação dos conceitos fundamentais, inclui variados exemplos e exercícios.

Todo o processo de desenvolvimento é exemplificado, em C e Java, para uma linguagem de exemplo simples, com recurso às ferramentas lex, yacc, antlr e burg. Este livro é dirigido aos estudantes de nível universitário e profissional, produtores de software, programadores e utilizadores em geral que pretendam compreender de que forma o compilador converte programas descritos por linguagens de alto nível em código executável. Este livro disponibiliza ainda a correspondência dos principais termos técnicos para o Português do Brasil.

ISBN 978‐972‐722‐768‐6

9 789727 227686

Programas apresentados no livro disponíveis em www.fca.pt até este se esgotar ou ser publicada nova edição atualizada ou com alterações.

Compiladores

O compilador é uma ferramenta que converte, de uma forma eficiente, programas descritos por linguagens de alto nível em linguagem-máquina. O compilador é determinante no desempenho das aplicações, já que quase todo o código executado é compilado.

27mm

16,7cm x 24cm

9cm x 24cm

Pedro Reis Santos Professor Auxiliar do Departamento de Engenharia Informática do Instituto Superior Técnico (IST), onde é docente desde 1990. Lecionou as disciplinas de Complementos de Compiladores, Algoritmos e Estruturas de Dados, Programação por Objetos e Ambientes de Desenvolvimento. É regente da disciplina de Compiladores (IST/Taguspark).

Thibault Langlois Professor Auxiliar do Departamento de Informática da Faculdade de Ciências da Universidade de Lisboa (FCUL), onde é docente desde 2001. Lecionou e foi regente das disciplinas de Compiladores, Introdução à Programação (Java), Laboratórios de Programação (Java) e Programação I e II (em C). É regente das disciplinas de Linguagens Formais e Autómatos, Princípios de Programação (programação funcional, Haskell) e Desenvolvimento Centrado em Objetos (Java). Entre 1994 e 2001 foi docente do Departamento de Engenharia Eletrotécnica e Computadores do Instituto Superior Técnico, onde lecionou as disciplinas de Introdução à Programação, Algoritmos e Estruturas de Dados, Compiladores e Projeto de Compiladores.


Distribuição

Lidel – edições técnicas, lda.

Sede R. D. Estefânia, 183, R/C Dto. – 1049-057 LISBOA Tel: +351 213 511 448 * Fax: +351 213 522 684 Revenda: revenda@lidel.pt Exportação: depinternacional@lidel.pt Venda online: livraria@lidel.pt Marketing: marketing@lidel.pt Livraria Av. Praia da Vitória, 14 – 1000-247 LISBOA Tel: +351 213 511 448 * Fax: +351 213 173 259 livraria@lidel.pt Edição

FCA – Editora de Informática

Av. Praia da Vitória, 14 – 1000-247 LISBOA Tel: +351 213 511 448 Email: fca@fca.pt Copyright © outubro 2014 FCA – Editora de Informática, Lda. ISBN: 978-972-722-768-6 Capa: José Manuel Reis Ilustração da capa: Miguel Montenegro Pré-impressão: Alice Simões Impressão e acabamento: Tipografia Lousanense, Lda. – Lousã Depósito Legal N.º 381845/14 Livro segundo o Novo Acordo Ortográfico Todos os nossos livros passam por um rigoroso controlo de qualidade, no entanto, aconselhamos a consulta periódica do nosso site (www.fca.pt) para fazer o download de eventuais correções. Os nomes comerciais referenciados neste livro têm patente registada. Marcas Registadas de FCA – Editora de Informática, Lda. –

®

®

®

Reservados todos os direitos. Esta publicação não pode ser reproduzida, nem transmitida, no todo ou em parte, por qualquer processo eletrónico, mecânico, fotocópia, digitalização, gravação, sistema de armazenamento e disponibilização de informação, sítio Web, blogue ou outros, sem prévia autorização escrita da Editora, exceto o permitido pelo CDADC, em termos de cópia privada pela AGECOP – Associação para a Gestão da Cópia Privada, através do pagamento das respetivas taxas.


ÍNDICE GERAL SOBRE O LIVRO ................................................................................................................................................................................... XV 1

INTRODUÇÃO

1

1.1

PROGRAMA EXECUTÁVEL .............................................................................................................................................. 1

1.2

PROCESSO DE COMPILAÇÃO........................................................................................................................................ 2

1.3

ESTRUTURA DO COMPILADOR.....................................................................................................................................3

1.4

1.3.1

ANÁLISE DETERMINISTA DE LINGUAGENS ............................................................................................. 4

1.3.2

SINTESE DO CÓDIGO .......................................................................................................................................... 5

DESENVOLVIMENTO DE UM COMPILADOR ........................................................................................................... 7 1.4.1

FERRAMENTAS DE DESENVOLVIMENTO ................................................................................................. 7

1.4.2

DESENVOLVIMENTO DE PROGRAMAS..................................................................................................... 9

1.4.3

AMBIENTE DE APOIO À EXECUÇÃO........................................................................................................... 10

1.4.4

UM NOVO COMPILADOR ................................................................................................................................. 10 1.4.4.1

UMA NOVA VERSÃO DO COMPILADOR................................................................................. 10

1.4.4.2 UMA NOVA LINGUAGEM ................................................................................................................. 11 1.4.4.3 UMA NOVA ARQUITETURA ...........................................................................................................12 1.5

DEFINIÇÃO DE UMA LINGUAGEM ...............................................................................................................................12

1.6

LINGUAGENS E GRAMÁTICAS .................................................................................................................................... 14 1.6.1

1.6.2

TERMINOLOGIA ................................................................................................................................................... 15 1.6.1.1

ALFABETO ............................................................................................................................................ 15

1.6.1.2

CADEIA ................................................................................................................................................... 15

1.6.1.3

CONJUNTO DE CADEIAS ................................................................................................................ 15

1.6.1.4

LINGUAGEM.......................................................................................................................................... 15

1.6.1.5

CONCATENAÇÃO ............................................................................................................................... 16

1.6.1.6

FECHO DE KLEENE ............................................................................................................................ 16

REPRESENTAÇÃO DE LINGUAGENS .......................................................................................................... 16 1.6.2.1

HIERARQUIA DE GRAMÁTICAS ................................................................................................... 17

1.6.2.2

PROPRIEDADES DAS GRAMÁTICAS: FECHO........................................................................ 18

EXERCÍCIOS ..................................................................................................................................................................................... 18

PARTE I – ANÁLISE DETERMINISTA DE LINGUAGENS ........................................................................ 19 2

ANÁLISE LEXICAL 2.1

21

EXPRESSÕES REGULARES.......................................................................................................................................... 22 2.1.1

OPERADORES DAS EXPRESSÕES REGULARES ................................................................................. 23

2.1.2

GRAMÁTICAS REGULARES ........................................................................................................................... 24

2.1.3

PROPRIEDADES DAS EXPRESSÕES REGULARES .............................................................................. 24

2.2 AUTÓMATOS FINITOS ................................................................................................................................................... 24 2.2.1

DIAGRAMA DE TRANSIÇÃO ......................................................................................................................... 25

2.2.2 TABELA DE TRANSIÇÃO ................................................................................................................................ 26 2.2.3 AUTÓMATO FINITO NÃO DETERMINISTA .............................................................................................. 26 2.2.3.1

ALGORITMO DE THOMPSON ....................................................................................................... 27

© FCA – EDITORA DE INFORMÁTICA

III


COMPILADORES: DA TEORIA À PRÁTICA

2.2.4 AUTÓMATO FINITO DETERMINISTA ......................................................................................................... 28 2.2.5 CONVERSÃO POR CONSTRUÇÃO DE SUBCONJUNTOS................................................................... 29 2.2.6 MINIMIZAÇÃO DA TABELA ............................................................................................................................. 31 2.2.7 CONSTRUÇÃO DE KLEENE ............................................................................................................................ 33 2.3 ANALISADOR LEXICAL .................................................................................................................................................. 35 2.3.1

COMPACTAÇÃO DA TABELA....................................................................................................................... 37

2.3.2 ANALISADORES EXPLICITAMENTE CODIFICADOS ............................................................................ 38 2.3.3 FERRAMENTAS .................................................................................................................................................. 39 EXERCÍCIOS ................................................................................................................................................................................... 39 3

GRAMÁTICAS LIVRES DE CONTEXTO 3.1

43

ESPECIFICAÇÃO DA GRAMÁTICA ............................................................................................................................ 43 3.1.1

REPRESENTAÇÃO GRÁFICA......................................................................................................................... 45

3.1.2

BACKUS-NAUR FORM ..................................................................................................................................... 45

3.1.3

PADRÕES COMUNS .......................................................................................................................................... 46

3.1.4

MANIPULAÇÃO DE GRAMÁTICAS ............................................................................................................. 47 3.1.4.1

FATORIZAÇÃO .................................................................................................................................. 48

3.1.4.2 SUBSTITUIÇÃO DOS CANTOS À ESQUERDA....................................................................... 48 3.1.4.3 ELIMINAÇÃO DAS PRODUÇÕES INATINGÍVEIS .................................................................. 49 3.1.4.4 FORMA NORMAL DE CHOMSKY ............................................................................................... 49 3.2 DERIVAÇÃO ...........................................................................................................................................................................51 3.3 ÁRVORE SINTÁTICA ........................................................................................................................................................ 52 3.4 PROPRIEDADES ................................................................................................................................................................. 53 3.4.1

AMBIGUIDADE..................................................................................................................................................... 53

3.4.2 ASSOCIATIVIDADE ........................................................................................................................................... 54 3.4.3 PRECEDÊNCIAS ENTRE OPERADORES.................................................................................................... 54 3.5 CONJUNTOS DE ANÁLISE.............................................................................................................................................. 55 3.5.1

CONJUNTO FIRST.............................................................................................................................................. 55

3.5.2 CONJUNTO FOLLOW ....................................................................................................................................... 57 3.5.3 CONJUNTO LOOKAHEAD.............................................................................................................................. 58 3.6 CATEGORIAS ....................................................................................................................................................................... 58 EXERCÍCIOS ................................................................................................................................................................................... 59 4

ANÁLISE SINTÁTICA DESCENDENTE 4.1

61

ANALISADORES DESCENDENTES RECURSIVOS ................................................................................................ 61

4.2 GRAMÁTICAS LL(1) .......................................................................................................................................................... 62 4.2.1

RECURSIVIDADES À ESQUERDA ............................................................................................................... 63 4.2.1.1

RECURSIVIDADE DIRETA .............................................................................................................. 63

4.2.1.2

RECURSIVIDADE INDIRETA .......................................................................................................... 63

4.2.2 FATORIZAÇÃO À ESQUERDA ..................................................................................................................... 65 4.3 ANALISADOR PREDITIVO NÃO RECURSIVO ....................................................................................................... 66 4.4 CONSTRUÇÃO DA TABELA DE ANÁLISE .............................................................................................................. 68 4.4.1

COMPRESSÃO DA TABELA .......................................................................................................................... 69

4.5 ANALISADORES DESCENDENTES HARDCODED ............................................................................................... 69 IV

© FCA – EDITORA DE INFORMÁTICA


ÍNDICE GERAL 4.6 GRAMÁTICAS AMBÍGUAS ........................................................................................................................................... 69 4.7 RECUPERAÇÃO DE ERROS SINTÁTICOS ............................................................................................................... 70 4.8 GRAMÁTICAS LL(K) ......................................................................................................................................................... 73 4.8.1

CONJUNTOS FIRSTK, FOLLOWK E LOOKAHEADK ............................................................................ 73

4.8.2 GRAMÁTICAS STRONG LL(K)........................................................................................................................ 75 4.8.3 ABALIZADORES STRONG LL(K): OTIMIZAÇÕES .................................................................................. 78 EXERCÍCIOS .................................................................................................................................................................................... 80 5

ANÁLISE SINTÁTICA ASCENDENTE POR TABELA 5.1

87

CONSTRUÇÃO DO ANALISADOR .............................................................................................................................. 88 5.1.1

GRAMÁTICA AUMENTADA ........................................................................................................................... 88

5.1.2

ESTADOS DO AUTÓMATO ............................................................................................................................ 88

5.1.3

TRANSIÇÕES VAZIAS...................................................................................................................................... 89

5.1.4

AUTÓMATO DETERMINISTA......................................................................................................................... 90

5.1.5

CONSTRUÇÃO DA TABELA DE ANÁLISE ................................................................................................. 91

5.1.6

GRAMÁTICAS LR(0) .......................................................................................................................................... 92

5.1.7

ANÁLISE ASCENDENTE POR TABELA ..................................................................................................... 93

5.1.8

GRAMÁTICAS SLR(1) ......................................................................................................................................... 94

5.2 GRAMÁTICAS LALR(1) ....................................................................................................................................................95 5.3 ELIMINAÇÃO DE CONFLITOS ..................................................................................................................................... 102 5.3.1

CONFLITOS DESLOCAMENTO-REDUÇÃO ............................................................................................. 103

5.3.2 CONFLITOS REDUÇÃO-REDUÇÃO ............................................................................................................ 105 5.4 UTILIZAÇÃO DETERMINISTA DE GRAMÁTICAS AMBÍGUAS ...................................................................... 106 5.4.1

ATRIBUIÇÃO DE ASSOCIATIVIDADES E PRIORIDADES ................................................................. 106

5.4.2 CONFLITOS REDUÇÃO-REDUÇÃO ............................................................................................................ 109 5.5 COMPACTAÇÃO DE TABELAS LR .............................................................................................................................. 111 5.5.1

TABELAS ESPARSAS ........................................................................................................................................ 111

5.5.2 PROPAGAÇÃO DE REDUÇÕES....................................................................................................................... 111 5.5.2.1 PROPAGAÇÃO DE REDUÇÕES UNITÁRIAS............................................................................. 111 5.5.2.2 PROPAGAÇÃO DE REDUÇÕES QUASE UNITÁRIAS .......................................................... 113 5.5.3 REDUÇÕES UNITÁRIAS ................................................................................................................................... 114 5.6 RECUPERAÇÃO DE ERROS .......................................................................................................................................... 114 5.6.1

RECUPERAÇÃO POR SÍMBOLO DE ERRO............................................................................................... 114

5.6.2 RECUPERAÇÃO AUTOMÁTICA..................................................................................................................... 118 5.7 OUTRAS GRAMÁTICAS LR .......................................................................................................................................... 118 5.7.1

GRAMÁTICAS LR(1) ............................................................................................................................................ 118

5.7.2 LALR(1) POR AGRUPAMENTO DE ESTADOS LR(1) ............................................................................... 121 5.7.3 GRAMÁTICAS QUASE LALR(1) .................................................................................................................... 122 5.7.4 GRAMÁTICAS LR(k ), k > 1 ............................................................................................................................. 123 5.8 PROPRIEDADES DA ANÁLISE ASCENDENTE ..................................................................................................... 124 5.8.1

RECURSIVIDADE EM ANALISADORES LR ............................................................................................. 124 5.8.1.1

RECURSIVIDADE À ESQUERDA ................................................................................................ 125

5.8.1.2 RECURSIVIDADE À DIREITA ....................................................................................................... 125 5.8.1.3 ASSOCIATIVIDADE ......................................................................................................................... 125

© FCA – EDITORA DE INFORMÁTICA

V


COMPILADORES: DA TEORIA À PRÁTICA

5.8.2 ANÁLISE ASCENDENTE VERSUS DESCENDENTE ............................................................................. 126 EXERCÍCIOS .................................................................................................................................................................................. 127 6

GRAMÁTICAS ATRIBUTIVAS 6.1

131

AÇÕES SEMÂNTICAS..................................................................................................................................................... 131 6.1.1

AVALIAÇÃO DIRIGIDA PELA SINTAXE ................................................................................................... 133

6.1.2

DEFINIÇÃO DE S-ATRIBUTOS ..................................................................................................................... 137

6.1.3

DEFINIÇÃO DE L-ATRIBUTOS ...................................................................................................................... 137

6.1.4

GRAFO DE DEPENDÊNCIAS ......................................................................................................................... 138

6.2 ESQUEMAS DE AVALIAÇÃO DE ATRIBUTOS ................................................................................................... 140 6.2.1

AÇÕES EM ANALISADORES PREDITIVOS ............................................................................................. 141

6.2.2 AÇÕES EM ANALISADORES ASCENDENTES ...................................................................................... 143 6.2.2.1

AVALIAÇÃO DOS ATRIBUTOS SINTETIZADOS ................................................................. 143

6.2.2.2 AVALIAÇÃO DOS ATRIBUTOS HERDADOS ........................................................................ 144 6.3 MANIPULAÇÃO DE ATRIBUTOS ............................................................................................................................... 147 6.3.1

ELIMINAÇÃO DA RECURSIVIDADE À ESQUERDA ............................................................................. 147

6.3.2 ELIMINAÇÃO DE ATRIBUTOS HERDADOS ............................................................................................ 149 6.3.2.1

ESCOLHA DE NOVOS ATRIBUTOS .......................................................................................... 149

6.3.2.2 ALTERAÇÃO DA GRAMÁTICA................................................................................................... 150 6.3.2.3 ESTRUTURA DE DADOS AUXILIAR .......................................................................................... 151 6.4 CONSTRUÇÃO DA ÁRVORE SINTÁTICA ................................................................................................................ 151 6.4.1

ÁRVORES DE INSTRUÇÕES .......................................................................................................................... 151

6.4.2 FOLHAS DA ÁRVORE ..................................................................................................................................... 152 6.4.3 NÓS DA ÁRVORE ............................................................................................................................................. 152 6.4.4 CONSTRUÇÃO DE ÁRVORES SINTÁTICAS PARA EXPRESSÕES ................................................ 153 EXERCÍCIOS .................................................................................................................................................................................. 155 7

ANÁLISE SEMÂNTICA 7.1

VERIFICAÇÕES ESTÁTICAS ....................................................................................................................................... 159 7.1.1

VERIFICAÇÕES DE TIPOS.............................................................................................................................. 160

7.1.2

VERIFICAÇÕES DE FLUXO DE CONTROLO ............................................................................................ 160

7.1.3

VERIFICAÇÕES DOS TAGS ........................................................................................................................... 160

7.1.4

7.1.5 7.2

159

DECLARAÇÕES ................................................................................................................................................... 161 7.1.4.1

REGISTO DO IDENTIFICADOR E DO SEU TIPO.................................................................... 162

7.1.4.2

UNICIDADE DA DECLARAÇÃO ................................................................................................... 162

7.1.4.3

VERIFICAR O REGISTO ................................................................................................................. 162

DETERMINAÇÃO DO CONTEXTO ............................................................................................................... 163

ANÁLISE DIRIGIDA PELA SINTAXE ......................................................................................................................... 163

7.3 MANIPULAÇÃO DE NOMES ........................................................................................................................................ 164 7.3.1

VARIÁVEIS VISÍVEIS E ACESSÍVEIS ....................................................................................................... 164

7.3.2 VARIÁVEIS NÃO VISÍVEIS E ACESSÍVEIS ............................................................................................ 164 7.3.3 TEMPO DE VIDA................................................................................................................................................ 165 7.3.4 TABELAS DE SÍMBOLOS............................................................................................................................... 166 7.4 SISTEMA DE TIPOS DE DADOS ................................................................................................................................ 168 VI

© FCA – EDITORA DE INFORMÁTICA


ÍNDICE GERAL 7.4.1

CONSISTÊNCIA DE TIPOS ............................................................................................................................. 168

7.4.2 POLIMORFISMO ................................................................................................................................................. 170 7.4.3 INFERÊNCIA DE TIPOS .................................................................................................................................... 174 EXERCÍCIOS ................................................................................................................................................................................... 176 8

PROJETO DE ANÁLISE 8.1

179

UM ANALISADOR PARA UMA LINGUAGEM REGULAR ................................................................................. 179 8.1.1

8.1.2

ANALISADOR LEXICAL LEX.......................................................................................................................... 179 8.1.1.1

AGRUPAMENTOS DO ANALISADOR ..................................................................................... 180

8.1.1.2

EXPRESSÕES REGULARES ........................................................................................................ 180

8.1.1.3

AÇÕES DAS REGRAS .................................................................................................................... 182

INTERLIGAÇÃO DO ANALISADOR LEXICAL .......................................................................................... 182

8.1.3

UMA LINGUAGEM REGULAR ...................................................................................................................... 184

8.1.4

GERAÇÃO DO ANALISADOR LEXICAL ................................................................................................... 186

8.1.5

GERADOR FLEX ................................................................................................................................................ 186

8.1.6

GERADOR JFLEX ............................................................................................................................................... 187

8.1.7

GERADOR JLEX................................................................................................................................................. 189

8.2 UMA LINGUAGEM DE PROGRAMAÇÃO SIMPLES ........................................................................................... 190 8.2.1

PROCESSO DE ANÁLISE................................................................................................................................. 191 8.2.1.1

SÍMBOLOS TERMINAIS .................................................................................................................. 191

8.2.1.2

SÍMBOLOS NÃO TERMINAIS ...................................................................................................... 192

8.2.1.3 COMPLETAR A GRAMÁTICA ...................................................................................................... 192 8.2.2 RESOLUÇÃO DE AMBIGUIDADES ............................................................................................................. 193 8.2.2.1

PRECEDÊNCIA E ASSOCIATIVIDADE ..................................................................................... 193

8.2.2.2 AÇÕES DAS REGRAS ................................................................................................................... 194 8.2.3 ANÁLISE DIRIGIDA PELA SINTAXE .......................................................................................................... 194 8.2.4 GERAÇÃO DE ÁRVORES SINTÁTICAS ................................................................................................... 195 8.2.4.1 ÁRVORES HOMOGÉNEAS .......................................................................................................... 195 8.2.4.2 ÁRVORES HETEROGÉNEAS ...................................................................................................... 198 8.2.5 TABELA DE SÍMBOLOS ................................................................................................................................ 200 8.2.6 ESTRUTURA DO COMPILADOR .................................................................................................................. 201 8.2.6.1 ABORDAGEM ESTRUTURADA................................................................................................. 202 8.2.6.2 PERSPETIVA ORIENTADA POR OBJETOS ......................................................................... 203 8.3 ANÁLISE ASCENDENTE.............................................................................................................................................. 204 8.3.1

ESPECIFICAÇÃO DA GRAMÁTICA ........................................................................................................... 204 8.3.1.1

RECURSIVIDADE ............................................................................................................................ 205

8.3.1.2 AMBIGUIDADE................................................................................................................................. 206 8.3.1.3 ASSOCIATIVIDADE E PRIORIDADE ....................................................................................... 207 8.3.2 INTERLIGAÇÃO COM O LEX........................................................................................................................ 208 8.3.2.1 PASSAGEM DE VALORES ........................................................................................................... 210 8.3.3 IDENTIFICAÇÃO E RESOLUÇÃO DE CONFLITOS .................................................................................. 211 8.3.3.1 CONFLITOS DESLOCAMENTO-REDUÇÃO ............................................................................. 212 8.3.3.2 CONFLITOS REDUÇÃO-REDUÇÃO ............................................................................................ 213 8.3.3.3 GRAMÁTICAS NÃO LALR(1) ......................................................................................................... 214

© FCA – EDITORA DE INFORMÁTICA

VII


COMPILADORES: DA TEORIA À PRÁTICA

8.3.3.4 DEPURAÇÃO DA GRAMÁTICA .................................................................................................. 215 8.3.4 TRATAMENTO DE ERROS ............................................................................................................................ 215 8.3.4.1 RECUPERAÇÃO DE ERROS ......................................................................................................... 216 8.3.5 AÇÕES SEMÂNTICAS ..................................................................................................................................... 217 8.3.5.1 AVALIAÇÃO DE ATRIBUTOS ..................................................................................................... 217 8.3.5.2 CONVERSÃO DE ATRIBUTOS HERDADOS À ESQUERDA .......................................... 220 8.3.5.3 AÇÕES INTERNAS......................................................................................................................... 220 8.3.6 BYacc/J: ANÁLISE SINTÁTICA EM JAVA ............................................................................................. 221 8.3.7 CUP: ANÁLISE SINTÁTICA EM JAVA ..................................................................................................... 224 8.4 ANÁLISE DESCENDENTE ........................................................................................................................................... 226 8.4.1

ANTLR ............................................................................................................................................................... 226

8.4.2 GERAÇÃO DE ÁRVORES ............................................................................................................................. 229 8.4.3 INTERAÇÃO COM JAVA ................................................................................................................................ 231 8.4.4 INTERAÇÃO COM C ........................................................................................................................................ 233 EXERCÍCIOS ................................................................................................................................................................................ 236

PARTE II – SÍNTESE DE CÓDIGO ................................................................................................................ 239 9

AMBIENTE DE EXECUÇÃO DE PROGRAMAS 9.1

241

REPRESENTAÇÃO DE VALORES............................................................................................................................. 241 9.1.1

NÚMEROS INTEIROS POSITIVOS .............................................................................................................. 241

9.1.2

NÚMEROS INTEIROS NEGATIVOS .......................................................................................................... 242

9.1.3

9.1.4

NÚMEROS REAIS ............................................................................................................................................ 243 9.1.3.1

NÚMEROS EM VÍRGULA FLUTUANTE .................................................................................. 243

9.1.3.2

NORMA IEEE-754.......................................................................................................................... 244

SEQUÊNCIA DE ARMAZENAMENTO ..................................................................................................... 246

9.1.5

PONTEIROS ....................................................................................................................................................... 246

9.1.6

CADEIA DE CARACTERES............................................................................................................................247

9.1.7

VETORES E ESTRUTURAS ......................................................................................................................... 248

9.2 REGISTOS DE ATIVAÇÃO ......................................................................................................................................... 249 9.2.1

PASSAGEM DE VALORES .......................................................................................................................... 249

9.2.2 CONVENÇÕES DE CHAMADA ................................................................................................................... 250 9.2.3 COLOCAÇÃO DE PARÂMETROS .............................................................................................................. 252 9.2.4 COLOCAÇÃO DO RETORNO ....................................................................................................................... 253 9.2.5 FUNÇÕES TERMINAIS ................................................................................................................................... 254 9.2.6 ORGANIZAÇÃO INTERNA ........................................................................................................................... 255 9.2.7 SALVAGUARDA DE REGISTOS ................................................................................................................ 257 9.2.8 FUNÇÕES DENTRO DE FUNÇÕES ............................................................................................................ 258 9.2.9 CORROTINAS .................................................................................................................................................... 259 9.2.10 EXCEÇÕES.......................................................................................................................................................... 260 9.3 GESTÃO DE MEMÓRIA ............................................................................................................................................... 262 9.3.1

SEGMENTOS DE MEMÓRIA ....................................................................................................................... 263

9.3.2 ALINHAMENTO DE MEMÓRIA .................................................................................................................. 266 9.3.3 COMPILAÇÃO SEPARADA .......................................................................................................................... 267 9.3.4 LIGAÇÃO COM O DESTINO ......................................................................................................................... 268 VIII

© FCA – EDITORA DE INFORMÁTICA


ÍNDICE GERAL 9.3.5 REPRESENTAÇÃO DE OBJETOS .............................................................................................................. 269 9.3.5.1 HERANÇA .......................................................................................................................................... 270 9.3.5.2 HERANÇA MÚLTIPLA ..................................................................................................................... 271 9.3.5.3 DELEGAÇÃO .................................................................................................................................... 273 EXERCÍCIOS ................................................................................................................................................................................. 274 10

REPRESENTAÇÃO DE CÓDIGO INTERMÉDIO 10.1

279

ARQUITETURAS DE PROCESSADORES.............................................................................................................. 279 10.1.1

MÁQUINAS DE PILHA ................................................................................................................................... 279

10.1.2 ACCUMULATOR MACHINES ....................................................................................................................... 280 10.1.3 MEMORY MACHINES ...................................................................................................................................... 281 10.1.4 LOAD-STORE MACHINES .............................................................................................................................. 281 10.1.5 COMPARAÇÃO DO CÓDIGO ....................................................................................................................... 282 10.1.6 MODOS DE ENDEREÇAMENTO ................................................................................................................ 283 10.1.7 PIPELINING ......................................................................................................................................................... 284 10.2 REPRESENTAÇÃO INTERMÉDIA............................................................................................................................. 286 10.3 TAXINOMIA DAS LINGUAGENS INTERMÉDIAS................................................................................................ 286 10.3.1 REPRESENTAÇÃO DE ALTO NÍVEL ........................................................................................................ 287 10.3.2 REPRESENTAÇÃO DE NÍVEL MÉDIO ...................................................................................................... 287 10.3.3 REPRESENTAÇÃO DE BAIXO NÍVEL ...................................................................................................... 287 10.3.4 REPRESENTAÇÃO MULTINÍVEL ............................................................................................................... 288 10.4 COMPILAÇÃO DURANTE A EXECUÇÃO .............................................................................................................. 288 10.5 ARQUITETURA VIA ...................................................................................................................................................... 289 10.5.1 TIPOS DE DADOS ............................................................................................................................................ 290 10.5.2 GERAÇÃO DE CÓDIGO INTERMÉDIO DE PILHA ................................................................................. 290 10.5.3 GERAÇÃO DE CÓDIGO INTERMÉDIO PARA REGISTOS ................................................................. 292 10.5.3.1 CÓDIGO DE TRIPLO ENDEREÇO (C3E) .................................................................................. 292 10.5.3.2 STATIC SINGLE ASSIGNMENT (SSA) .................................................................................... 293 10.5.4 CONVERSÃO DE CÓDIGO DE PILHA PARA REGISTOS .................................................................. 295 10.6 ASSEMBLY VIA ............................................................................................................................................................. 296 10.6.1 DELIMITAÇÃO ................................................................................................................................................... 297 10.6.2 COMENTÁRIOS................................................................................................................................................. 297 10.6.3 SEGMENTOS ..................................................................................................................................................... 297 10.6.4 ETIQUETAS ........................................................................................................................................................ 298 10.6.5 DADOS INICIADOS E NÃO INICIADOS .................................................................................................... 298 10.6.6 ALINHAMENTO................................................................................................................................................. 298 10.7 INSTRUÇÕES VIA .......................................................................................................................................................... 299 10.7.1 ACESSO À PILHA ............................................................................................................................................ 299 10.7.2 OPERAÇÕES DE ACESSO À MEMÓRIA ................................................................................................ 299 10.7.3 OPERAÇÕES ARITMÉTICAS ....................................................................................................................... 300 10.7.4 OPERAÇÕES BIT A BIT ................................................................................................................................. 300 10.7.5 OPERAÇÕES DE COMPARAÇÃO ............................................................................................................. 300 10.7.6 OPERAÇÕES DE SALTO................................................................................................................................ 301 10.7.7 OPERAÇÕES NA PILHA ................................................................................................................................. 301

© FCA – EDITORA DE INFORMÁTICA

IX


COMPILADORES: DA TEORIA À PRÁTICA

10.7.8 FUNÇÕES .............................................................................................................................................................. 301 10.7.9 ARITMÉTICA DE VÍRGULA FLUTUANTE................................................................................................ 302 10.7.10 OPERAÇÕES SEM SINAL ............................................................................................................................ 302 10.7.11 OPERAÇÕES SINTETIZADAS .................................................................................................................... 302 EXERCÍCIOS ................................................................................................................................................................................ 303 11

GERAÇÃO DE CÓDIGO INTERMÉDIO 11.1

11.2

307

GERAÇÃO DE DADOS ................................................................................................................................................. 307 11.1.1

COMPILAÇÃO SEPARADA ......................................................................................................................... 307

11.1.2

DADOS GLOBAIS ............................................................................................................................................ 308

GERAÇÃO DE EXPRESSÕES ....................................................................................................................................... 311 11.2.1

ACESSO A ESCALARES ................................................................................................................................. 311

11.2.2 ATRIBUIÇÃO MÚLTIPLA ................................................................................................................................. 312 11.2.3 GERAÇÃO DE ACESSOS A PONTEIROS, VETORES E ESTRUTURAS ....................................... 313 11.3

GERAÇÃO DE INSTRUÇÕES ....................................................................................................................................... 316 11.3.1

GERAÇÃO DE CONDIÇÕES ........................................................................................................................... 316

11.3.2 GERAÇÃO DE CICLOS ..................................................................................................................................... 317 11.3.2.1

ALTERAÇÃO DO FLUXO DE CONTROLO ............................................................................. 320

11.3.3 GERAÇÃO DE OPERAÇÕES LÓGICAS ................................................................................................... 322 11.3.3.1

AVALIAÇÃO NÃO CONDICIONAL DE OPERAÇÕES LÓGICAS ................................... 323

11.3.4 GERAÇÃO DO IF-ARITMÉTICO .................................................................................................................. 324 11.3.5 GERAÇÃO DE INSTRUÇÕES DE ESCOLHA MÚLTIPLA: SWITCH/CASE .................................. 324 11.3.5.1 11.4

AGRUPAMENTO DE CASOS ..................................................................................................... 326

GERAÇÃO DE FUNÇÕES ............................................................................................................................................. 327 11.4.1

INVOCAÇÃO DE FUNÇÕES .......................................................................................................................... 327

11.4.2 REALIZAÇÃO DE FUNÇÕES ....................................................................................................................... 329 EXERCÍCIOS .................................................................................................................................................................................. 331 12

GERAÇÃO DE CÓDIGO FINAL PARA MÁQUINAS DE PILHA 12.1

337

GERAÇÃO DE BYTECODES DE JAVA.................................................................................................................. 337 12.1.1

ASSEMBLER EM JAVA: JASMIN ............................................................................................................. 339

12.2 GERAÇÃO PARA O MICROSOFT .NET (MSIL)..................................................................................................... 341 12.3 GERAÇÃO DE CÓDIGO INTERMÉDIO PARA i386 (PENTIUM) .................................................................... 342 12.3.1 GERAÇÃO LOAD-STORE ............................................................................................................................. 344 12.3.2 GERAÇÃO REGISTO + MEMÓRIA ............................................................................................................ 345 12.3.3 GERAÇÃO COM O TOPO DA PILHA NUM REGISTO ........................................................................ 346 12.3.4 GERAÇÃO PARA AMD64 OU X86-64 (64 BITS) .............................................................................. 347 12.4 GERAÇÃO PARA O PROCESSADOR ARM ........................................................................................................ 349 12.4.1 CONJUNTO DE INSTRUÇÕES ..................................................................................................................... 350 12.4.2 CONJUNTO DE INSTRUÇÕES DE 16 BITS: THUMB ............................................................................ 350 12.4.3 CONJUNTO DE INSTRUÇÕES ARM DE 32 BITS ................................................................................... 351 12.4.4 GERAÇÃO LOAD-STORE ............................................................................................................................. 353 12.4.5 GERAÇÃO COM O TOPO DA PILHA NUM REGISTO ........................................................................ 354 12.5 INTERPRETAÇÃO POR THREADING .................................................................................................................... 355 X

© FCA – EDITORA DE INFORMÁTICA


ÍNDICE GERAL 12.5.1 SUBROTINE THREADING.............................................................................................................................. 355 12.5.2 DIRECT THREADING ....................................................................................................................................... 356 12.5.3 INDIRECT THREADING ................................................................................................................................... 357 12.5.4 TOKEN THREADING ........................................................................................................................................ 358 EXERCÍCIOS ................................................................................................................................................................................. 360 13

SELEÇÃO E ESCALONAMENTO DE INTRUÇÕES 13.1

361

SELEÇÃO DE INSTRUÇÕES ....................................................................................................................................... 362 13.1.1

GRAMÁTICAS DE INSTRUÇÕES................................................................................................................ 362

13.1.2 SELEÇÃO SEQUENCIAL DE INSTRUÇÕES ............................................................................................ 363 13.1.2.1

SELEÇÃO POR JANELA (PEEPHOLE) .................................................................................... 363

13.1.2.2 SELEÇÃO POR GRAMÁTICA REGULAR ............................................................................... 364 13.1.3 SELEÇÃO DE INSTRUÇÕES POR ÁRVORE SINTÁTICA .................................................................. 365 13.1.3.1

SELEÇÃO DE INSTRUÇÕES POR PROGRAMAÇÃO DINÂMICA ................................. 367

13.1.3.2 EMPARELHAMENTO COM CUSTOS VARIÁVEIS ............................................................. 369 13.1.3.3 CONSTRUÇÃO DA ÁRVORE SINTÁTICA PARA EMPARELHAMENTO ..................... 371 13.1.3.4 FERRAMENTAS BURG E LINGUAGEM SIMPLES................................................................ 371 13.1.3.5 GERAÇÃO VIA COM PBURG ..................................................................................................... 373 13.1.3.6 SELEÇÃO DE INSTRUÇÕES POR MAXIMAL-MUNCH ..................................................... 377 13.2 ESCALONAMENTO DE INSTRUÇÕES ................................................................................................................... 378 13.2.1 RESOLUÇÃO DE ANTIDEPENDÊNCIA ..................................................................................................... 380 13.2.2 GRAFO DE DEPENDÊNCIA ........................................................................................................................... 381 13.2.3 ESCALONAMENTO DAS INSTRUÇÕES .................................................................................................. 383 13.2.4 ESCALONAMENTO DAS INSTRUÇÕES POR LISTA.......................................................................... 384 EXERCÍCIOS ................................................................................................................................................................................. 387 14

RESERVA DE REGISTOS 14.1

389

SPILLING ............................................................................................................................................................................ 389 14.1.1

REGISTOS LIMPOS E SUJOS...................................................................................................................... 390

14.2 ATRIBUIÇÃO DE REGISTOS ....................................................................................................................................... 391 14.3 RESERVA GREEDY........................................................................................................................................................ 391 14.4 RESERVA TOP-DOWN ................................................................................................................................................ 392 14.5 RESERVA POR ORDENAÇÃO DE SUBÁRVORES ........................................................................................... 392 14.6 RESERVA POR PRÓXIMO USO ............................................................................................................................... 395 14.7 RESERVA POR PESQUISA LINEAR....................................................................................................................... 398 14.7.1 ALGORITMO DE PESQUISA LINEAR ....................................................................................................... 399 14.8 RESERVA POR COLORAÇÃO DE GRAFOS ......................................................................................................... 401 14.8.1 CONSTRUÇÃO DO GRAFO DE INTERFERÊNCIAS ............................................................................. 402 14.8.2 SIMPLIFICAÇÃO DO GRAFO ....................................................................................................................... 405 14.8.3 SELECIONAR CORES DOS NÓS ................................................................................................................ 406 14.8.4 COALESCÊNCIA E SPILLING ....................................................................................................................... 407 14.8.4.1 COALESCÊNCIA DE NÓS ........................................................................................................... 408 14.8.4.2 CONGELAR MOVIMENTOS....................................................................................................... 409 14.8.4.3 IDENTIFICAR SPILL POTENCIAL ............................................................................................. 409

© FCA – EDITORA DE INFORMÁTICA

XI


COMPILADORES: DA TEORIA À PRÁTICA

14.8.4.4 EFETUAR SPILL REAL .................................................................................................................. 410 14.8.5 PRÉ-COLORAÇÃO DE NÓS ........................................................................................................................... 410 EXERCÍCIOS ................................................................................................................................................................................... 411 15

ANÁLISE DE FLUXO

413

15.1 ANÁLISE DE FLUXO DE CONTROLO ....................................................................................................................... 413 15.1.1

BLOCOS BÁSICOS ............................................................................................................................................ 413

15.1.2 GRAFO DE FLUXO DE CONTROLO ............................................................................................................ 414 15.1.2.1

CÁLCULO DE DOMINANTES........................................................................................................ 416

15.1.2.2 DETEÇÃO DE CICLOS ..................................................................................................................... 417 15.2 ANÁLISE DE FLUXO DE DADOS ............................................................................................................................... 418 15.2.1 GRAFOS DIRIGIDOS ACÍCLICOS (DAG) .................................................................................................... 418 15.2.1.1

GERAÇÃO DAS INSTRUÇÕES A PARTIR DE UM DAG .................................................... 419

15.2.2 FLUXO DE DADOS ENTRE BLOCOS BÁSICOS ................................................................................... 420 15.2.2.1 TERMINOLOGIA NA ANÁLISE DE FLUXO DE DADOS ..................................................... 421 15.2.3 EQUAÇÕES DE FLUXO DE DADOS ......................................................................................................... 422 15.2.3.1 TODOS OS CAMINHOS EM SENTIDO DIRETO .................................................................. 422 15.2.3.2 UM DOS CAMINHOS EM SENTIDO DIRETO........................................................................ 423 15.2.3.3 TODOS OS CAMINHOS EM SENTIDO INVERSO............................................................... 423 15.2.3.4 UM DOS CAMINHOS EM SENTIDO INVERSO .................................................................... 424 15.2.3.5 RESUMO DAS EQUAÇÕES DE FLUXO ................................................................................. 425 15.2.3.6 CADEIAS DE UTILIZAÇÃO-DEFINIÇÃO ................................................................................. 425 15.2.4 RESOLUÇÃO DAS EQUAÇÕES DE FLUXO ........................................................................................... 425 15.2.4.1 FLUXO DE DADOS EM SEQUÊNCIAS.................................................................................... 426 15.2.4.2 FLUXO DE DADOS EM ALTERNATIVAS...............................................................................427 15.2.4.3 FLUXO DE DADOS EM CICLOS .................................................................................................427 15.2.4.4 FLUXO EM PROGRAMAS NÃO ESTRUTURADOS ........................................................... 428 15.3 ANÁLISE DE CICLOS .................................................................................................................................................... 429 15.3.1 DESLOCAÇÃO DE INVARIANTES DE CICLO ........................................................................................ 430 15.3.2 SIMPLIFICAÇÃO DE VARIÁVEIS DE INDUÇÃO .................................................................................... 431 15.3.3 FUSÃO E DESDOBRAMENTO DE CICLOS ............................................................................................ 432 15.4 ANÁLISE DE PROCEDIMENTOS .............................................................................................................................. 433 15.4.1 INVOCAÇÃO DE PROCEDIMENTOS ........................................................................................................ 433 15.4.2 ANÁLISE INTERPROCEDIMENTAL ........................................................................................................... 434 15.5 MELHORAR O DESEMPENHO ................................................................................................................................. 435 15.5.1 OTIMIZAÇÃO PELO UTILIZADOR ............................................................................................................. 435 15.5.2 OTIMIZAÇÃO INDEPENDENTE DA MÁQUINA .................................................................................... 436 15.5.2.1 DESDOBRAMENTO E PROPAGAÇÃO DE CONSTANTES (CONSTANT FOLDING) 437 15.5.2.2 ELIMINAÇÃO DE CÓDIGO INACESSÍVEL ............................................................................. 437 15.5.2.3 INLINE DE FUNÇÕES ................................................................................................................... 437 15.5.2.4 CICLOS............................................................................................................................................... 437 15.5.2.5 DETEÇÃO DE SINÓNIMOS (ALIASES) ................................................................................. 438 15.5.2.6 ELIMINAÇÃO DE SUBEXPRESSÕES COMUNS ................................................................ 438 15.5.2.7 DESVIRTUALIZAÇÃO.................................................................................................................. 438

XII

© FCA – EDITORA DE INFORMÁTICA


ÍNDICE GERAL 15.5.3 OTIMIZAÇÃO DEPENDENTE DA MÁQUINA......................................................................................... 438 15.5.3.1 ELIMINAÇÃO DE CÓDIGO REDUNDANTE ............................................................................ 439 15.5.3.2 SIMPLIFICAÇÕES ALGÉBRICAS ............................................................................................... 439 15.5.3.3 REDUÇÃO DE ESFORÇO (STRENGTH-REDUCE) ............................................................... 439 15.5.3.4 PROPAGAÇÃO DE CÓPIAS........................................................................................................ 440 15.5.3.5 REDUÇÃO E PREVISÃO DE SALTOS (BRANCH PREDICTION)....................................440 15.5.3.6 ESPERAS DE MEMÓRIA (MEMORY ACCESS STALLS).................................................440 15.5.3.7 OTIMIZAÇÃO EM TERMOS DE ESPAÇO (CODE HOISTING) ......................................... 441 15.5.3.8 OTIMIZAÇÃO ESPECÍFICA........................................................................................................... 441 15.5.4 COMPILADOR OTIMIZANTE........................................................................................................................ 442 EXERCÍCIOS ................................................................................................................................................................................. 443 GLOSSÁRIO DE TERMOS PORTUGUÊS EUROPEU / PORTUGUÊS DO BRASIL

445

REFERÊNCIAS BIBLIOGRÁFICAS

447

ÍNDICE REMISSIVO

457

© FCA – EDITORA DE INFORMÁTICA

XIII



S0BRE O LIVRO

O

compilador é uma ferramenta que mapeia de uma forma eficiente programas em linguagem-máquina. A geração do código-máquina é precedida de uma fase de análise do programa para garantir a correção do mesmo. A análise, para ser eficiente, é efetuada com algoritmos de complexidade linear. Para tal são abordados autómatos finitos para linguagens regulares, autómatos de pilha para análise descendente LL(1) e SLL(k), bem como análise ascendente LR(0), SLR(1) e LALR(1). Em qualquer dos casos são apresentadas soluções de análise baseadas em tabela e por codificação direta (hardcoded). O processo de análise é exemplificado com uma linguagem de programação simples numa abordagem estruturada na linguagem C e numa abordagem orientada por objetos na linguagem Java. A construção do compilador recorre a analisadores lexicais (flex, jflex e jlex), analisadores sintáticos ascendentes (byacc e bison) e o analisador sintático descendente antlr. A geração de código parte de uma árvore sintática que é linearizada para instruções de uma máquina de pilha abstrata. O código linearizado é convertido por tradução direta em bytecodes Java, MSIL de .net. São igualmente apresentadas três abordagens de geração de código de pilha, por tradução direta, para o processador Pentium e duas para o processador arm. A geração de código final para processadores de registos uniformes recorre à seleção de instruções com base na árvore sintática, seguida do escalonamento das instruções resultantes e da reserva de registos. As linguagens de programação, e outras linguagens, continuam e evoluir. As arquiteturas dos computadores, e outros dispositivos, são cada vez mais sofisticadas. As aplicações são cada vez mais ambiciosas, quer na sua dimensão quer na sua complexidade. Os compiladores têm problemas cada vez mais ambiciosos, seja em termos de complexidade ou de dimensão. Embora de um ponto vista global os problemas dos compiladores permaneçam inalterados, os desafios específicos evoluem continuamente. Além disso, à medida que os recursos disponíveis aumentam, os compiladores atuais podem agora utilizar algoritmos mais exigentes, quer novos quer já estabelecidos, em termos de memória ou tempo de processamento.

MOTIVAÇÃO A compilação é uma atividade de engenharia que oferece boas soluções para a tradução de problemas práticos, nomeadamente, a execução em máquinas reais com processadores atuais de programas reais desenvolvidos em linguagens atuais. Normalmente, o desenvolvimento de um compilador tem de utilizar linguagens e máquinas existentes, não podendo influenciar ou melhorar as características nem de uma nem de outra. Assim, as decisões que tomar no desenho do compilador, em termos da escolha do processo de análise ou das transformações utilizadas na geração, serão determinantes no desempenho e qualidade quer do próprio compilador quer dos programas por ele gerados. A principal motivação deste livro é apresentar o compilador como ferramenta determinante no desempenho das aplicações e permitir compreender a interligação entre linguagens, processadores, arquiteturas e sistemas operativos. Compreender as limitações e os pontos fortes das linguagens de programação, nomeadamente as compiladas, e as implicações das respetivas © FCA – EDITORA DE INFORMÁTICA

XV


COMPILADORES: DA TEORIA À PRÁTICA

características em termos de desempenho do programa produzido, e até do próprio compilador. Compreender como os compiladores utilizam os processadores e a forma como a evolução dos processadores tem feito evoluir os compiladores, já que a quase totalidade do código executado é compilado. Este livro procura abordar os desafios das linguagens e das arquiteturas atuais, preparando o leitor para os desafios que, inevitavelmente, surgirão no futuro na área dos compiladores.

ABORDAGEM O livro procura contemplar diversas abordagens ao estudo e aplicação dos compiladores, nomeadamente para linguagens de programação. Para tal, considera-se desde a interpretação, à geração de código de nível intermédio (bytecodes de Java ou MSIL para .net) e à geração de código final otimizado para processadores de registos uniformes. O material apresentado serve de base a diversas abordagens ao estudo dos compiladores, em particular para um nível universitário. Além da experiência recolhida na lecionação das disciplinas de Compiladores, Projeto de Compiladores e Complementos de Compiladores do Instituto Superior Técnico (IST), procura-se apresentar outras soluções semelhantes largamente divulgadas. Fazendo parte do currículo dos cursos de Ciência da Computação e de Informática, o estudo dos compiladores é essencial para compreender a ligação entre a engenharia de software, as linguagens de programação, os sistemas operativos e as arquiteturas de processadores. A abordagem utilizada tem como objetivo fornecer uma competência sólida dos conceitos subjacentes ao desenvolvimento dos compiladores, e das ferramentas utilizadas na sua construção. As noções teóricas, em paralelo com uma forte vertente prática, oferece um contacto direto com exercícios e projetos de complexidade crescente. Os exemplos, exercícios e projetos utilizam as linguagens de programação C/C++ e Java, paralelamente numa abordagem puramente estruturada ou orientada por objetos. Só com a prática se consegue a solidificação dos conceitos, através da visão concreta da sua aplicação.

ORGANIZAÇÃO DA OBRA O livro acompanha o processamento de um programa, desde a leitura da sua especificação até ao código máquina otimizado. O Capítulo 1 introduz o estudo dos compiladores e das linguagens, sendo seguido de duas partes, correspondentes às duas principais fases do processo de compilação: a análise e a síntese. A Parte I, ou análise determinista de linguagens, baseia-se na utilização de autómatos finitos, com e sem pilha auxiliar, para verificar o programa de entrada e construir uma representação interna. No Capítulo 2 são apresentados algoritmos que leem o programa carácter a carácter e produzem uma sequência de elementos lexicais. Os três capítulos seguintes descrevem o uso de gramáticas livres de contexto para processar os elementos lexicais, garantindo a sua correta sequência sintática. Os Capítulos 6 e 7 completam as verificações da programa com base em valores, designados por atributos, associados aos elementos gramaticais (lexicais e sintáticos). O Capítulo 8

XVI

© FCA – EDITORA DE INFORMÁTICA


SOBRE O LIVRO exemplifica, com auxílio de uma linguagem simples todo o processo com recurso às ferramentas mais divulgadas. A Parte II, ou geração de código, está dividida em dois grandes grupos, sendo o primeiro constituído pelos Capítulos 9 a 12 e o segundo pelos Capítulos 13 a 15. O Capítulo 9 descreve o ambiente de execução enquanto os Capítulos 10 e 11 descrevem e produzem código linearizado de nível intermédio. Este código intermédio pode ser utilizado diretamente para produzir bytecodes de Java, MSIL para .net ou até código final não otimizado para máquinas i386, como o processador Pentium, bem como o processador arm, tal como apresentado no Capítulo 12. Os capítulos finais seguem os passos necessários para produzir código otimizado para máquinas de registos uniformes. No Capítulo 13 as instruções do processador final são selecionadas e escalonadas de acordo com as capacidades de paralelismo do processador. Seguidamente determina-se a ocupação dos registos do processador. O Capítulo 15 introduz algumas técnicas adicionais de otimização de código.

ORGANIZAÇÃO DO CURSO Uma referência impõe-se ao dimensionamento de um curso com base neste livro. Na realidade, a extensão e complexidade da matéria pode obrigar a tomar algumas opções. Em primeiro lugar, é necessário determinar o grau de profundidade que se pretende obter, podendo-se optar por processar apenas o indispensável para a interpretação de programas ou pela geração de código. Assim, numa perspetiva exclusivamente de análise de linguagens, apenas os primeiros oito capítulos são essenciais. Para a interpretação do código analisado podem também ser úteis o Capítulo 9 e a última secção do Capítulo 12. No caso de se optar pela geração de código, é importante ter presente que a geração de código otimizado implica um esforço adicional significativo. Pelo que uma abordagem interessante consiste em gerar código final não optimizado, tal como é descrito nos primeiros 12 capítulos. Por outro lado é essencial ter em conta os conhecimentos anteriores, uma vez que se existir um conhecimento prévio de autómatos finitos ou de teoria da computação, o esforço dispensado nos capítulos iniciais será significativamente menor. Os Capítulos 4 e 5 apresentam perspetivas distintas de análise gramatical. Enquanto no Capítulo 4 sobre Análise Descendente a especificação da gramática é mais exigente e o analisador mais simples, no Capítulo 5 sobre Análise Ascendente são aceites gramáticas menos restritivas. Experiências anteriores sugerem que a compreensão do Capítulo 5 fica muito facilitada com a compreensão do processo de análise descendente descrito no Capítulo 4. No entanto, caso se pretenda utilizar apenas uma abordagem descendente, por exemplo, com recurso à ferramenta antlr, o Capítulo 5 torna-se facultativo. O Capítulo 12 descreve a utilização de código intermédio como facultativo numa abordagem completa que inclua os capítulos procuram otimizar o código a gerar, sendo, por isso, opcionais. geração de código aceitavelmente otimizado, uma vez que as riores já introduzem a maioria dos ganhos de otimização.

código final não otimizado, sendo seguintes. Os Capítulos 13 a 15 O Capítulo 15 é facultativo numa técnicas dos dois capítulos ante-

© FCA – EDITORA DE INFORMÁTICA

XVII


COMPILADORES: DA TEORIA À PRÁTICA

O livro pressupõe o conhecimento de estruturas de dados (como árvores, tabelas de dispersão, listas e grafos) e de uma de três linguagens de programação: C, C++ ou Java. O leitor poderá encontrar todos os programas apresentados neste livro na página da editora na Internet, em http://www.fca.pt, até o livro se esgotar ou ser publicada nova edição atualizada ou com alterações.

XVIII

© FCA – EDITORA DE INFORMÁTICA


1 INTRODUÇÃO Um compilador é uma aplicação complexa, sem privilégios especiais, que traduz uma descrição numa linguagem fonte numa descrição equivalente numa linguagem destino. Na sua forma mais complexa, um compilador traduz um programa numa linguagem de programação, num programa executável em linguagem máquina numa arquitetura de destino. Uma vez que a maioria do software é compilado, é imperativo que o compilador garanta a correção do resultado. Assim, os princípios fundamentais da compilação consistem em preservar o significado do programa fonte e melhorar o resultado produzido de forma significativa. O compilador deve ser eficiente, para que a tradução não seja exageradamente lenta, e o código produzido eficiente, para que seja rápido quando executado. Para tal, a construção de compiladores reúne diversas disciplinas da ciência da computação numa só aplicação. Os compiladores representam a ponte entre as aplicações e os sistemas. O compilador é um microcosmo de engenharia de software, usando estruturas de dados e algoritmos complexos. Como parte do processo de tradução, o compilador necessita de analisar o programa fonte para determinar se é uma descrição válida e converter o programa num conjunto finito de recursos na arquitetura de destino. Para que o resultado seja eficiente, necessita explorar alternativas utilizando, por exemplo, heurísticas ou métricas estatísticas. Atualmente, é a própria tecnologia dos compiladores que determina quais as características que devem ser acrescentadas ao novo hardware, bem como quais as novas características das linguagens necessárias ao desenvolvimento de software. A própria tecnologia dos compiladores tem evoluído significativamente nos últimos anos com a automatização das técnicas de análise, a compilação parcial, o compilador JIT (Just-in-time) e novas técnicas de otimização para processadores RISC.

1.1 PROGRAMA EXECUTÁVEL O computador tem as suas origens em máquinas não programáveis, como, por exemplo, as calculadoras mais simples de hoje em dia. Estas máquinas podem possuir uma unidade de processamento (CPU) e alguma memória para armazenar resultados intermédios. Nestes casos, para efetuar uma nova tarefa cada novo programa obrigava à construção de uma nova máquina. O grande avanço, em meados do século XX, foi a utilização da memória para armazenar não apenas dados como o próprio programa. Assim, num processador Pentium atual, cada valor em memória pode ter várias interpretações. Por exemplo, se uma posição de memória contiver o valor binário 01101000 ou 68 em hexadecimal, esta localização de memória pode ser interpretada como o valor © FCA – EDITORA DE INFORMÁTICA

1


COMPILADORES: DA TEORIA À PRÁTICA

inteiro decimal 104, como o carácter ’h’ na tabela ASCII ou como a instrução-máquina que empurra uma constante para a pilha do processador (push immediate). É da responsabilidade do compilador determinar os valores a guardar e condicionar a forma como devem ser interpretados. A tradução do programa resultante para valores binários a partir de descrições textuais é muitas vezes efetuada por uma outra ferramenta – o montador ou assembler. O programa compilado pode ser agora executado diretamente pelo processador com o apoio do sistema operativo. No entanto, o assembler faz uma tradução direta do texto em assembly para binário sem alterar as instruções, embora alguns assemblers possam fazer algumas manipulações básicas. O processo inverso também é possível através de uma ferramenta de desmontagem, ou disassembler, que produz uma descrição textual em assembly diretamente a partir de programas executáveis, bibliotecas ou ficheiros-objeto.

1.2 PROCESSO DE COMPILAÇÃO O processo de compilação designa o conjunto de tarefas que o compilador deve realizar para poder gerar uma descrição numa linguagem a partir de outra. Numa fase inicial, designada por análise, o compilador lê uma descrição na linguagem fonte e deve garantir que a descrição está correta. Na sua forma mais simples, designada por interpretação, o próprio compilador, designado por interpretador, executa as instruções que acabou de ler:

A interpretação pode ser diretamente efetuada a partir da descrição, em geral textual, do programa de entrada. Este caso, designado por interpretação direta, é utilizado por diversas ferramentas simples como, por exemplo, os interpretadores de comandos fornecidos com os sistemas operativos. Uma solução mais eficiente consiste em construir uma estrutura de dados que represente o programa. Neste caso, a repetição de código, como ciclos ou rotinas, só são analisados uma só vez, mas executados diversas vezes. Para obter uma execução ainda mais eficiente, a estrutura do programa pode ser linearizada num conjunto de instruções. Esta técnica, designada por threading (ver secção 12.5, no Capítulo 12), gera um conjunto código semelhante ao código final de um processador. As instruções são representadas por ponteiros para as rotinas que as realizam ou são numeradas e posteriormente descodificadas através de uma tabela. As representações resultantes da análise do programa de entrada, como as estruturas de dados ou as linearizações por ponteiros ou numeradas, podem ser salvaguardadas para posterior interpretação ou compilação. O ambiente Java guarda nos ficheiros .class 2

© FCA – EDITORA DE INFORMÁTICA


INTRODUÇÃO

1

instruções para uma máquina virtual numeradas de 0 a 255, que se designam por bytecodes pois ocupam um só byte. Estas instruções podem ser posteriormente interpretadas mais rapidamente, pois o processo de análise é muito mais simples do que numa descrição textual. O compilador analisa uma descrição numa linguagem-fonte, tal como o interpretador, mas gera uma linguagem que pode ser diretamente executada por um processador real. O resultado pode ser depois executado de uma forma eficiente:

Notar que o compilador pode analisar uma linguagem de alto nível como a linguagem C e produzir código-máquina. No caso da linguagem de alto nível Java, o compilador gera um conjunto de bytecodes. Posteriormente, estes bytecodes podem ser interpretados pela máquina virtual de Java ou compilados para código-máquina para execução imediata pelo ambiente de execução Java. Estes compiladores que produzem código executável para execução imediata, não num ficheiro mas diretamente na memória do processo que os vai executar, são designados por compiladores JIT. Notar que também existem compiladores JIT para a linguagem C, como o tcc [102]. A principal diferença entre um compilador JIT e um interpretador é que no caso do compilador JIT é o processador que executa as instruções e não a ferramenta.

1.3 ESTRUTURA DO COMPILADOR Internamente, um compilador é constituído por dois componentes distintos: a análise e a síntese. Na análise, o compilador processa a sequência de entrada, em geral sob a forma de um ficheiro, e verifica se corresponde a uma sequência válida para a linguagem em análise. Nesta fase da análise, o compilador produz dois resultados possíveis: erro ou sucesso. Em caso de erro, devem ser geradas mensagens o mais explicativas possível dos erros encontrados, não sendo efetuado nenhum outro processamento. Em caso de sucesso, a fase de análise constrói uma estrutura de dados interna que é passada à fase de síntese. Esta estrutura de dados deverá conter toda a informação necessária à síntese que se encontrava na sequência de entrada. Na fase de síntese, a estrutura de dados interna é processada para produzir um formato de saída. Este formato de saída pode ser a geração de código-máquina para determinado processador, a geração de código de alto nível numa outra linguagem, a interpretação dos valores e ações existentes na sequência

© FCA – EDITORA DE INFORMÁTICA

3


COMPILADORES: DA TEORIA À PRÁTICA

de entrada ou um simples relatório estatístico. Cada uma das duas fases anteriores, análise e síntese, pode ser dividida em subfases:

1.3.1

ANÁLISE DETERMINISTA DE LINGUAGENS

No caso da análise, esta é geralmente dividida em análise lexical, análise sintática e análise semântica. Esta divisão permite explorar ao máximo a capacidade e eficiência, quer em termos de tempo de execução quer em termos de memória ocupada, de cada um dos algoritmos utilizados na análise. Para ser utilizável num compilador eficiente, cada um dos algoritmos utilizados deverá ser determinista e ter um desempenho, pelo menos, linear. A análise lexical agrupa conjuntos de caracteres em elementos lexicais ou tokens com recurso a expressões regulares. O processamento de expressões regulares tem a vantagem de poder ser realizada com analisadores muito simples e exigindo muito pouca memória. Na análise lexical, são identificados operadores, delimitadores, identificadores, literais, comentários, caracteres brancos ou palavras reservadas da linguagem. É responsabilidade do analisador lexical distinguir entre o operador subtração (-) e o operador decremento (--), mas não se o operador subtração for unário (para determinação do simétrico, por exemplo, -5) ou binário (por exemplo, 8 - 5). É responsabilidade do analisador sintático determinar se existem caracteres individuais ou sequências destes que não representem elementos lexicais válidos na linguagem. Por exemplo, na análise de programas em C/C++ [23, 33] ou Java [47] os caracteres @ ou $ não podem ser utilizados fora de cadeias de caracteres ou comentários. De um ponto de vista da língua portuguesa, a análise lexical deve identificar a palavra bolacha como um nome, mas gerar um erro ao encontrar a palavra bolaxa, tal como fazem os verificadores linguísticos. A análise sintática recebe os elementos lexicais gerados pela análise lexical e procura colocá-los numa árvore sintática, de acordo com as regras gramaticais que definem a linguagem. A árvore sintática é frequentemente transformada por compiladores mais complexos numa estrutura de dados em árvore. Nos casos mais simples a estrutura não necessita de ser explicitamente construída. O analisador sintático recorre a uma pilha

4

© FCA – EDITORA DE INFORMÁTICA


INTRODUÇÃO

1

auxiliar, com base na qual determina se a sequência dos elementos lexicais é a correta. Se o elemento lexical for obrigatório ou opcional na linguagem, é responsabilidade do analisador sintático garantir ou não a sua existência na sequência de entrada. Por exemplo, na análise de programas em C/C++ ou Java não é possível ter dois literais inteiros seguidos sem um operador ou separador entre eles. Notar que um analisador sintático pode processar diretamente a sequência do ficheiro de entrada, carácter a carácter, mas a gramática fica desnecessariamente complexa e o processamento mais dispendioso, em termos de tempo e memória, pois um analisador sintático é muito mais complexo do que um analisador lexical. Todas as verificações que não tenham sido efetuadas pelas duas fases anteriores necessitam de ser efetuadas na análise semântica. Notar que, de um ponto de vista linguístico, a análise semântica não existe. De um ponto de vista computacional, a análise semântica resulta da incapacidade da análise sintática em efetuar todas as verificações necessárias apenas com base numa gramática EBNF. Por vezes, embora algumas verificações sejam possíveis gramaticalmente, a gramática resultante ficaria desnecessariamente complexa, pelo que se opta por usar uma gramática mais simples e efetuar a verificação omitida através da análise semântica. Por exemplo, na análise de programas em C/C++ ou Java é possível verificar se as instruções break ou continue existem dentro de ciclos, criando dois tipos de blocos de instruções. Da mesma forma, a instrução break pode ainda existir dentro de instruções switch, pelo que passa a haver três tipos de blocos. A gramática fica significativamente mais simples com um só tipo de bloco e na análise semântica usa-se uma variável auxiliar para verificar se o bloco se encontra dentro de um ciclo ou switch. Por outro lado, a verificação do tipo das variáveis, ou se estas foram declaradas antes de serem usadas, tem de ser efetuada na análise semântica. Durante a análise sintática apenas deve ser verificado que uma variável pode ser utilizada naquele contexto, independentemente do nome da variável. Terminada a análise semântica ficase com a certeza de que a sequência de entrada obedece à linguagem especificada e que é possível proceder à sua geração. Notar que do ponto de vista da língua portuguesa é necessário determinar que a ação O João comeu a bolacha está correta, mas que a ação A bolacha comeu o João está errada, pois o sujeito (a bolacha) é incapaz de realizar a ação pedida.

1.3.2 SINTESE DO CÓDIGO A fase de síntese utiliza uma estrutura de dados que a análise garante estar correta. Notar que em compiladores muitos antigos ou linguagens muito simples, a estrutura pode ser a própria sequência de entrada sem qualquer alteração. Casos simples, como a interpretação ou a geração dirigida pela sintaxe, serão abordados mais à frente. Na interpretação, a cada primitiva da linguagem corresponde uma sequência de código que é executada pelo interpretador. A ativação destas sequências de código direta é feita através da interpretação linha a linha do texto do programa, dirigida pela sintaxe ou por interpretação da árvore sintática.

© FCA – EDITORA DE INFORMÁTICA

5


COMPILADORES: DA TEORIA À PRÁTICA

Uma forma mais complexa de interpretação consiste em gerar código para um processador, imaginário ou real, como se de um normal compilador se tratasse, mas as instruções ativam as sequências de código do interpretador por uma técnica de threading, permitindo a emulação de processador por outro. Embora o código a interpretar possa ser código final de um outro processador, é frequente recorrer a processadores imaginários que incluem simplificações tais como a ausência de registos ou um número ilimitado de registos. Estas simplificações facilitam a geração de código, eliminando diversas fases do processo de síntese. Este tipo de código, genericamente designado por intermédio, é frequentemente utilizado na primeira fase do processo de síntese, durante a linearização das instruções a partir da estrutura de dados interna. O C3E, ou código de triplo endereço, e o SSA, ou static single assignment, são exemplos de código intermédio para registos ilimitados. Por outro lado, os bytecodes de Java ou o MSIL do .net são exemplos de código intermédio para máquinas sem registos, também designadas por máquinas de pilha. Neste livro utilizaremos ainda o conhecido processador i386 (ou as suas variantes mais atuais, designadas por Pentium) como uma máquina de pilha, o que permite a execução direta de código intermédio sem tirar partido de todos os registos do processador, de instruções para funções específicas ou da execução paralela, entre outros. Na solução mais complexa e mais extensamente analisada neste livro, ou seja, a geração de código para um processador, existem diversas fases no processo de síntese do compilador. Numa primeira fase, a estrutura de dados interna é linearizada utilizando instruções-máquina genéricas. Seguidamente, estas instruções são convertidas em instruções do processador de destino, tendo em conta as capacidades específicas deste. Nesta fase, designada por seleção, uma instrução genérica pode ser convertida em diversas instruções do processador, ou vice-versa, uma sequência de diversas instruções genéricas pode ser convertida numa só instrução, dependendo das capacidades do processador. Depois, as instruções são escalonadas para permitir maior paralelismo. Os processadores recentes permitem que uma soma seja efetuada ao mesmo tempo que uma multiplicação, desde que o resultado seja colocado num registo diferente. Assim, é possível reordernar as instruções por forma a tirar partido dessa capacidade e reduzir o tempo de execução. Até esta fase todos os valores são guardados em registos abstratos. A fase de reserva de registos determina quais serão os valores que podem ficar nos registos do processador e quais os que, por serem menos usados, terão de ser guardados na memória, com a sobrecarga adicional de ler e escrever os seus valores. O código obtido após esta fase deverá ser de boa qualidade, mas pode haver otimizações que permitam melhorar ainda mais o desempenho do código gerado, quer do ponto de vista do tempo de execução quer do ponto de vista do espaço ocupado. A fase de otimização analisa o código gerado, em termos de fluxo de controlo e de fluxo de dados, e determina se existem melhores soluções. A otimização deverá preservar o significado do código original e permitir ganhos visíveis. Finalmente, e para dar apoio às fases anteriores, é necessária uma infraestrutura auxiliar como estruturas de dados: pilhas, árvores, grafos. O compilador recorre a estas estruturas para guardar informação e determinar dependências entre os diversos ele-

6

© FCA – EDITORA DE INFORMÁTICA


INTRODUÇÃO

1

mentos. Um dos mais importantes é a tabela de símbolos que mantém a informação das variáveis, constantes e funções definidas pelo programa. Também podem ser necessárias rotinas auxiliares de escrita e leitura, em especial para escrever o código executável em formato binário.

1.4 DESENVOLVIMENTO DE UM COMPILADOR Um compilador é uma ferramenta complexa que há alguns anos atrás exigia um esforço significativo, na ordem das centenas de pessoas/mês. Inicialmente, tal como os assemblers e outras ferramentas, os compiladores tinham de ser codificados em binário e salvaguardados em memórias ROM. O primeiro compilador de FORTRAN desenvolvido pela IBM nos anos 1950 requereu um esforço estimado de 18 pessoas/ano. Hoje em dia existem diversas ferramentas que facilitam significativamente a construção de um compilador [6, 8, 22, 26, 40, 48, 56, 58, 59, 61, 74, 80, 82, 89, 98]; entre estas é de salientar a utilização de outros compiladores, que podem ser compiladores de outras linguagens de programação na qual escolhemos escrever o nosso compilador, por exemplo C/C++ ou Java, ou compiladores especificamente desenhados para a construção de compiladores, também designados por compiladores de compiladores.

1.4.1

FERRAMENTAS DE DESENVOLVIMENTO

Podemos optar por desenvolver o nosso compilador na linguagem C, pela sua divulgação e disponibilidade no ambiente onde pretendemos produzir o nosso compilador, por exemplo. Neste caso, o compilador de C será a nossa ferramenta de eleição para gerar o compilador. No entanto, se todo o código C necessário tivesse de ser diretamente escrito pelo programador, o esforço seria significativo. O objetivo é utilizar ferramentas específicas para cada fase do processo de desenvolvimento. Estas ferramentas processam especificações mais simples, legíveis e compactas, gerando o código C que será, por sua vez, incluído no compilador a desenvolver. Não querendo, ainda entrar em pormenores em relação ao funcionamento das ferramentas, podemos utilizar um gerador lexical que, ao reconhecer uma expressão regular, constrói o elemento lexical a passar à análise sintática. A ferramenta lex [75], nome pelo qual é conhecida a ferramenta originalmente desenvolvida para UNIX, bem como as suas variantes flex, jlex ou jflex, entre outras, realiza essa tarefa. A vantagem da ferramenta consiste não apenas em apresentar as expressões regulares de uma forma legível e permitir associar facilmente o código de construção e os elementos lexicais, como permite efetuar um conjunto de verificações à linguagem especificada. Adicionalmente, fornece mecanismos para a definição de conjuntos de expressões regulares em diferentes zonas do programa, por exemplo, para processamento de cadeias de caracteres ou comentários. Assim, ao desenvolver o compilador, o programador escreve um

© FCA – EDITORA DE INFORMÁTICA

7


COMPILADORES: DA TEORIA À PRÁTICA

ficheiro com a especificação lexical, geralmente com a extensão .l e utiliza a ferramenta para gerar o código C: lex especif-lexical.l

O resultado produzido é um ficheiro designado por lex.yy.c que contém o analisador lexical, tal como descrito no Capítulo 2. A mesma ferramenta também pode gerar código C++ quando devidamente parametrizada, enquanto as ferramentas jlex ou jflex geram código Java a partir de especificações semelhantes. Da mesma forma, a análise sintática pode ser efetuada por ferramentas de análise sintática, que recebem uma especificação gramatical e produzem um analisador sintático em C ou Java, por exemplo, tal como descrito nos Capítulos 3 a 5. A ferramenta yacc [64] desenvolvida para UNIX, bem como as ferramentas byacc ou bison, aceitam uma especificação gramatical e as respetivas ações, e produzem um analisador sintático LALR(1) (ver Capítulo 5). Neste caso, a especificação sintática é construída num ficheiro com a extensão .y, e é gerado um ficheiro y.tab.c contendo o código do analisador. Também existem ferramentas equivalentes para outras linguagens, como, por exemplo, o byaccj ou o cup para Java: yacc sintaxe.y

Uma vez que os elementos lexicais produzidos pelo analisador lexical têm de ser conhecidos pelo analisador sintático, é produzido o ficheiro y.tab.h para o efeito. Notar que, como este ficheiro é produzido durante a geração do analisador sintático e não na geração do analisador lexical, a ordem de geração tem de ser mantida. Assim, para um compilador simples, em que a análise semântica e a síntese são efetuadas em C no ficheiro code.c, o compilador pode ser gerado pela sequência: yacc sintaxe.y lex especificação-lexical.l cc lex.yy.c y.tab.c code.c

Outras ferramentas, como, por exemplo, o antlr [95], permitem reunir numa só especificação, com a extensão .g, as instruções para a geração dos analisadores lexical (Lexer.java) e sintático (Parser) e para a construção da árvore. Neste caso, a geração do compilador em Java seria: java -cp antlr.jar antlr.Tool linguagem.g javac linguagemParser.java linguagemLexer.java code.java

Notar que existem ferramentas para auxiliar outras fases do processo de desenvolvimento de um compilador, como, por exemplo, as ferramentas burg para a seleção de instruções. No entanto, os casos mais complexos serão tratados em capítulos posteriores.

8

© FCA – EDITORA DE INFORMÁTICA


INTRODUÇÃO

1

1.4.2 DESENVOLVIMENTO DE PROGRAMAS Uma vez gerado o compilador, estamos aptos a compilar programas escritos para o novo compilador. Assumindo que o novo compilador, que designaremos por simples (ver Capítulo 8), produz código assembly para processadores i386, a sequência de instruções: simples exemplo.spl nasm -felf exemplo.asm ld -o exemplo exemplo.o runtime.o

permitiria produzir um executável a partir de um exemplo escrito na linguagem simples. O compilador analisa o ficheiro contendo a descrição do programa a compilar exemplo.spl e produz um ficheiro em assembly contendo uma descrição textual das instruções do processador exemplo.asm. Para converter o código textual em binário, tal como o processador entende, utilizaremos um assembler. Neste caso optámos pela ferramenta nasm, pela sua portabilidade e suporte em UNIX e Windows. Finalmente, o programa de exemplo é ligado ao suporte de execução da linguagem (ver abaixo) para produzir o ficheiro executável. Se o ficheiro exemplo.spl contiver o programa: program print '

Olá pessoal!';

end

a execução do programa gerado será: exemplo Olá pessoal!

Notar que se o compilador for desenvolvido em Windows a sequência é semelhante, com a exceção do formato do ficheiro binário e do comando de ligação: simples exemplo.spl nasm -fwin32 exemplo.asm link /out:exemplo.exe exemplo.obj runtime.obj KERNEL32.LIB

Se o compilador gerar bytecodes de Java em vez de código i386, teremos de utilizar a ferramenta jasmin que converte um ficheiro com bytecodes num ficheiro .class, ou seja: simples exemplo.spl jasmin exemplo.j java exemplo Olá pessoal!

© FCA – EDITORA DE INFORMÁTICA

9


9cm x 24cm

16,7cm x 24cm

C

Implementação de tipos de dados abstratos em C; algoritmos e estruturas de dados essenciais para escrever programas de média e elevada complexidade; novas secções sobre árvores, caminhos e circuitos.

M

Y

CM

MY

CY

CMY

K

Aprenda as regras e boas práticas na análise, conceção e desenvolvimento de aplicações orientadas pelos objetos, através de vários projetos de software e exercícios analisados e implementados em Java.

A geração de código-máquina é precedida de uma fase de análise do programa para garantir a correção do mesmo e construir uma estrutura que o represente. Num compilador, o processo de análise permite compreender muitas das limitações das linguagens de programação. Por outro lado, a geração de código permite compreender como os compiladores utilizam os processadores e a forma como a evolução dos processadores tem feito evoluir os compiladores. O livro aborda os diversos passos do desenvolvimento de um compilador, incluindo: A análise determinista linear com autómatos finitos para linguagens regulares e autómatos de pilha para uma análise ascendente e descendente; A realização de verificações semânticas e a construção da árvore sintática do programa analisado; A linearização das instruções para a geração de código direto para máquinas de pilha; A seleção e o escalonamento das instruções, bem como a reserva de registos, para máquinas de registos uniformes;

Com a leitura deste livro : Conheça o funcionamento interno de um compilador, quer ao nível da análise das linguagens a compilar, quer no que respeita à geração de código; Compreenda quais as razões de algumas limitações das linguagens compiladas; Acompanhe o desenvolvimento de um compilador simples, em C e em Java, com geração de código para bytecodes Java, MSIL .net, Pentium e arm; Domine os compiladores de compiladores lex, yacc, antlr e burg.

A otimização do código resultante, com base na análise do fluxo de controlo e de dados. Este livro reúne os aspetos mais importantes a ter em conta: como conceber uma boa experiência de jogo, o que caracteriza um jogo, a teoria de jogos, a indústria, como ser empreendedor, etc.

Uma obra que ajuda estudantes e profissionais a compreenderem os sistemas de gestão de bases de dados relacionais. Com apresentação dos conceitos fundamentais, inclui variados exemplos e exercícios.

Todo o processo de desenvolvimento é exemplificado, em C e Java, para uma linguagem de exemplo simples, com recurso às ferramentas lex, yacc, antlr e burg. Este livro é dirigido aos estudantes de nível universitário e profissional, produtores de software, programadores e utilizadores em geral que pretendam compreender de que forma o compilador converte programas descritos por linguagens de alto nível em código executável. Este livro disponibiliza ainda a correspondência dos principais termos técnicos para o Português do Brasil.

ISBN 978‐972‐722‐768‐6

9 789727 227686

Programas apresentados no livro disponíveis em www.fca.pt até este se esgotar ou ser publicada nova edição atualizada ou com alterações.

Compiladores

O compilador é uma ferramenta que converte, de uma forma eficiente, programas descritos por linguagens de alto nível em linguagem-máquina. O compilador é determinante no desempenho das aplicações, já que quase todo o código executado é compilado.

27mm

16,7cm x 24cm

9cm x 24cm

Pedro Reis Santos Professor Auxiliar do Departamento de Engenharia Informática do Instituto Superior Técnico (IST), onde é docente desde 1990. Lecionou as disciplinas de Complementos de Compiladores, Algoritmos e Estruturas de Dados, Programação por Objetos e Ambientes de Desenvolvimento. É regente da disciplina de Compiladores (IST/Taguspark).

Thibault Langlois Professor Auxiliar do Departamento de Informática da Faculdade de Ciências da Universidade de Lisboa (FCUL), onde é docente desde 2001. Lecionou e foi regente das disciplinas de Compiladores, Introdução à Programação (Java), Laboratórios de Programação (Java) e Programação I e II (em C). É regente das disciplinas de Linguagens Formais e Autómatos, Princípios de Programação (programação funcional, Haskell) e Desenvolvimento Centrado em Objetos (Java). Entre 1994 e 2001 foi docente do Departamento de Engenharia Eletrotécnica e Computadores do Instituto Superior Técnico, onde lecionou as disciplinas de Introdução à Programação, Algoritmos e Estruturas de Dados, Compiladores e Projeto de Compiladores.


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.