Buscar

Roteiro da apresentação do Geração do código intermediário

Prévia do material em texto

UEPB- Universidade Estadual de Campina Grande 
Campus I – Campina Grande/PB 
 
Discente: Franklin da Silva Basilio 
Docente: Cheyenne Ribeiro 
2020.1 
 
 
 
 
 
 
Compiladores: 
Geração de Código Intermediário 
 
 
 
 
 
Novembro de 2020 
Um compilador é idealizado se dividindo em front-end, back-end, onde no front se 
analisa o programa fonte e cria uma representação intermediária, onde os detalhes da 
linguagem fonte permanece aqui, enquanto no back, se concentra a geração do código 
objeto para a máquina alvo; desta forma uma codificação intermediária adequada 
poderá gerar várias saídas com os dados do front-end e back-end, onde desses dois 
podemos ter diversas soluções de compiladores: front x back soluções. 
 
 
 
 
No verificador estático, a etapa antes do gerador de código intermediário, ocorre a verificação 
de tipo, que garante que os operadores são aplicados a operandos compatíveis, e também inclui 
quaisquer verificações sintáticas que restarem após a análise sintática; 
Exemplo na linguagem C, verifica se o comando break está incorporado em comando while; 
 
Geração de Código Intermediário 
Na etapa do gerador de código intermediário, ocorre a transformação do código fonte para 
uma representação intermediária afim de que dependendo do destino final ela possa ser 
modificada para a máquina alvo. 
Nesta etapa não especifica detalhes de quais os registradores serão usados, quais endereços de 
memória serão referenciados, evitando assim deixar o código preso a uma arquitetura de uma 
máquina final. Com isso temos um código otimizado que auxilia na implementação de um 
compilador mais simples, atendendo o objetivo final, além de que esse código intermediário 
possa ser usado para diversas máquinas diferentes; 
Dependendo do destino final, a representação pode ser de baixo nível quando as tarefas 
dependam da máquina ou até de alto nível quando não dependerá da máquina que o código 
rodará, a exemplo de um programa escrito em Java, que rodará em uma máquina virtual java. 
Mas como desvantagem se torna um passo a mais para o compilador realizar, podendo levar um 
tempo extra e uso de poder computacional, ou seja, dependendo do objetivo pode ser um passo 
a ser pensado em ter ou deixar de ter, dependendo do destino. 
 
 
 
 
Analisador 
Sintático 
Verificador 
Estático 
Gerador de 
código 
intermediário 
 
Gerador 
de Código 
 
Código intermediário 
Front-end back-end 
Codificação de três endereços 
É construído a partir de dois conceitos: endereços e instruções; em termos da orientação por 
objetos, esses conceitos correspondem a classes, e os vários tipos de endereços e instruções 
correspondem a subclasses apropriadas. Como alternativa, o código de três endereços pode ser 
implementado usando -se registros com campos para os endereços; 
Neste tipo de codificação existe no máximo um operador do lado direito de uma instrução; ou 
seja, não haverá construção de expressão aritmética com vários operadores; 
Os endereços podem ser: 
• Um nome; 
• Uma constante; 
• Um temporário gerado pelo compilador- muito utilizado e aumenta a eficiência de um 
compilador otimizado; 
Dentre as instruções temos: 
• Rótulos simbólicos 
• Instrução de atribuição x= y op z; op aritmético binário ou lógico 
• Instrução x = op y; op unário 
• Instrução de cópia x = y 
• Desvio incondicional goto L; 
• Desvio condicionais da forma 
if x goto L ifFalse x goto L 
if x relop y goto L 
• Chamadas de procedimentos e retornos 
Param x – parâmetros 
Call p, n – chamada de procedimento 
Y = call p, n – chamada de função 
Return y – retorno 
• Instruções de indexadas de cópia: 
o X = y[i] – atribui a x o valor contido no endereço y[i] 
o X[i] = y – atribui o conteúdo do endereço x[i] a y 
• Atribuições de endereços e apontadores: 
o X = &y - x recebe o valor do endereço y; 
o X = *y - 
o *x = y - valor do objeto apontado por x com valor de y; 
 
