O que você vai aprender
Traduzir código de três endereços para instruções da máquina-alvo.
Explicar a alocação de registradores e o spilling.
Modelar a alocação de registradores como coloração de grafos.
Relacionar a geração de código à arquitetura do processador.
Da forma neutra à máquina real
O TAC é elegante e independente de máquina — mas o processador não o entende. A geração de código traduz cada instrução de três endereços para o assembly real do alvo, lidando com suas restrições: poucos registradores, conjunto fixo de instruções, modos de endereçamento.
É aqui que o compilador finalmente "aterrissa" na arquitetura, e decisões como quais valores ficam em registradores afetam diretamente o desempenho do programa.
O fim do pipeline
A geração de código fecha a jornada iniciada no scanner:
TAC→Seleção de
instruções→Alocação de
registradores→Código-alvo
assembly
Por depender da máquina, é a parte central do back-end: o mesmo TAC gera assembly diferente para x86, ARM ou RISC-V.
Seleção de instruções
Uma instrução de três endereços simples pode exigir várias instruções de máquina (carregar operandos, operar, guardar resultado), ou uma única instrução poderosa pode cobrir várias da RI.
De uma soma TAC ao assembly
A instrução TAC t = a + b tipicamente exige carregar os operandos da memória para registradores, somar e guardar:
Quatro instruções de máquina para uma de três endereços. Reduzir esse número (mantendo valores em registradores) é o papel da otimização e da alocação.
Gerando código para uma expressão
O TAC de (a + b) * c é t1 = a + b; t2 = t1 * c. Em assembly didático:
Com reuso esperto, bastaram dois registradores.
Traduza TAC passo a passo
Alocação de registradores
Registradores são a memória mais rápida do processador, mas existem em número pequeno (dezenas). Usá-los bem é decisivo para o desempenho.
Spilling: quando faltam registradores
O spilling tem custo: cada LOAD/STORE extra é lento comparado a operar direto em registrador. Um bom alocador minimiza spills, mantendo nos registradores os valores mais usados.
A bancada do marceneiro
RISC × CISC
| RISC | CISC | |
|---|---|---|
| Instruções | Poucas, simples, tamanho fixo | Muitas, complexas, tamanho variável |
| Acesso à memória | Só via load/store | Operações direto na memória |
| Registradores | Muitos | Menos |
| Exemplos | ARM, RISC-V, MIPS | x86 |
O estilo da arquitetura muda profundamente o código gerado: em RISC, tudo passa por registradores; em CISC, instruções acessam a memória diretamente.
Registradores e a hierarquia de memória
+ rápido→Cache L1/L2→RAM→Disco
+ lento
Quanto mais perto do processador, mais rápido e mais escasso. Manter valores em registradores (e evitar idas à memória) é a base da geração de código eficiente.
Alocação como coloração de grafos
Atribuir registradores vira colorir esse grafo com k cores (k = número de registradores), de modo que vizinhos tenham cores diferentes. Se não couber em k cores, algumas variáveis sofrem spilling.
Verifique seu entendimento
O que acontece quando há mais valores vivos do que registradores disponíveis?
Compilando com poucos registradores
Suponha o TAC t1 = a + b; t2 = c + d; t3 = t1 * t2 e apenas 2 registradores. As variáveis t1 e t2 ficam vivas simultaneamente até t3, mais os operandos — não cabem todas.
Tropeços na geração de código
Ignorar a escassez de registradores. Assumir registradores infinitos gera código que não roda na máquina real.
Spilling excessivo. Derramar valores muito usados degrada o desempenho.
Esquecer a arquitetura. Gerar código x86 supondo o modelo RISC (ou vice-versa) produz assembly inválido.
Gerando bom código de máquina
Use o grafo de interferência para alocar: vizinhos (vivos juntos) recebem registradores diferentes.
Quando o spilling for inevitável, derrame o valor menos usado dali em diante.
Conheça o alvo: número de registradores, RISC/CISC e modos de endereçamento moldam o melhor código.
Pense antes de revelar
Por que a alocação de registradores é modelada como coloração de grafos, e não como uma simples ordenação?
Revise os termos
Como esta aula se liga ao curso
- Consome a aula 10 (RI): o TAC é a entrada da geração de código.
- Usa a aula 11 (execução): chamadas e variáveis locais viram acessos ao frame na pilha.
- Realiza a aula 2 (back-end): é aqui que a dependência da máquina se concretiza.
- Conecta a Organização de Computadores: registradores, RISC/CISC e cache definem o código gerado.
O essencial da aula
Atividade em grupo · Compilando à mão
Em trios, traduzam TAC para um assembly didático.
Roteiro
- Gerem o TAC de (a + b) * c (usem o simulador da aula 10).
- Traduzam cada instrução de três endereços para LOAD/ADD/MUL/STORE.
- Contem quantos registradores foram necessários.
- Discutam o que aconteceria com apenas 2 registradores (spilling).
Mini-quiz · Aula 12
20 questões sobre esta aula. Escolha e veja a explicação na hora.
📌 Resumo — leve isto para a prova
- A geração de código mapeia o TAC para instruções do alvo (seleção de instruções).
- Registradores são rápidos e escassos; o que não cabe sofre spilling para a memória.
- A alocação de registradores é modelada como coloração de um grafo de interferência.
- O back-end depende da arquitetura: RISC/CISC, número de registradores e cache.
- Manter valores muito usados em registradores é a chave do desempenho.