Aula 18: Geração de Código Intermediário (IR)
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