Logo uma expressão do tipo, x + y * z deve ser particionada para se adequar ao sistema 
de instrução de 3 endereços, logo podemos fazer, que t1 = y*z --- t2 = x+t1 -- Neste 
caso em questão t1, e t2 foram gerados temporariamente pelo compilador para 
satisfazer o código de três endereços; a instrução básica, a avaliação de expressões 
aritméticas x=y operador z 
 
O nome advém dessa forma de instrução na qual ocorre a manipulação de até 3 
endereços na memória: x, y, z; 
 
Declarações de variáveis: 
T → B { 
T = b.type ; w = B.width; 
} 
B→ { 
B.type = float ; b.width = 8; 
} 
 
A escolha dos operadores permitidos determinam a qualidade da forma intermediária, quanto 
mais próximo das instruções da máquina facilitam a implementação da forma intermediária para 
a máquina alvo; quanto maior for as sequencias de instruções mas trabalho terá o gerador de 
código e o otimizador para reestruturar as instruções e gerar o código na máquina alvo; Para 
essa etapa ainda temos as variantes do código de três endereços, as quadruplas e as triplas. 
 
Exemplo: 
Código de três 
endereços 
Quádruplas Triplas 
t1 = minus c 
t2 = b* t1 
t3 = minus c 
t4 = b* t3 
t5 = t2 + t4 
a = t5 
 
op arg1 arg2 result 
minus C t1 
* b t1 t2 
minus c t3 
* b t3 t4 
+ t2 t4 t5 
= t5 a 
 
op arg1 arg2 
minus c 
* b (0) 
minus c 
* b (2) 
+ (1) (3) 
= a (4) 
 
 
 
➔ Como entrada para a etapa de geração de código intermediário temos a árvore sintática 
abstrata e a tabela de símbolos; 
➔ Como saída temos um código com representação mais próxima do código-alvo, quanto 
mais complexo for, mais demorado será a reestruturação do código na fase seguinte; 
Podendo ser em maior ou menor nível de abstração; utilizando ou não informações da máquina 
alvo; 
 
 
 
 
Tradução de Expressões 
Nesta etapa ocorre a mudança/tradução das instruções, a exemplo de expressões com mais de 
um operador, que será transformada em instruções de no máximo um operador por instrução; 
 
Dado o exemplo de E → E1 +E2 geram código para computar o valor de E a partir dos de E1, E2; 
Se E1 for calculado e armazenado no endereço. E1.addr e E2 calculado e armazenado no 
endereço E2.addr, logo 
E1+E2 é traduzido para um temporário, t, t = E1.addr + E2.addr e em seguida E.addr recebe o 
valor de t; 
T1,t2,t3... são criados sucessivamente a medida que for sendo necessário por new temp(); 
Para a tradução de expressões é utilizado algumas funções e instruções típicas do gerador do 
código intermediário, dentre elas citamos: 
• A notação gen(x ’=’ y + z ), representa uma instrução de três endereços do tipo x = y + z 
; onde x,y,z representam variáveis que são avaliada ao passarem por gen, que esta é 
uma função que monta uma instrução e a retorna; o que estiver entre apóstrofos, ‘’, 
será considerado literal. 
• A função top.get determina o endereço para o identificador, exemplo: E→ id – 
top.get(id.lexeme) 
• New Temp() é o comando para gerar nomes temporários, variáveis temporários a nível 
de compilador; 
• Code representa um conjunto de codificação em formato de três endereços; E.code 
B.code; 
• Addr representação de endereço utilizado por alguma variável; E.addr, B.addr; 
• Offset, obtém o próximo endereço relativo disponível; por padrão antes de utilizá-lo, é 
chamado e zerado; 
• Top.put() cria uma entrada na tabela de símbolos; exe: top.put(id.lexeme) 
 
