Geração de Código Intermediário (IR) e a Portabilidade
- Descrição
- Currículo
- Revisões

A Geração de Código Intermediário (IR – Intermediate Representation) é um dos pilares mais importantes na arquitetura de compiladores modernos, funcionando como a ponte entre o front-end e o back-end.
No front-end, o compilador analisa o código-fonte por meio das etapas de análise léxica, sintática e semântica, construindo uma estrutura de alto nível que descreve o programa. Já no back-end, o compilador é responsável por aplicar otimizações e gerar o código final para a máquina alvo. O papel da IR é justamente desacoplar essas duas etapas, permitindo que qualquer linguagem de programação possa ser traduzida para uma mesma representação intermediária e, a partir dela, gerar código eficiente para múltiplas arquiteturas.
A IR serve, portanto, como uma linguagem interna do compilador, simples o bastante para ser analisada e otimizada, mas rica o suficiente para preservar toda a lógica do programa. Isso traz duas vantagens principais:
-
Portabilidade: Um compilador que utiliza uma IR agnóstica à máquina pode facilmente gerar executáveis para diferentes plataformas sem reescrever todo o processo de tradução. Esse é o princípio que permite, por exemplo, que o bytecode da JVM rode em qualquer sistema que tenha uma máquina virtual instalada, ou que o LLVM IR seja utilizado por várias linguagens modernas e compilado para diversas arquiteturas.
-
Otimização: A IR fornece uma camada onde os compiladores podem aplicar técnicas avançadas, como eliminação de código morto, propagação de constantes e otimizações de loops, antes mesmo da geração do código final. Isso garante que o executável resultante seja mais eficiente e confiável.
Diferentes tipos de IR foram desenvolvidos, cada um com características próprias:
-
O código de três endereços, simples e direto, muito próximo do assembly.
-
O bytecode da JVM, baseado em pilha, que tornou a linguagem Java altamente portável.
-
O LLVM IR, flexível e poderoso, que se tornou referência em compiladores modernos graças à sua capacidade de otimização e suporte a múltiplas linguagens.
Assim, compreender a IR significa entender como os compiladores transformam linguagens de alto nível em programas executáveis de forma eficiente, portável e otimizada, sendo um tema essencial para qualquer estudo sobre compiladores.
-
1Introdução: Geração de Código Intermediário
Neste primeiro módulo, vamos revisitar os fundamentos da compilação, explorando o que significa transformar um código escrito por humanos em instruções compreensíveis por máquinas. Além de relembrar a definição, você entenderá em detalhes como os compiladores são estruturados, quais são as principais fases do processo e como cada parte se conecta até chegar ao código final. Essa visão geral será essencial para compreender, nos próximos módulos, o papel da Representação Intermediária (IR) e sua importância no fluxo de tradução.
-
2A diferença entre compilador e interpretador
-
3O que é IR (Intermediate Representation)
Neste segundo módulo, vamos entender oque é a Representação Intermédia (IR) que atua como um “idioma neutro” dentro do compilador. Imagine que o front-end fala a língua do programador (C, Java, etc.) e o back-end fala a língua da máquina (assembly, código binário). A IR entra como tradutor no meio, garantindo que os dois lados se entendam sem esforço. Esse “idioma intermediário” traz grandes vantagens:
-
Evita a necessidade de construir dezenas de compiladores diferentes.
-
Permite que o compilador otimize o programa antes de gerar o código final.
-
Facilita tanto a análise do código-fonte quanto a adaptação para diferentes máquinas.
Sem a IR, seria como tentar erguer um prédio sem um projeto: confusão, retrabalho e maior risco de erros.
-
-
4AST para Codigo Intermediário
-
5📝 Quiz — Compilação e Representação Intermédia (IR) 📝
-
6SSA (Static Single Assignment)
Neste módulo vamos estudar duas representações intermediárias essenciais em compiladores: SSA (Static Single Assignment) e Código de Três Endereços (3AC). Você verá o que caracteriza a SSA (cada temporário tem uma única definição e o papel dos φ-nós), por que a SSA facilita otimizações e como ela é criada e revertida. Também abordaremos o 3AC sua forma linear, tipos de instruções e as estruturas de armazenamento (quádruplas, triplos, triplos indiretos) mostrando como essas representações se complementam no caminho do código-fonte ao código alvo.
-
7LLVM IR
Neste módulo vamos explorar o LLVM IR (Intermediate Representation), a linguagem intermediária da infraestrutura LLVM. Ele atua como a espinha dorsal do processo de compilação, servindo como formato unificado entre o front-end (linguagem-fonte) e o back-end (geração de código para máquinas-alvo). Por ser baseado em SSA (Static Single Assignment), o LLVM IR garante análises mais seguras e otimizações robustas, ao mesmo tempo em que oferece flexibilidade e portabilidade para diferentes linguagens e arquiteturas.
-
8LLVM-IR (assembly LLVM)
-
9Código de Três Endereços (Three-Address Code – 3AC)
Neste tópico vamos estudar o Código de Três Endereços (Three-Address Code – 3AC), uma forma intermediária de representação de programas utilizada em compiladores. O 3AC organiza o código em uma sequência linear de instruções simples, cada uma realizando no máximo uma operação, geralmente no formato "x = y op z", com até três endereços: dois operandos e um resultado. Essa abordagem permite simplificar expressões complexas, definir claramente a ordem de execução e facilitar processos de otimização e geração do código final. Os endereços podem ser variáveis do programa, constantes ou temporários criados pelo compilador. Além disso, o 3AC abrange diferentes tipos de instruções, como atribuições, cópias, operações unárias, desvios condicionais e incondicionais, manipulação de arrays e chamadas de procedimentos. Para sua implementação, pode ser representado em estruturas de dados como quádruplas, triplos ou triplos indiretos, cada uma com características próprias de organização e flexibilidade. Em resumo, o 3AC é uma ferramenta fundamental no processo de tradução e otimização de programas, por transformar construções complexas em instruções claras e manipuláveis.
-
10Código 3 endereços
-
11Bytecode
Neste tópico vamos estudar o Bytecode, uma forma intermediária para a qual programas Java são inicialmente compilados. Esse bytecode é executado pela Máquina Virtual Java (JVM), que atua como um interpretador de software, garantindo a grande portabilidade da linguagem: um programa compilado em uma máquina pode ser executado em qualquer outra que possua a JVM, inclusive através de redes. Apesar dessa vantagem, a interpretação do bytecode pode gerar perda de desempenho, já que o código não é executado diretamente pelo processador, mas traduzido em tempo de execução. Para reduzir essa sobrecarga, são utilizados compiladores Just-In-Time (JIT), que convertem dinamicamente partes do bytecode em código nativo, priorizando trechos mais utilizados do programa. Além disso, existem abordagens que traduzem diretamente o código-fonte em instruções de máquina, eliminando o uso do bytecode. Por fim, na comparação entre diferentes representações intermediárias (IRs), o bytecode da JVM se destaca pela alta portabilidade e compatibilidade multiplataforma, embora apresente limitações de otimização e dependa da VM para melhor desempenho, ao contrário de outras IRs como o 3AC ou o LLVM IR.
-
12GERANDO BYTECODE EM JAVA COM O COMPILADOR JAVAC
-
13📝 Quiz - Tipos de Representações Intermediárias 📝
-
14Código de Três Endereços (3AC)
O Código de Três Endereços (3AC) é uma representação intermediária amplamente usada em compiladores de linguagens imperativas (como C e Fortran). Ele decompõe expressões complexas em instruções simples de até um operador, utilizando variáveis temporárias para armazenar resultados intermediários.
-
15JVM Bytecode
O Bytecode da JVM é uma representação intermediária portátil e independente de arquitetura, criada inicialmente para Java, mas também utilizada em linguagens como C#. Ele é interpretado ou compilado em tempo de execução pela Máquina Virtual Java (JVM), permitindo que programas compilados em uma máquina rodem em qualquer outra com JVM instalada.
-
16LLVM IR
O LLVM IR é uma linguagem intermediária universal usada em compiladores modernos (C, C++, Rust, Swift, Julia, entre outras) que serve como base para otimização e geração de código para múltiplas arquiteturas de hardware.
-
17📝 Quiz - Estudos de Caso e exemplos de código 📝