Geração de Código Intermediário • Vantagens do uso de uma notação intermediária: – facilidade de retargetting: geração de código para vários processadores diferentes, reusando o máximo possível do código em todos os compiladores, mudando apenas o gerador de código final. – uso de otimizador único para vários geradores de código diferentes.
38
Embed
Geração de Código Intermediário Vantagens do uso de uma notação intermediária: –facilidade de retargetting: geração de código para vários processadores.
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Geração de Código Intermediário
• Vantagens do uso de uma notação intermediária:– facilidade de retargetting: geração de código
para vários processadores diferentes, reusando o máximo possível do código em todos os compiladores, mudando apenas o gerador de código final.
– uso de otimizador único para vários geradores de código diferentes.
Tradução dirigida por sintaxe
parserverificadorde código
código intermediário
geradorde código
intermediário
gerador de código
Código de Três Endereços
• Frequentemente usado como código intermediário, para linguagens imperativas/OO.
• Abstrai um assembler, onde cada instrução básica referencia 3 (ou menos) endereços (i.e. variáveis).
• Formato: x := y op z• Exemplo:
x + y * z
é reescrito para t1 := y * z
t2 := x + t1
Código de três endereços - exemplo
• t1 := -ct2 := b * t1
t3 := -ct4 := b * t3
t5 := t2 + t4
a := t5
*
assign
+
uminus
a
*
uminus
c
b
b
c
Tipos de código de três endereços
• Atribuição do tipo: x := y op z
• Atribuição do tipo: x := op y
• Cópia: x := y
• desvios incondicionais: goto L
• desvios condicionais: if x relop y goto L
• chamada de procedimentos: param x e call p,n e return y
Tipos de código de três endereços (cont.)
• atribuição indexada: x := y[i] e x[i] := y
• atribuição de endereços e ponteiros:x := &y, x := *y e *x := y
Tradução dirigida por sintaxe para código de três endereços
S id := E S.code := E.code || gen(id.place ‘:=‘ E.place)
• Declarações dentro de um procedimento frequentemente são tratadas como um único grupo, e são acessadas como um offset do ponto onde começam a ser armazenadas (na pilha).
• offset inicialmente é zero, e a cada novo símbolo declarado o valor do offset é guardado na tabela de símbolos, e o offset é incrementado.
• exemplo: procedimento enter(name,type,offset)
Declarações
P {offset := 0} D D D ; DD id : T { enter(id.name, T.type, offset);
T array [ num ] of T1 { T.type := array(num.val, T1.type); T.width := num.val * T1.width }
Declarações –tratando escopo
• uma possibilidade é manter tabelas de símbolos separadas para cada procedimento, com referências ao contexto mais externo (outra tabela de símbolos).
Declarações –tratando escopo
• procedimentos utilizados:– mktable(previous) – cria uma nova tabela de
símbolos, con referência à anterior– enter(table, name, type, offset) – insere um novo
símbolo na tabela– addwidth(table, width) – incrementa contador do
tamanho da tabela– enterproc(table,name,newtable) – insere um novo
símbolo (procedimento) na tabelae pilhas offset e tblptr
Declarações –tratando escopoP M D { addwidth(top(tblptr),top(offset));
pop(tblptr); pop(offset); }M {t := mktable(nil);
push(t,tblptr); push(0,offset);}
D D1 ; D2
D proc id ; N D1 ; S { t := top(tblptr); addwidth(t, top(offset)); pop(tblptr); pop(offset); enterproc(top(tblptr),id.name,t)}
D id : T { enter(top(tblptr),id.name,T.type,top(offset)); top(offset) := top(offset) + T.width }
N { t := mktable(top(tblptr)); push(t,tblptr); push (0,offset) }
Declarações –tratando registros
• nomes de campos de registros também podem ser guardados usando tabelas de símbolos específicas para cada (tipo) registro.
T record L D end { T.type := record(top(tblptr)); T.width := top(offset); pop(tblptr); pop(offset); }
L { t := mktable(nil); push(t,tblptr); push (0,offset) }
Revendo Atribuições
S id := E { p := lookup(id.name); if p != nil then emit (p ‘:=‘ E.place) else error }
• Podem ser avaliadas de duas formas:– codificar true e false como números, e avaliar as
expressões da mesma forma que expressões matemáticas.
– usar o fluxo de controle, i.e. representar um valor por uma posição atingida no programa. Usada em if-then-else, while-do etc. Permite “curto-circuito” de expressões: se E1 é avaliado como true, na expressão E1 or E2, não é necessário avaliar E2.
Representação numérica - exemplo
• a or b and not c
• t1 := not ct2 := b and t1
t3 := a or t2
Representação numérica - exemplo
• a < b é traduzido paraif a < b then 1 else 0
• 100: if a < b goto 103101: t := 0102: goto 104103: t := 1104:
Esquema de Tradução
E E1 or E2 { E.place := newtemp; emit (E.place ‘:=‘ E1.place ‘or’ E2.place) }