Outro ponto é a tradução incremental ao invés de realizar uma única tradução, que já se torna 
resolvido por meio da codificação em três endereços que além de particionar instruções grande, 
ela permite que o valor de uma variável seja incrementado em outra variável temporária; 
Como exemplo imagine, 
 S → id = E 
Onde podemos fazer { 
gen ( top.get (id.lexeme) ‘=’ E.addr); 
} 
 
 
Ou então, E→ E1 + E2 
 { 
E.addr = new Temp(); 
 gen(E.addr ‘=’ E1.addr ‘+’ E2.addr); 
} 
E → - E1; 
{ 
 E.addr = new Temp(); 
 gen(E.addr ‘=’ ‘minus’ E1.addr); 
 } 
 
Campos em registrose classes: 
T → record ‘{‘ D ‘}’ 
Endereçamentos de elementos de arranjo: 
Os elementos de um arranjo podem ser acessados rapidamente se forem armazenados em um 
bloco de endereços consecutivos, onde o termo base é o endereço relativo de memória alocado 
para o arranjo, tipo A[0] 
Fluxo de Controle 
A tradução de comandos do tipo if-else e while está ligada a tradução das expressões booleanas, 
onde estas expressões são utilizadas para: 
• Alterar fluxo de controle; - são usadas como expressões condicionais. 
• Computar valores lógicos; - representando true ou false como valores, podendo ser 
usados em instruções de três endereços com operadores lógicos; 
Uma expressão após a palavra reservada IF é usada para alterar o fluxo de controle, 
enquanto outra, do lado direito de um comando de atribuição, é usada para denotar um 
valor lógico; 
 
O comando de IF, 
if ( x <100 || x > 200 && 
x!= y ) 
 X = 0; 
 
If x < 100 goto L2 
ifFalse x > 200 goto L1 
ifFalse x!= y goto L1 
L2: x = 0 
L1: 
while ( i < 10 * j ){ 
 a[ i ] = i + 2*j ; 
 ++ i; 
} 
_L1 _t1 := 10 * j 
 If i > = _t1 goto _L2 
 _t2 := 2 * j 
 _t3 := i + _t2 
 _t4 := 4 * i 
 a[_t4] := _t3 
 i := i+1 
 goto _L1 
 
_L2: ... 
 
Se formos por exemplo computar o fatorial de um número, teremos em codificação a seguinte 
apresentação; 
Exemplo de Fatorial com IF Três endereços 
read x; 
 if 0 < x then 
 fatorial := 1; 
 repeat 
 fatorial := fatorial * x; 
 x:= x-1; 
 until x = 0; 
 end_if 
 
read x 
 t1 = x > 0 
 if_false t1 goto L1 
 fatorial = 1 
 label L2 
 t2 = fatorial * x 
 fatorial = t2 
 t3 = x-1 
 x = t3 
 t4 = x == 0 
 if_false t4 goto L2 
 write L1 
 halt 
 
 
S → if B 
 S1 
 Else 
 S2 
B.true = newLabel () 
B.false = newLabel () 
S1.next = S2.next = S.next 
S.code = B.code 
 ||label ( B.true ) || S1.code 
 ||gen (‘goto’ S.next ) 
 ||label ( B.false ) || S2.code 
 
1 e 2 significa a ordem dos endereços dos 
comandos 
S→ while ( B ) 
 S1 
Begin = newLabel () 
B.true = newlabel () 
B.false = S.next 
S1.next = begin 
S.code = label ( begin ) || B.code 
 ||label(B.true) || S1.code 
 
 
 
| do i = i+1; 
| while (a[i] < v) 
 
 
 
 
| t1 = i+1 
| i = t1 
| t2 = i *8 
| t3 = a[t2] 
| if t3 < v goto L 
Bibliografia 
AHO, Alfred et al. Compiladores: Princípios, Técnicas e Ferramentas. 2a Edição 
PEARSON – Addison Wesley, 2007. 
RICARTE, Ivan. Introdução à compilação. ELSEVIER, 2008

Continue navegando