O que você vai aprender
Justificar o uso de uma representação intermediária no compilador.
Ler, escrever e gerar código de três endereços (TAC).
Conhecer formas de implementar a RI (quádruplas, triplas) e tipos de RI.
Conectar a geração de RI à árvore sintática via SDT.
Por que não ir direto ao assembly?
Seria possível traduzir a árvore sintática diretamente para o assembly de cada máquina. Mas com m linguagens e n máquinas isso exigiria m×n tradutores. A representação intermediária (RI) quebra esse produto em uma soma.
Ao gerar primeiro uma forma neutra, independente de máquina, reaproveitamos otimizações e back-ends. É exatamente a estratégia do LLVM e a razão de a RI ser a peça central do compilador.
O meio do caminho
A RI fica entre a análise (front-end) e a síntese (back-end):
3 endereços→Otimização→Código-alvo
Tudo o que vem antes depende da linguagem; tudo o que vem depois depende da máquina. A RI é o ponto de encontro neutro.
O que é o código de três endereços
x = y op z, com no máximo um operador por instrução e até três operandos (dois de origem, um de destino). Resultados parciais ficam em variáveis temporárias (t1, t2, …).O nome vem dos três "endereços" envolvidos: dois operandos e um destino. É uma linguagem-assembly idealizada, sem as restrições de uma máquina real.
Por que decompor em passos mínimos
Cada instrução TAC faz uma operação só. Uma expressão complexa vira uma sequência linear de operações simples, com temporários ligando uma à outra.
- Facilita a otimização: operações isoladas são fáceis de reordenar, eliminar ou combinar.
- Facilita a geração de código: cada instrução TAC mapeia para poucas instruções de máquina.
- É independente de máquina: não assume número de registradores nem conjunto de instruções.
Traduzindo uma expressão
A expressão a + b * c respeita a precedência: multiplica antes de somar.
E (a + b) * c, com parênteses, inverte a ordem:
Gere TAC passo a passo
A RI como atributo na SDT
lugar (a variável/temporário que guarda seu valor) e cod (a sequência de instruções que o calcula).Os atributos lugar e cod
A geração de código intermediário é exatamente a gramática de atributos da aula 8 aplicada com um propósito concreto:
lugaré sintetizado: o nome do temporário onde o valor do nó ficou.codé sintetizado: a concatenação do código dos filhos mais a instrução do próprio nó.novoTemp()gera nomes frescos (t1, t2, …) para evitar colisões.
A receita decomposta
Quádruplas × triplas
O TAC pode ser representado internamente de formas diferentes:
| Quádrupla | Tripla | |
|---|---|---|
| Campos | op, arg1, arg2, resultado | op, arg1, arg2 (sem resultado nomeado) |
| Temporários | Nomeados explicitamente (t1, t2) | Referência ao número da tripla |
| Reordenar código | Fácil | Difícil (referências por posição) |
| Memória | Maior | Menor |
Da árvore à sequência linear
pós-ordem→Emite TAC
por nó→Sequência
linear
A árvore (hierárquica) vira uma lista (sequencial) de instruções. O percurso pós-ordem garante que os operandos de cada instrução já foram calculados quando ela é emitida.
Outros estilos de RI
O TAC linear não é a única RI possível. As principais famílias:
- RI gráfica: a própria árvore sintática abstrata (AST) ou o DAG (que compartilha subexpressões repetidas).
- RI linear: código de três endereços, bytecode de pilha (como o da JVM).
- SSA (Static Single Assignment): cada variável recebe valor uma única vez; facilita muitas otimizações modernas.
Verifique seu entendimento
Quantos operadores no máximo aparecem em uma instrução de três endereços?
Uma atribuição completa
Veja como x = a + b * 2 vira TAC, incluindo a atribuição final:
A última instrução copia o resultado para a variável de destino. Em uma versão otimizada, t2 poderia ser eliminado e escreveríamos x = a + t1 diretamente.
Tropeços ao gerar TAC
Reusar temporários cedo demais. Sobrescrever um temporário ainda necessário corrompe o cálculo. Use novoTemp() para nomes frescos.
Emitir a instrução do pai antes da dos filhos. O operando precisa existir antes de ser usado.
Achar que t1, t2 são registradores. São temporários lógicos; a alocação real vem só na geração de código.
Gerando TAC com segurança
Use um contador global para novoTemp(): nomes sempre frescos, sem colisão.
Concatene o cod dos filhos antes da instrução do pai.
Mantenha o TAC independente de máquina: nada de supor número de registradores nesta fase.
Pense antes de revelar
Por que a multiplicação aparece antes da soma no TAC de "a + b * c", se a soma está mais à esquerda no texto?
Revise os termos
Como esta aula se liga ao curso
- Usa a aula 5 (GLC): a precedência codificada na gramática determina a ordem das instruções geradas.
- Usa a aula 8 (atributos): gerar TAC é uma SDT com atributos lugar e cod.
- Usa a aula 2 (front/back-end): a RI é justamente o que desacopla as duas pontas.
- Prepara a aula 12 (código): o TAC é a entrada da geração de código de máquina.
O essencial da aula
lugar e cod percorrendo a árvore. A precedência da gramática define a ordem das instruções.Atividade em grupo · Tradutores humanos
Em trios, gerem TAC à mão e comparem com o simulador.
Roteiro
- Escolham 3 expressões de complexidade crescente (ex.: a*b+c, (a+b)*(c-d), a+b*c-d/e).
- Gerem o código de três endereços à mão, respeitando precedência.
- Confiram no simulador da aula.
- Discutam onde surgiram os temporários e por quê.
Mini-quiz · Aula 10
20 questões sobre esta aula. Escolha e veja a explicação na hora.
📌 Resumo — leve isto para a prova
- A RI desacopla front-end e back-end (m+n em vez de m×n) e habilita otimização reaproveitável.
- TAC: instruções x = y op z, um operador cada, com temporários.
- A geração de RI é uma SDT que sintetiza os atributos lugar e cod.
- Quádruplas, triplas, DAG e SSA são formas distintas de representar a RI.
- A precedência da gramática (via árvore) determina a ordem das instruções geradas.