Geração de Código Intermediário Tradução dirigida por sintaxe 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. parser verificador de código front-end gerador de código intermediário código intermediário gerador de código 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 t 1 := y * z t 2 := x + t 1 Código de três endereços - t 1 := -c t 2 := b * t 1 t 3 := -c t 4 := b * t 3 t 5 := t 2 + t 4 a := t 5 a = b * -c + b * -c; b a assign * + uminus c * b uminus 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: x = &y, x = *y e *x = y 1
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 E 1 + E 2 E.place := newtemp; E.code := E 1.code E 2.code gen(e.place = E 1.place + E 2.place) E - E 1 E.place := newtemp; E.code := E 1.code gen(e.place = uminus E 1.place) E ( E 1 ) E.place := E 1.place; E.code := E 1.code E id E.place := id.place; E.code := S while (E) S 1 Tradução do while S.begin := newlabel; S.end := newlabel; S.code := gen(s.begin : ) E.code gen( if E.place = 0 goto S.end) S 1.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. : 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 T 1 { T.type := array(num.val, T 1.type); T.width := num.val * T 1.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, 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 2
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 D 1 ; D 2 D proc id ; N D 1 ; 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 E 1 + E 2 { E.place = newtemp; gen(e.place = E 1.place + E 2.place) } E - E 1 { E.place = newtemp; gen(e.place = uminus E 1.place) } E ( E 1 ) { E.place = E 1.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 E 1 to t 1 evaluate E 2 to t 2 t := t 1 + t 2 Reuso de nomes - 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[i 1, i 2 ] base + ((i 1 low 1 ) * n 2 + i 2 low 2 ) * w row-major vs. column-major 3
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 ifthen-else, while-do etc. Permite curto-circuito de expressões: se E 1 é avaliado como true, na expressão E 1 or E 2, não é necessário avaliar E 2. Representação numérica - a or b and not c t 1 = not c t 2 = b and t 1 t 3 = a or t 2 Representação numérica - 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: E E 1 or E 2 { E.place = newtemp; gen(e.place = E 1.place or E 2.place) } E id 1 relop id 2 { E.place = newtemp; gen( if id 1.place relop.op id 2.place goto nextstat + 3); gen(e.place = 0 ); gen( goto nextstat + 2); gen(e.place = 1 ) } E ( E 1 ) { E.place = E 1.place } E true { E.place = newtemp; gen(e.place = 1 ) } Representação numérica - 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 a < b or c < d and e < f 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 t4 Fluxo de controle Associamos a cada expressão booleana desvios para labels caso a expressão seja avaliada para true ou false. E.true e E.false 4
Tradução do if-then Tradução do if-then-else S if (E) S 1 E.true = newlabel; E.false = S.next; S 1.next = S.next; S.code = E.code gen(e.true : ) S 1.code S if (E) S 1 else S 2 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 herdados Tradução do while S while (E) S 1 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 E E 1 or E 2 { E 1.true := E.true; E 1.false := newlabel; E 2.true := E.true; E 2.false := E.false; E.code := E 1.code gen(e 1.false : ) E 2.code) } E E 1 and E 2 {E 1.true := newlabel; E 1.false := E.false; E 2.true := E.true; E 2.false := E.false; E.code := E 1.code gen(e 1.true : ) E 2.code) } E not E 1 { E 1.true := E.false; E 1.false := E.true; E.code := E 1.code} E id 1 relop id 2 {E.code := gen( if id 1.place relop.op id 2.place goto E.true gen( goto E.false) } E true {E.code := gen( goto E.true } E false {E.code := gen( goto E.false } 5
Exemplo 1 Exemplo 2 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. 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 L1 switch/case switch expression begin case 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 tradução de switch/case switch E begin case V 1 : S 1 case V 2 : S 2 case V n-1 : S n-1 default: S n end código para avaliar E em t goto test L 1 : código para S 1 L 2 : código para S 2 L n-1 : código para S n-1 L n : código para S n test: if t = V 1 goto L 1 if t = V 2 goto L 2 if t = V n-1 goto L n-1 goto L n next: 6
outra tradução de switch/case código para avaliar E em t if t V 1 goto L 1 código para S 1 L 1 : if t V 2 goto L 2 código para S 2 L 2 : L n-2 : if t V n-1 goto L n-1 código para S n-1 L n-1 : código para S n next: Backpatching uso de um passo extra para resolver as referências a labels definidos posteriormente. E E 1 or M E 2 { backpatch(e 1.falselist,M.quad); E.truelist := merge(e1.truelist,e 2.truelist); E.falselist := E 2.falselist } M ε {M.quad := nextquad } E E 1 and M E 2 { backpatch(e 1.truelist,M.quad); E.truelist := E 2.truelist; E.falselist := merge(e1.falselist,e 2.falselist); } E true E false { E.truelist := makelist(nextquad); emit( goto _ ) } { E.falselist := makelist(nextquad); emit( goto _ ) } 7