Aula 03: Fases de um Compilador

Data de Publicação

18/03/2026

Data de Modificação

08/03/2026

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

De volta ao topo