Aula 03: Fases de um Compilador
Objetivos
Ao final desta aula, você será capaz de:
- Detalhar o pipeline de compilação e a entrada/saída de cada fase.
- Relacionar cada fase com decisões de projeto em Engenharia de Computação.
- Localizar no projeto Mini-Pascal onde cada fase é implementada.
Motivação e visão de alto nível
Enxergar o compilador como um pipeline de transformações bem definidas é fundamental para:
- Depurar: identificar em qual fase um erro se origina (token inválido, tipo não inferido, etc.).
- Otimizar: localizar gargalos (scanner, parser, otimizador).
- Estender: adicionar novos construtos sem quebrar o restante.
Em toolchains reais (GCC, Clang/LLVM, compiladores embarcados), a separação em fases permite combinar vários front-ends com um mesmo back-end, ou trocar apenas o gerador de código para uma nova arquitetura.
O pipeline em uma visão
O diagrama abaixo resume o fluxo de dados entre as fases. Cada caixa consome a saída da fase anterior e produz uma representação que a próxima fase consome.
flowchart LR
C[Caracteres] --> Lex
Lex[Léxico] --> Tok[Tokens]
Tok --> Pars[Parser]
Pars --> AST[AST]
AST --> Sem[Semântica]
Sem --> ASTd[AST decorada]
ASTd --> IR[Geração IR]
IR --> IRcode[IR]
IRcode --> Opt[Otimização]
Opt --> IRopt[IR otimizada]
IRopt --> Codegen[Codegen]
Codegen --> Asm[Assembly/Binário]
Conteúdo: as seis fases
1. Análise léxica (Scanner)
- Entrada: fluxo de caracteres (código fonte).
- Saída: fluxo de tokens (pares tipo + atributo opcional).
- Função: agrupar caracteres em lexemas e classificá-los; eliminar comentários e espaços em branco; eventualmente manter número de linha e coluna para mensagens de erro.
O scanner é a única fase que toca cada caractere da entrada; por isso precisa ser muito eficiente (bufferização, tabelas de transição, AFD).
2. Análise sintática (Parser)
- Entrada: fluxo de tokens.
- Saída: Árvore Sintática Abstrata (AST).
- Função: verificar se a sequência de tokens satisfaz a gramática da linguagem e construir a estrutura hierárquica do programa (expressões, comandos, declarações).
A AST é a estrutura de dados central sobre a qual as fases seguintes atuam. Erros sintáticos devem ser reportados com localização precisa (linha/coluna).
3. Análise semântica
- Entrada: AST.
- Saída: AST decorada (tipos, referências a símbolos) e tabela de símbolos preenchida.
- Função: verificar significado estático: declaração antes do uso, compatibilidade de tipos, escopo, unicidade de nomes onde aplicável.
Decisões de engenharia aqui incluem: representação de tipos (inteiros, floats, ponteiros, arrays), modelo de escopo (pilha de tabelas vs árvore de escopos), qualidade das mensagens de erro semântico.
4. Geração de código intermediário (IR)
- Entrada: AST decorada.
- Saída: Representação intermediária (ex.: código de três endereços, LLVM IR, bytecode abstrato).
- Função: traduzir a estrutura de árvore para uma sequência de instruções simples, independente de máquina, facilitando otimização e geração para múltiplos alvos.
A IR é a “língua franca” entre front-end e back-end: vários front-ends podem gerar a mesma IR e vários back-ends podem consumi-la.
5. Otimização
- Entrada: IR.
- Saída: IR otimizada (mesma linguagem, comportamento observável preservado).
- Função: melhorar desempenho, tamanho de código ou consumo de energia (eliminação de código morto, propagação de constantes, movimentação de código invariante, etc.).
Para Engenharia de Computação, esta fase é central em desempenho, energia (sistemas embarcados/mobile) e tamanho de firmware (memória limitada).
6. Geração de código alvo
- Entrada: IR otimizada.
- Saída: Assembly ou código de máquina (objeto).
- Função: mapear instruções da IR para o conjunto de instruções (ISA) da arquitetura alvo, alocando registradores físicos e respeitando convenções de chamada (ABI).
Aqui o conhecimento de Arquitetura de Computadores é essencial: número de registradores, modos de endereçamento, pipeline, hazards. O layout de ativação (stack frames) e as chamadas de função devem seguir a ABI da plataforma.
Visão front-end vs back-end
flowchart TB
subgraph front [Front-end]
L[Léxico]
P[Parser]
S[Semântica]
L --> P --> S
end
subgraph back [Back-end]
G[Geração IR]
O[Otimização]
C[Codegen alvo]
G --> O --> C
end
front --> AST2[AST]
AST2 --> back
- Front-end: léxico + sintático + semântico → AST decorada. Foco em correção e estrutura.
- Back-end: IR + otimização + codegen → código executável. Foco em desempenho e arquitetura.
Conexões com o projeto Mini-Pascal
| Fase | Onde no projeto |
|---|---|
| Léxico | Etapas 2 (manual) e 4 (ANTLR) |
| Sintático | Etapas 3 (manual) e 4 (ANTLR) |
| Semântica | Etapas 5 (escopo) e 6 (tipos) |
| IR / Codegen | Etapa 7 (bytecode JVM) |
Reconhecer claramente as entradas e saídas de cada fase facilita isolar bugs (por exemplo, inspecionar a AST antes da semântica ou a IR antes/depois da otimização).
Resumo
- O compilador é um pipeline de seis fases: léxico → sintático → semântico → geração de IR → otimização → geração de código alvo.
- A divisão em fases favorece modularidade, reuso (front/back-ends) e testabilidade.
- Front-end = análise (correção e estrutura); back-end = síntese (desempenho e arquitetura).
- O projeto Mini-Pascal replica esse pipeline em escala reduzida.
Perguntas de reflexão
- Quais fases você acredita que demandam mais poder computacional em um compilador industrial? Por quê?
- Ao portar um compilador de x86 para RISC-V, quais fases mudam e quais podem ser reaproveitadas?
- Que tipos de bugs são típicos de cada fase (léxico, sintático, semântico, otimização, codegen)?
Referências
- Aho et al. (2006). Compilers: Principles, Techniques, and Tools. Cap. 1.
- Cooper & Torczon (2011). Engineering a Compiler.
Materiais da aula
Última atualização: 08/03/2026