Compiladores
Geração de
código intermediário (2)
Geração de TAC para
as principais construções
• Geração de código de três endereços:
– Expressões
– Declarações (escopo simples)
– Declarações (escopos aninhados)
– Comandos de atribuição
– Arrays e Registros
– Expressões booleanas
– Comandos de decisão
– Comandos de iteração
tradução em código TAC de
expressões
S
→
id := E
E
→
E
1+ E
2E
→
E
1* E
2E
→
(E
1)
E
→
id
T1 := Y * Z
T2 := X + T1
A := T2
Exemplo:
A := X + Y * Z
Usa-se dois mecanismos: 1. Atributos sintetizados:
• O atributo ‘local’ armazena o nome da variável • O atributo ‘codigo’ armazena o código TAC sintetizado 2. Funções auxiliares:
• A função “geracod” escreve (na tela) código
• A função “novo_tmp” retorna um nome de temporário tmp1, tmp2...
Expressões (cont.)
(“ || “ significa concatenação)
S
→id :=E
{S.codigo = E.codigo ||
geracod(id.local”:=“ E.local)}
E
→E
1+ E
2{E.local = novo_tmp();
E.codigo = E
1.codigo || E
2.codigo ||
geracod (E.local “:=“ E
1.local “+” E
2.local) ; }
E
→E
1* E
2{E.local = novo_tmp();
E.codigo = E
1.codigo || E
2.codigo ||
geracod (E.local “:=“ E
1.local “*” E
2.local) ; }
E
→(E
1)
{E.local = E
1.local; E.codigo = E
1.codigo }
E
→id
{E.local = id.local; E.codigo = “ ”}
Árvore de derivação anotada
S id := E E + E E * E id id id cod = “ “ local=A cod = “ “ local=X cod = “ “ local=Y cod = “ “ local=Z cod = T1:=Y*Z || T2:=X+ T1 local=T2 cod = “ “ local=X cod = “ “ local=Y cod = “ “ local=Z cod = T1:=Y*Z local=T1
A := X + Y * Z
cod = T1:=Y*Z || T2:=X+ T1 || A := T2Reaproveitamento de temporários
•
Deve-se poupar os temporários
•
Usa-se um contador c
– Valor inicial: c = 0– Quando se gera um novo temporário: 1. Usa-se o valor atual c;
2. Incrementa c de 1 unidade: c++. • (Ou seja: “tmp_c++ := alguma_coisa”)
– Cada vez que se usa um temporário como operando, diminui-se c de uma unidade.
•
Exemplo: X := A*B + C*D – E*F
– Necessita apenas 2 temporários:C =0 tmp0 := A*B C=1 tmp1 := C*D C=2 tmp0 := tmp0 + tmp1 C=1 tmp1 := E*F C=2 tmp0 := tmp0 – tmp1 C=1 X := tmp0 C=0
TAC
• Geração de código de três endereços:
– Expressões
– Declarações (escopo simples)
– Declarações (escopos aninhados)
– Comandos de atribuição
– Arrays e Registros
– Expressões booleanas
– Comandos de decisão
– Comandos de iteração
Parsing de declarações
• Em linguagens como C, Pascal e Fortran, variáveis de
um mesmo procedimento pertencem a um mesmo grupo
– Associa posições de memória a nomes locais de procedimentos – Calcula um deslocamento• Antes da primeira declaração zera o deslocamento
• Quando o parser analisa declarações, ele:
– atualiza na tabela de símbolos o endereço de memória relativo: • à base da área estática de dados para declarações estáticas
(globais)
• À base da pilha para dados locais no registro de ativação
• A geração de endereços pode ter máquina alvo em
mente:
– Leva em consideração o tamanho da palavra, necessidade de alinhamento, etc...
– ex. inteiros consecutivos diferem de 4 bytes
Parsing de declarações
• Usar tradução dirigida pela sintaxe:
– atributos:
• Nome (lexema associado a um identificador) • Tipo (expressão de tipo)
• Size (tamanho de representação do tipo) • deslocamento
• Procedimento auxiliar:
– insert(nome, tipo, deslocamento):
• cria uma nova entrada na tabela de símbolos para nome, e lhe confere o tipo e o endereço relativo deslocamento
• Expressões de tipos:
– Tipos básicos (int, real, ...)
– array(num, tipo)
– ponteiro(tipo)
Parsing de declarações
• Tamanhos de alguns tipos:
– inteiro: 4 bytes
– real: 8 bytes
– array: número de elementos x tamanho do tipo
– ponteiro: 4 bytes
• Gramática típica a tratar:
P →→→→MD M →→ εεεε→→ D →→→→D ; D D →→→→id : T T →→→→int T →→→→real T →→→→array [num] of T1 T →→→→↑T1
Esquema de tradução
P →MD M → ε {desloc = 0; } D →D ; DD →id : T { insert (id.nome, T.tipo, desloc); desloc = desloc + T.size} T →int {T.tipo = int; T.size = 4; } T →real {T.tipo = real; T.size = 8; } T →array [num] of T1 {T.tipo = array(num.val, T1.tipo);
T.size = num.size* T1.size; } T →↑T1 {T.tipo = ponteiro(T1.tipo); T.size = 4; }
Árvore de derivação - a: int; b: real;
P D M ε ; {desloc =0} {insert(a,int,0); desloc:=desloc+4} {adSimb(b,real,4); desloc:=desloc+8} tipo=real tam=8 D id : int T {tipo=int tam=4} nome=a D T : real id nome=b
TAC
• Geração de código de três endereços:
– Expressões
– Declarações (escopo simples)
– Declarações (escopos aninhados)
– Comandos de atribuição
– Arrays e Registros
– Expressões booleanas
– Comandos de decisão
– Comandos de iteração
Controlando Escopo
• Cada procedimento possui um escopo onde
endereços relativos devem ser criados
• Usar uma tabela de símbolos para cada escopo
P →
MD
M → ε
D →
D; D
D →
id : T
D
→
proc id; ND; S
N
→ ε
Ações semânticas
• geratab(anterior):
– cria uma tabela de símbolos retornando um apontador para a mesma. “anterior” é um ponteiro para a tabela criada anteriormente
• insert(tabela, nome, tipo, deslocamento)
– cria uma nova entrada para nome na tabela de símbolos
• deftam(tabela, largura):
– registra a largura acumulada de todas as entradas de tabela no cabeçalho associado a tabela de símbolos
• insert_proc(tabela, nome, tipo, ptr_tab)
– cria uma nova entrada para o nome de procedimento nomena tabela de símbolos. Ptr_tab aponta para a tabela associada a nome.
Estruturas auxiliares
• Pilhas de tabela de símbolos (tabPtr)
• Pilhas de deslocamentos (desloc)
• Métodos:
– Push(t, desloc), push(t, tabPtr)
– Pop(desloc), pop(tabPtr)
– Top(desloc), top(tabPtr)
Exemplo: esquema de tradução para
gerar uma árvore de tabelas de símbolos
P
→
MD
M
→ ε
D
→
D; D
D
→
id : T
D
→
proc id; ND; S
N
→ ε
T
→
int
T
→
real
T
→
array [num] of T
1T
→
^T
1Exemplo: esquema de tradução para
gerar uma árvore de tabelas de símbolos
P →
MD
M → ε
{t=geraTab(nil);
push(t,tabPtr);
push(0,desloc)}
D →
D; D
D →
id : T
D →
proc id; ND; S
Exemplo: esquema de tradução para
gerar uma árvore de tabelas de símbolos
P
→
MD
{defTam(top(tabPtr),top(desloc));
pop(tabPtr);
pop(desloc)}
M
→ ε
D
→
D; D
D
→
id : T
D
→
proc id; ND; S
Exemplo: esquema de tradução para
gerar uma árvore de tabelas de símbolos
P
→
MD
{defTam(top(tabPtr),top(desloc)); pop(tabPtr); pop(desloc)}M
→ ε
{t=geraTab(nil); push(t,tabPtr); push(0,desloc)}D
→
D; D
D
→
id : T
{insert (top(tabPtr),id.nome, T.tipo, top(desloc)); top(desloc) = top(desloc) + T.tam}D
→
proc id; ND; S
Exemplo: esquema de tradução para
gerar uma árvore de tabelas de símbolos
P →MD {defTam(top(tabPtr),top(desloc)); pop(tabPtr); pop(desloc)} M → ε {t=geraTab(nil); push(t,tabPtr); push(0,desloc)} D →D; D
D →id : T {insert (top(tabPtr),id.nome, T.tipo, top(desloc)); top(desloc) =top(desloc) + T.tam}
D →proc id; ND; S {t=top(tabPtr); defTam(t, top(desloc)); pop(tabPtr); pop(desloc); insert(top(tabPtr),id.nome, t)}
Exemplo: esquema de tradução para
gerar uma árvore de tabelas de símbolos
N
→ ε
T
→
int
T
→
real
T
→
array [num] of T
1T
→
^T
1Exemplo: esquema de tradução para
gerar uma árvore de tabelas de símbolos
N → ε {t=geraTab(top(tabPtr));
push(t,tabPtr); push(0,desloc)}
T →int {T.tipo = int; T.tam = 4}
T →real {T.tipo = real; T.tam = 8}
T →array [num] of T1 {T.tipo = arranjo(num.val, T1.tipo); T.tam = num.val* T1.tam}
T →^T1 {T.tipo = ponteiro(T1.tipo);
T.tam = 4} nil
Exemplo
a, real, 0
a: real; b: int; proc p1; c: real; ---end p1; proc p2; d: array[5] of int; proc p3; e,f: real; ---end p3; ---end p2;---nil
Exemplo
a, real, 0
b, int, 8
a: real; b: int; proc p1; c: real; ---end p1; proc p2; d: array[5] of int; proc p3; e,f: real; ---end p3; ---end p2; ---nilExemplo
a, real, 0
b, int, 8
p1,
a: real; b: int; proc p1; c: real; ---end p1; proc p2; d: array[5] of int; proc p3; e,f: real; ---end p3; ---end p2; ---8c, real, 0
nilExemplo
a: real; b: int; proc p1; c: real; ---end p1; proc p2; d: array[5] of int; proc p3; e,f: real; ---end p3; ---end p2; ---8c, real, 0
a, real, 0
b, int, 8
p1,
p2,
d, array(5,int), 0
p3,
nilExemplo
a: real; b: int; proc p1; c: real; ---end p1; proc p2; d: array[5] of int; proc p3; e,f: real; ---end p3; ---end p2; ---8c, real, 0
a, real, 0
b, int, 8
p1,
p2,
d, array(5,int), 0
p3,
16e, real, 0
f, real, 8
nilExemplo
a: real; b: int; proc p1; c: real; ---end p1; proc p2; d: array[5] of int; proc p3; e,f: real; ---end p3; ---end p2; ---8c, real, 0
a, real, 0
b, int, 8
p1,
p2,
24d, array(5,int), 0
p3,
16e, real, 0
f, real, 8
nil 20Exemplo
a: real; b: int; proc p1; c: real; ---end p1; proc p2; d: array[5] of int; proc p3; e,f: real; ---end p3; ---end p2; ---8c, real, 0
a, real, 0
b, int, 8
p1,
p2,
24d, array(5,int), 0
p3,
16e, real, 0
f, real, 8
TAC
• Geração de código de três endereços:
– Expressões
– Declarações (escopo simples)
– Declarações (escopos aninhados)
– Comandos de atribuição
– Arrays e Registros
– Expressões booleanas
– Comandos de decisão
– Comandos de iteração
Comandos de atribuição
S →id := E { p = lookup(id.nome);if p <> NULL thenS.codigo = E.codigo || geracod (p “:=“ E.local) else erro}
E →E1+E2 {E.local = geratemp;
E.codigo = E1.codigo || E2.codigo || geracod (E.local “:=“ E1.local “+” E2.local) }
E →E1*E2 {E.local = geratemp;
E.codigo = E1.codigo || E2.codigo || geracod (E.local “:=“ E1.local “*” E2.local) }
E →-E1 {E.local = geratemp;
E.codigo = E1.codigo || geracod (E.local “:=“ “-u” E1.local) } E →E1 {E.local = E1.local;}
E →id { p=lookup(id.nome);