Geração de Código
Intermediário
“Nação se levantará contra nação e reino contra reino. Haverá terremotos em vários lugares e também
fomes. Essas coisas são o início das dores [...]. O irmão trairá seu próprio irmão, entregando-o à morte, e o mesmo fará o pai a seu filho.”
Etapas da Compilação
Análise Léxica Análise Sintática Analise Semântica Geração de Código Intermediário Geração de Código Final Front-End (Análise) Back-End (Síntese)Sumário da Aula
Introdução
Código de Três Endereços Traduzindo Expressões
Geração de Código
Intermediário
O objetivo dessa etapa é gerar um código
intermediário equivalente ao código fonte dado
como entrada para o compilador
Entrada: árvore sintática anotada Saída: código intermediário
Código Intermediário
O nome “intermediário” indica:
Que ele não é tão abstrato quanto o código fonte, nem tão dependente de máquina quanto o código alvo final
Que ele é gerado em uma etapa intermediária da compilação (antes do código-alvo)
Um código intermediário deve ser simples e
independente de máquina
Objetivo
É simples para:
Ser fácil de traduzir para código de máquina
É independente de máquina para:
Deixar apenas o back-end dependente de máquina
Permitir gerar código para várias máquinas diferentes (a partir de uma mesma linguagem fonte)
Código Intermediário
Existem diversos tipos
A escolha depende da linguagem fonte e, até
mesmo, da(s) linguagem(ns) alvo
Um desenvolvedor pode criar um novo tipo de
Código Intermediário
O livro afirma que a árvore sintática (já vista)
pode ser entendida como um tipo de código intermediário
Código Intermediário
Veremos aqui um tipo mais interessante de
código intermediário – o código de três
endereços
Mais fácil de traduzir para código final, como códigos assembly de x86 ou de MIPS, por exemplo
Código de Três Endereços
Programas representados como uma simples
lista de instruções
Muitas instruções são similares aos comandos de linguagens imperativas
Porém são tão simples que lembram as instruções de vários códigos assembly (e.g., MIPS)
As instruções usam, no máximo, três operandos
Tipos de Endereços
Um “endereço” pode ser:
Um nome – representando uma variável, função, etc.
Uma constante – valor literal de um número, uma string, um caractere, etc.
Um temporário – variável auxiliar criada pelo compilador (t1, t2, etc.)
Tipos de Instruções
Instruções de atribuição da forma
Onde op é um operador binário aritmético,
binário ou lógico
Exemplo:
x = y op z
Tipos de Instruções
Instruções de atribuição da forma
Onde op é um operador unário, como: negação
lógica, menos, conversão de tipo
Exemplos:
x = op y
t1 = (int) temp; t1 = - temp;
Tipos de Instruções
Instruções de cópia
Numa fase posterior, de otimização, essa
instrução pode ser removida
Tipos de Instruções
Desvio incondicional
Desvia para o rótulo L : faz com que a próxima
instrução a ser executada seja a que tem o rótulo L Exemplo: goto L label2: aux = t + 1; ... goto label2
Tipos de Instruções
Desvios condicionais
Desviam para o rótulo L dependendo do valor
booleano de x
if x goto L
Tipos de Instruções
Desvios condicionais com operadores
relacionais (<, >, ==, !=, ...)
Desvia para L se o resultado da operação
relacional for verdadeiro
Exemplo:
if x relop y goto L
label2:
aux = t + 1;
Tipos de Instruções
Chamada de procedimento
Chama o procedimento Proc
Tipos de Instruções
Preparação de parâmetros
Definem os valores que serão passados em
uma chamada de procedimento
O exemplo dado representa: Proc(x1, x2, ...) param x1
param x2 ...
Tipos de Instruções
Retorno de valor
Usada dentro de um procedimento para retornar
o valor y
Tipos de Instruções
Atribuições de endereços e ponteiros
Acessam, respectivamente:
O endereço de memória da variável y
O valor que está no endereço que y guarda O valor que está no endereço que x guarda
x = &y x = *y *x = y
Traduzindo Expressões
Uma expressão com várias operações...
...é decomposta em expressões menores, com
uma operação cada
myVar = aux + temp * 3
t1 = 3
t2 = temp * t1 t3 = aux + t2 myVar = t3
Traduzindo Expressões
A árvore sintática, na verdade, já guarda as
expressões decompostas da forma desejada
O resultado de cada sub-expressão pode ser
armazenado em alguma variável
Pode ser necessário criar uma variável temporária (criar função nextTempVar())
Traduzindo Expressões
A regra geral para traduzir um nó que
representa uma operação binária:
1. Gerar o código das duas sub-expressões
2. Pegar os nomes das variáveis que guardam o resultado de cada sub-expressão
3. Criar uma instrução para executar a operação sobre essas duas variáveis
Exemplo
Curto-Circuito
Em expressões lógicas com operadores &&
(and) e || (or), é possível abreviar a avaliação, em alguns casos
Lembrando que:
Basta uma expressão ser falsa para o && delas dar falso
Basta uma expressão ser verdadeira para o || delas dar verdadeiro
Curto-Circuito
Em uma condição (expr1 && expr2)
Se expr1 for falsa, nem precisa avaliar expr2
Em uma condição (expr1 || expr2)
Curto-Circuito
Avaliação de expressões lógicas com
curto-circuito é quando a avaliação dos seus
operandos é interrompida nos casos em que já é possível antecipar o valor dela
Usada em muitas linguagens
C/C++ Java Python
Traduzindo Instruções
Veremos como fazer a tradução de alguns
comandos mais representativos
Atribuição If-Else
While
Referências
Atribuição
Primeiro, deve-se gerar código para a expressão
do lado direito
Retorna o nome da variável que guardará o resultado da expressão
Por fim, basta fazer uma instrução de cópia d a
variável retornada para a variável do lado esquerdo da atribuição
If-Else
Criar dois labels (rótulos) únicos*
LElse: para o comando do else
LDepois: para a próxima instrução, após o if-else
* - crie um procedimento nextLabel() para retornar strings únicas
If-Else
A primeira coisa a fazer é gerar o código da
expressão de teste
Retorna o nome de uma variável
Depois, deve-se gerar um comando de desvio
condicional ifFalse-goto aplicado à variável e ao label LElse
If-Else
Depois, deve-se gerar o código do comando
dentro do if
Lembrando que ele só vai ser atingido quando a expressão for verdadeira
Depois, gerar um desvio incondicional goto para
o label LDepois
If-Else
A seguir, deve-se escrever o label LElse
Marca o início do código do else
Gerar o código do comando dentro do else E, por fim, escrever o label LResto
If-Else
<código que avalia a expressão de teste aqui>
TN = <resultado da expressão>
ifFalse TN goto labelElse <código do bloco do “if”> goto labelResto
labeElse:
<código do bloco do “else”> labelResto:
If-Else
<código que avalia a expressão de teste aqui>
TN = <resultado da expressão>
ifFalse TN goto labelElse <código do bloco do “if”>
goto labelResto labeElse:
<código do bloco do “else”>
While
Criar dois labels únicos
LInicio: para o início do while
LResto: para o próximo comando, após o while
Naturalmente, deve-se começar escrevendo o
While
Depois, gerar o código da expressão de teste
E receber o nome da variável que guarda o resultado da expressão...
Depois, gerar um desvio condicional
ifFalse-goto aplicado à variável e ao label LResto
Quando a expressão não for verdade, passa para o primeiro comando após o while
While
Em seguida, deve-se gerar o código do
comando interno do while
Depois, gerar uma instrução de desvio
incondicional goto para o label LInicio
Para avaliar a expressão novamente e decidir se entra no laço novamente ou termina
Traduzindo Instruções
Mostrei UMA possível maneira de gerar código
para cada um desses comandos
A maneira vista é relativamente fácil de
implementar, mas gera código ineficiente
A fase de otimização (que não veremos), poderia compensar isso...
Usem esses exemplos como base para