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
parser verificador de código código intermediário gerador de código intermediário gerador de código
front-end
back-end
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
• t
1:= -c
t
2:= b * t
1t
3:= -c
t
4:= b * t
3t
5:= t
2+ t
4a := t
5 * assign + uminus a * uminus c b b ca = b * -c + 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 goto L e ifFalse x
goto L
• desvios condicionais: if x relop y goto L
Tipos de código de três endereços
(cont.)
• chamada de procedimentos:
param x, call p, n e return y
• atribuição indexada: x = y[i] e x[i] = y
• atribuição de endereços e ponteiros:
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)
E E1+ E2 E.place := newtemp;
E.code := E1.code || E2.code ||
gen(E.place ‘=‘ E1.place ‘+’ E2.place) E - E1 E.place := newtemp;
E.code := E1.code ||
gen(E.place ‘=‘ ‘uminus’ E1.place) E ( E1) E.place := E1.place; E.code := E1.code E id E.place := id.place; E.code := ‘’
Tradução do while
S
while (E) S
1 Regra Semântica: S.begin := newlabel; S.end := newlabel; S.code := gen(S.begin ‘:’) || E.code ||gen(‘if’ E.place ‘=‘ ‘0’ ‘goto’ S.end) || S1.code ||
gen(‘goto’ S.begin) || gen(S.end ‘: ‘)
Declarações
• 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 ; D
D id : T { enter(id.name, T.type, offset); offset := offset + T.width } T integer { T.type := integer;
T.width := 4; } T real { T.type := real;
T.width := 8; } 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ímbolosseparadas 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, com 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 tabela e pilhas offset e tblptr
Declarações –tratando escopo
P 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) gen(p ‘=‘ E.place) else error }
E E1+ E2 { E.place = newtemp;
gen(E.place ‘=‘ E1.place ‘+’ E2.place) } E - E1 { E.place = newtemp;
gen(E.place ‘=‘ ‘uminus’ E1.place) } E ( E1) { E.place = E1.place }
E id { p = lookup(id.name);
if (p != nil) E.place = p else error }
Reuso de nomes
• Controlando o tempo de vida dos nomes usados dentro de cada statement ou expressão, é possível fazer o reuso de nomes de variáveis temporárias.
• Exemplo: usar contador incrementado para cada novo temporário, e decrementado para cada uso. • evaluate E1to t1
evaluate E2to t2 t := t1+ t2
Reuso de nomes - exemplo
• x = ((a * b) + (c * d)) – (e * f)
• t0 = a * b
t1 = c * d
t0 = t0 + t1
t1 = e * f
t0 = t0 - t1
x = t0
Acesso a Arrays
• controlado de acordo com o tamanho e o tipo do array: base + (i – low) * w
• ou melhor ainda: i * w + (base – low * w) • existem fórmulas genéricas para arrays
multidimensionais:
– 2D: A[i1 , i2]
base + ((i1– low1) * n2+ i2– low2) * w • row-major vs. column-major
Expressões Booleanas
• 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
E1or E2, não é necessário avaliar E2.
Representação numérica
-exemplo
• a or b and not c
• t
1= not c
t
2= b and t
1t
3= a or t
2Representação numérica
-exemplo
• a < b
é traduzido para
if (a < b) 1 else 0
• 100: if a < b goto 103 101: t = 0 102: goto 104 103: t = 1 104:Esquema de Tradução
E E1or E2 { E.place = newtemp;gen(E.place ‘=‘ E1.place ‘or’ E2.place) }
E id1relop id2
{ E.place = newtemp;
gen(‘if’ id1.place relop.op id2.place ‘goto’ nextstat + 3);
gen(E.place ‘=‘ ‘0’); gen(‘goto’ nextstat + 2); gen(E.place ‘=‘ ‘1’) } E ( E1) { E.place = E1.place }
E true { E.place = newtemp; gen(E.place ‘=‘ ‘1’) }
Representação numérica
-exemplo
100: if a < b goto 103 101: t1 = 0 102: goto 104 103: t1 = 1 104: if c < d goto 107 105: t2 = 0 106: goto 108 107: t2 = 1 108: if e < f goto 111 109: t3 = 0 110: goto 112 111: t3 = 1 112: t4 = t2 and t3 113: t5 = t1 or t4a < b or c < d and e < f
Fluxo de controle
• Associamos a cada expressão booleana
desvios para labels caso a expressão seja
avaliada para true ou false.
Tradução do if-then
S
if (E) S
1Regra Semântica:
E.true = newlabel;
E.false = S.next;
S
1.next = S.next;
S.code = E.code || gen(E.true ‘:’) || S
1.code
Tradução do if-then-else
S
if (E) S
1else S
2Regra Semântica:
E.true = newlabel; E.false = newlabel;
S
1.next = S.next; S
2.next = S.next;
S.code = E.code ||
gen(E.true ‘:’) || S
1.code
gen(‘goto’ S.next);
gen(E.false ‘:’) || S
2.code
atributos herdadosTradução do while
S
while (E) S
1Regra Semântica:
S.begin = newlabel; E.true = newlabel;
E.false = S.next; S
1.next = S.begin;
S.code = gen(S.begin ‘:’) || E.code ||
gen(E.true ‘:’) || S
1.code ||
gen(‘goto’ S.begin)
Fluxo de controle
• a < b traduzido para:
if a < b goto E.true
goto E.false
Esquema de Tradução
E E1or E2 { E1.true := E.true; E1.false := newlabel;
E2.true := E.true; E2.false := E.false; E.code := E1.code ||
gen(E1.false ‘:’) || E2.code) } E E1and E2 {E1.true := newlabel; E1.false := E.false;
E2.true := E.true; E2.false := E.false; E.code := E1.code ||
gen(E1.true ‘:’) || E2.code) }
Esquema de Tradução
E not E1 { E1.true := E.false; E1.false := E.true;
E.code := E1.code} E id1relop id2
{E.code := gen(‘if’ id1.place relop.op id2.place ‘goto’ E.true ||
gen(‘goto’ E.false) } E true {E.code := gen(‘goto’ E.true } E false {E.code := gen(‘goto’ E.false }
Exemplo 1
• a < b or c < d and e < f • if a < b goto Ltrue goto L1 L1: if c < d goto L2 goto Lfalse L2: if e < f goto Ltrue goto LFalse• é possível otimizar o código acima.
Exemplo 2
• while a < b do if c < d then x := y + z else x := y – z • L1: if a < b goto L2 goto Lnext L2: if c < d goto L3 goto L4 L3: t1 := y + z x := t1 goto L1 L4: t2 := y – z x := t2 goto L1switch/case
• switch expression begincase value: statement case value: statement …
case value: statement default: statement end
implementação de switch/case
• sequência de goto’s condicionais, um para cada caso. • loop percorrendo uma tabela de valores e labels para
o código do statement correspodente. • hash table com valores e labels
• criar um array de labels, com o label para o statement j na posição da tabela índice j.
tradução de switch/case
• switch E begin case V1: S1 case V2: S2 … case Vn-1: Sn-1 default: Sn endtradução de switch/case
código para avaliar E em tgoto test L1: código para S1 goto next L2: código para S2 goto next … Ln-1: código para Sn-1 goto next Ln: código para Sn goto next test: if t = V1goto L1 if t = V2goto L2 … if t = Vn-1 goto Ln-1 goto Ln next:
outra tradução de switch/case
código para avaliar E em tif t ≠ V1goto L1 código para S1 goto next L1: if t ≠ V2goto L2 código para S2 goto next L2: … Ln-2: if t ≠ Vn-1goto Ln-1 código para Sn-1 goto next Ln-1: código para Sn next:
Backpatching
• uso de um passo extra para resolver as referências a labels definidos posteriormente.
Esquema de Tradução
E E1or M E2
{ backpatch(E1.falselist,M.quad); E.truelist := merge(E1.truelist,E2.truelist); E.falselist := E2.falselist }
M ε {M.quad := nextquad } E E1and M E2
{ backpatch(E1.truelist,M.quad); E.truelist := E2.truelist;
E.falselist := merge(E1.falselist,E2.falselist); }