Aula 18: Geração de Código Intermediário (IR)

Data de Publicação

25/03/2026

Data de Modificação

08/03/2026

Objetivos

Ao final desta aula, você será capaz de:

  • Justificar a necessidade de uma representação intermediária (IR) entre front-end e back-end.
  • Descrever código de três endereços (3AC) e suas vantagens para otimização e geração de código.
  • Representar instruções 3AC como quádruplas ou triplas e discutir trade-offs.
  • Relacionar a IR com o pipeline do compilador (AST → IR → otimização → código alvo) e com o projeto Mini-Pascal.

Motivação

Gerar assembly ou código de máquina diretamente a partir da AST é possível, mas:

  • Dificulta otimizações que dependem de uma forma linear e uniforme de instruções.
  • Acopla o front-end a uma arquitetura específica; trocar de alvo exige reescrever muita lógica.
  • Dificulta reuso: vários front-ends (linguagens) e vários back-ends (arquiteturas) não se conectam facilmente.

Uma IR atua como “língua franca”:

  • Vários front-ends geram a mesma IR (ou variantes compatíveis).
  • Otimizações são implementadas uma vez sobre a IR.
  • Vários back-ends consomem a IR e emitem código para diferentes ISAs.

Onde a IR se situa no pipeline

flowchart LR
  AST[AST decorada] --> GenIR[Geração de IR]
  GenIR --> IR[IR]
  IR --> Opt[Otimização]
  Opt --> IR2[IR otimizada]
  IR2 --> Codegen[Geração código alvo]
  Codegen --> Asm[Assembly/Binário]

A geração de IR traduz a AST (após análise semântica) para uma sequência de instruções em formato padronizado. Essa sequência é otimizada e, em seguida, o gerador de código alvo produz assembly ou objeto.

Conteúdo

1. Níveis de IR

As IRs podem ser classificadas por “distância” da linguagem fonte e da máquina:

Nível Características Exemplos
Alto Próximo da linguagem fonte; pode manter estruturas como arrays, objetos, chamadas de alto nível. AST, formas específicas de algumas ferramentas.
Médio Independente de linguagem e de máquina; instruções simples, fluxo de controle explícito. Código de três endereços (3AC), SSA, LLVM IR.
Baixo Próximo da máquina (registradores virtuais, instruções por operação), mas ainda independente de ISA específica. LLVM MachineIR, IR após seleção de instruções.

O código de três endereços é um representante clássico do nível médio: cada instrução tem no máximo um operador e (em geral) um destino explícito.

2. Código de três endereços (3AC)

Cada instrução tem a forma geral:

\[\texttt{destino} = \texttt{arg1}\ \mathit{op}\ \texttt{arg2}\]

ou variações com um operando (negação, cópia) ou sem destino (salto, chamada que não retorna valor). “Três endereços” refere-se aos dois operandos e ao resultado (cada um pode ser um nome temporário, variável ou constante).

Exemplo: expressão a = b + c * d em 3AC:

t1 = c * d
t2 = b + t1
a  = t2

As expressões complexas são “quebradas” em sequências de operações elementares. Isso:

  • Simplifica a geração de código: muitas instruções 3AC mapeiam 1:1 para instruções de máquina (load, store, add, mul, etc.).
  • Facilita otimizações: reordenação, eliminação de subexpressões comuns, alocação de registradores sobre uma lista linear de instruções é mais simples que sobre uma árvore.

Outras formas de instrução 3AC:

  • Atribuição unária: x = op y (ex.: negação, conversão).
  • Saltos: goto L, if x relop y goto L.
  • Chamadas: param p1; param p2; call f, n; x = return (ou convenção análoga).

3. Representação interna: quádruplas e triplas

As instruções 3AC precisam ser armazenadas em memória para otimização e geração de código.

Quádruplas: cada instrução é uma tupla \((\mathit{op}, \mathit{arg1}, \mathit{arg2}, \mathit{result})\):

  • Vantagem: o campo \(\mathit{result}\) é explícito; reordenar instruções (por exemplo, para otimização ou scheduling) não quebra referências, pois o resultado é nomeado.
  • Desvantagem: ocupa mais espaço (quatro campos).

Triplas: cada instrução é \((\mathit{op}, \mathit{arg1}, \mathit{arg2})\); o resultado é implícito: é a própria posição da instrução (índice na lista de triplas).

  • Vantagem: mais compacto.
  • Desvantagem: reordenar instruções altera os índices; referências a resultados de outras instruções (por índice) precisam ser atualizadas. Por isso triplas são menos convenientes quando há muitas transformações que reordenam o código.
flowchart LR
  subgraph quad [Quádruplas]
    Q1[op arg1 arg2 result]
  end
  subgraph tri [Triplas]
    T1[op arg1 arg2]
    T2[result = índice]
  end

4. Exemplo: trecho de programa em 3AC

Fragmento em linguagem de alto nível:

if (a < b)
  x = a + b;
else
  x = a - b;

Em 3AC (esquemático, com rótulos e saltos):

  if a < b goto L1
  goto L2
L1:
  t1 = a + b
  x = t1
  goto L3
L2:
  t2 = a - b
  x = t2
L3:
  ...

O fluxo de controle fica explícito (condicionais viram saltos); expressões aritméticas viram sequências de três endereços.

5. Uso no compilador

  • Geração: um visitor (ou passada) sobre a AST percorre nós de expressões, comandos e declarações e emite instruções 3AC (e rótulos para desvios). Variáveis e temporários são mapeados para nomes ou índices na IR.
  • Otimização: a IR é transformada (constant folding, eliminação de código morto, movimentação de código invariante, etc.) sem alterar o comportamento observável.
  • Codegen: o back-end mapeia cada instrução 3AC para uma ou mais instruções de máquina, aloca registradores para os operandos e o resultado, e resolve rótulos para endereços.

Conexões com o projeto Mini-Pascal

  • Na Etapa 7 você gera bytecode JVM a partir da AST. O bytecode JVM pode ser visto como uma forma de IR (instruções de pilha, bytecode por método). Os mesmos princípios aplicam: uma representação intermediária linear (lista de instruções) entre a AST e o arquivo .class.
  • Conceitos de 3AC (quebrar expressões em passos, nomear resultados temporários, fluxo de controle explícito) ajudam a organizar a geração de código mesmo quando o alvo é bytecode em vez de assembly.

Resumo

  • A IR desacopla front-end e back-end e permite otimizações uniformes.
  • Código de três endereços (3AC): cada instrução tem no máximo um operador e um destino; expressões são decompostas em sequências de operações.
  • Quádruplas (op, arg1, arg2, result) facilitam reordenação; triplas (op, arg1, arg2 com resultado implícito por índice) são mais compactas mas sensíveis a reordenação.
  • A IR é a entrada da otimização e da geração de código alvo.

Perguntas de reflexão

  • Por que uma IR em nível “médio” é preferível a gerar assembly diretamente da AST para um compilador que pretende suportar várias arquiteturas?
  • Em que situações triplas podem ser preferíveis a quádruplas, apesar da reordenação?

Referências

  • Aho et al. (2006). Compilers: Principles, Techniques, and Tools. Cap. 6 (tradução dirigida por sintaxe, código de três endereços).
  • Cooper & Torczon (2011). Engineering a Compiler.

Materiais da aula

Última atualização: 08/03/2026

De volta ao topo