700 likes | 793 Views
Departamento de Estatística e Informática Universidade Federal de Sergipe Compiladores. Blocos básicos e Traces. Giovanny Lucero giovanny@ufs.br. Adequação de Tree. Alguns aspectos de Tree não tem correspondência com linguagens de máquina CJUMP tem dois rótulos
E N D
Departamento de Estatística e Informática Universidade Federal de Sergipe Compiladores Blocos básicos e Traces Giovanny Lucero giovanny@ufs.br
Adequação de Tree • Alguns aspectos de Tree não tem correspondência com linguagens de máquina • CJUMP tem dois rótulos • Ordem de avaliação de expressões interfere com otimização • Sem ESEQ e CALL, ordem de avaliação não importa
Transformação em três passos • Trees canônicos sem SEQ ou ESEQ • Subindo SEQ e ESEQ ao topo da árvore • Todo pai de SEQ é um SEQ • Portanto, substituímos os SEQs por uma lista de Tree.Stm • Agrupação em blocos sem JUMPS internos ou rótulos (Blocos Básicos) • Traces: c/CJUMP seguido imediatamente pelo seu rótulo false
Árvores Canônicas • Não tem SEQ nem ESEQ • O pai de cada CALL é • EXP(...) ou MOVE(Temp t, ...)
Regras de transformação ESEQ ESEQ e ESEQ s1 SEQ s2 e s2 s1 ESEQ(s1,ESEQ(s2,e)) = ESEQ(SEQ(s1,s2),e)
ESEQ BINOP BINOP s1 op ESEQ e2 e1 op e2 e1 s1 BINOP(op, ESEQ(s1,e1),e2) = ESEQ(s1, BINOP(op, e1,e2)) MEM(ESEQ(s,e1)) = ESEQ(s,MEM(e1)) JUMP(ESEQ(s,e1) = SEQ(s,JUMP(e1) CJUMP(op,ESEQ(s,e1),e2,L1,L2) = SEQ(s,CJUMP(op,e1,e2,L1,L2))
BINOP ESEQ BINOP(op,e1,ESEQ(s1,e2)) = ESEQ(s1, BINOP(op,e1,e2)) op e1 ESEQ s1 BINOP e2 s1 op e2 e1 Está correto?
BINOP ESEQ BINOP(op,e1,ESEQ(s1,e2)) = ESEQ(s1, BINOP(op,e1,e2)) op e1 ESEQ s1 BINOP e2 s1 op e2 e1 • Está correto? Não em todos os casos. • s1 pode realizar alguma ação que modifique o valor de e1.
BINOP ESEQ op e1 ESEQ MOVE ESEQ e2 s1 s1 BINOP TEMP e1 op TEMP e2 • Solução • Criar um temporário t. t t BINOP(op,e1,ESEQ(s1,e2)) = ESEQ(MOVE(TEMP t, e1), ESEQ(s1, BINOP(op, TEMP t, e2)
CJUMP(op,e1,ESEQ(s,e2),L1,L2) = SEQ(MOVE(TEMP t,e1), SEQ(s, CJUMP(op,TEMP t,e2,L1,L2)))
Se s e e1 comutam: BINOP(op,e1,ESEQ(s,e2)) = ESEQ(s,BINOP(op,e1,e2)) CJUMP(op,e1,ESEQ(s,e2),L1,L2) = SEQ(s, CJUMP(op,e1,e2,L1,L2) ) • Observe que s e e1 comutam se s não produz efeitos colaterais que alterem e1 • Dados de e1não são referenciados por s • Não podemos saber sempre se duas expressões comutam. MOVE(MEM(x),y) e MEM(z) • Tomamos uma abordagem conservadora • NAME(L) e CONST(n) comutam com todo mundo.
CALL • Regras similares são aplicadas a CALL quando seu pai não é MOVE ou EXP. • Exemplo: • BINOP(PLUS, CALL(...), CALL(...)); • CALLs devolvem resultados em um mesmo registrador. (Sobrescrita).
CALL • Para resolver este problema, substituir cada ocorrência de CALL por: • ESEQ(MOVE(TEMP tnew, CALL(...), TEMP tnew).
Linearização dos Statements • Após execução destes passos, todos os SEQ estarão próximos a raiz da árvore. • No entanto podemos encontrar construções desta forma: • SEQ(SEQ(a,b), c)). • Para eliminarmos estas construções, aplicamos novas transformações tal que: • SEQ(SEQ(a,b),c) = SEQ(a, SEQ(b, c)). • Agora sim, podemos eliminar os construtores SEQ.
Transformação em três passos • Trees canônicos sem SEQ ou ESEQ • Subindo SEQ e ESEQ ao topo da árvore • Todo pai de SEQ é um SEQ • Portanto, substituímos os SEQs por uma lista de Tree.Stm • Agrupação em blocos sem JUMPS internos ou rótulos (Blocos Básicos) • Traces: c/CJUMP seguido imediatamente pelo seu rótulo false
Blocos Básicos • Em um bloco básico: • O primeiro comando é um rótulo • O último comando é um JUMP ou CJUMP • Não há mais rótulos JUMPS ou CJUMPS Algoritmo: • scanear o programa Tree assim: • Se um rótulo é achado, começa um novo bloco • Se um (C)JUMP é achado, termina o bloco • Se ficou algum bloco não finalizado por (C)JUMP, adicione um JUMP para o próximo bloco • Se ficou algum bloco sem começar com rótulo, invente um novo rótulo
Transformação em três passos • Trees canônicos sem SEQ ou ESEQ • Subindo SEQ e ESEQ ao topo da árvore • Todo pai de SEQ é um SEQ • Portanto, substituímos os SEQs por uma lista de Tree.Stm • Agrupação em blocos sem JUMPS internos ou rótulos (Blocos Básicos) • Traces: c/CJUMP seguido imediatamente pelo seu rótulo false
Traces • Observe que os blocos básicos podem ser re-arranjados em qualquer ordem sem alterar a semântica do programa • Escolhemos um ordenamento de blocos tal que c/CJUMP é seguido por seu rótulo falso, e • Se possível, JUMPs seguido imediatamente do seu rótulo alvo
Traces • Algoritmo: • Enquanto existir blocos não marcados. • Comece com qualquer bloco (marque o bloco) • Siga o possível caminho de execução (JUMP), marcando os blocos percorridos. • Se CJUMP() escolha um dos dois caminhos. • Ligue os blocos percorridos (trace gerado).
Traces • Finalizando: • Qualquer CJUMP imediatamente seguido pelo seu rótulo “false”. • Deixe como está. • Qualquer CJUMP imediatamente seguido pelo seu rótulo “true”. • Trocamos o rótulo true por false e negamos a condição.
Traces • Qualquer CJUMP(cond, a, b, lt, lf) seguido nem por true ou false. • Rescrevemos o CJUMP para a seguinte forma: • CJUMP(cond, a, b, lt, l’f) • LABEL l’f • JUMP(NAME lf);
Departamento de Estatística e Informática Universidade Federal de Sergipe Compiladores Seleção de instrução Giovanny Lucero giovanny@ufs.br
Padrões Tree • Identificamos uma instrução de máquina como um fragmento de Tree (um padrão) • Tiling: recortamos a árvore em um mosaico/“quebra cabeças” de padrões • Objetivo: obter um conjunto “otimizado” de padrões.
Padrões para Jouette + × - / + + CONST CONST CONST - CONST Em jouette o registrador 0 sempre contém 0
MEM MEM MEM MEM + + CONST CONST CONST MOVE MOVE MOVE MOVE MEM MEM MEM MEM + + CONST CONST CONST MOVE MEM MEM
Tiling árvores MOVE MEM MEM + + CONST x fp MEM * TEMP i CONST 4 + CONST a fp a[i]:=x
Tiling árvores 9 MOVE 8 MEM MEM 6 + + 7 2 CONST x fp MEM * 5 TEMP i CONST 4 + 2. LOAD r_1 ← M[fp+a] 4. ADDI r_2 ← r_0 + 4 5. MUL r_2 ← r_i × r_2 6. ADD r_1 ← r_1 + r_2 8. LOAD r_2 ← M[fp+x] 9. STORE M[r_1+0] ← r_2 3 4 fp CONST a fp 1 a[i]:=x
Tiling árvores 9 MOVE MOVE 8 MEM MEM MEM MEM 6 + 2. LOAD r_1 ← M[fp+a] 4. ADDI r_2 ← r_0 + 4 5. MUL r_2 ← r_i × r_2 6. ADD r_1 ← r_1 + r_2 8. LOAD r_2 ← M[fp+x] 9. STORE M[r_1+0] ← r_2 9. MOVEM M[r1] ← M[r2] + + + 7 2 MEM * fp CONST x CONST x fp 5 MEM * TEMP i CONST 4 + TEMP i CONST 4 + 3 4 X X X X X X X X fp CONST a CONST a fp 1 a[i]:=x
Tilings ótimos e “otimais” • C/instrução de máquina tem um custo (tempo de execução) • Ótimo soma dos custos dos tiles é mínima • Otimal não existe nenhum par de tiles adjacentes que possam ser combinados em um único tile mais eficiente • Ótimo Ótimal, mas não viceversa • Para RISC otimal e ótimo não são muito diferentes • Para CISC nota-se às vezes a diferença
MaximalMunch • Algoritmo top-down que calcula tilingotimal • Começando pela raiz, sempre escolha o tile maior que puder • Continue top-down com as sub-árvores ainda sem cobrir • Por c/tile colocado, gere as instruções correspondentes • Gera instruções em ordem inversa • Se todas as instruções têm o mesmo peso, o tile maior é o que tem mais nós.
Tiling Ótimo • O algoritmo usa programação dinâmica: encontra a solução ótima baseada nas soluções ótimas de cada subproblema • Tiling ótimo de uma árvore é baseado no tiling ótimos das sub-árvores • Associa com cada nó um custo • a soma dos custos do conjunto de instruções ótimo para sua sub-árvore • Trabalha bottom-up
MEM + CONST1 CONS2 Exemplo • CONST1 só é casado por ADDI e tem custo 1 • Similarmente CONST2 • Para + temos: + + CONST + CONST
MEM + CONST1 CONS2 • Para MEM temos MEM MEM + CONST MEM + CONST
Emissão de código • Uma vez calculado o custo da raiz (e assim da árvore inteira), emitimos o código assim emission(n): para cada folha l do tile t selecionado para n faça emission(l); emita o código para t
MEM + CONST1 CONS2 Emissão de código O código emitido para o exemplo é ADDI r_1 ← r_0+1 observe que não é gerado código para o nó +
MEM + CONST1 CONS2 Emissão de código O código emitido para o exemplo é ADDI r_1 ← r_0+1 LOAD r_1 ←M[r_1+2] observe que não é gerado código para o nó +
Complexidade dos Algoritmos • Tanto maximal munch como programação dinâmica tem complexidade linear. Porém a constante do maximal munch é bem menor. • Detalhes no livro do tigre • Na prática esta fase é muito eficiente se comparada com outras do compilador.
Geradores de geradores • Existem ferramentas que geram automaticamente um gerador de código • Recebem como entrada a especificação dos Tiles usando gramáticas • Para cada regra da gramática é associado um custo e uma ação específica. • Custos são usados para encontrar o Tiling ótimo. • Ações das regras casadas são usadas na fase de emissão.
Departamento de Estatística e Informática Universidade Federal de Sergipe Compiladores Análise de Liveness Giovanny Lucero giovanny@ufs.br
Longevidade (Liveness) • Tradução para código intermediário assume um número ilimitado de temporários • Máquinas têm um número limitado de registradores • Dois temporários cabem num registrador se eles não são usados ao mesmo tempo • Excessos de temporários devem ser armazenados em memória • Análise de Liveness é uma tarefa prévia a alocação de registradores • Baseado no grafo de fluxo de controle • a está vivo (live) sse contém um valor necessário no futuro
Grafo de fluxo de controle 1 a:=0 a ← 0 L1: b ← a + 1 c ← c + b a ← b * 2 if a < N goto L1 return c 2 b := a+1 3 c := c+b 4 a := b*2 b está viva em 3→4 e 2→3 a em 1→2 e 4→5→2, mas não em 3→4 c em todo o programa 5 a < N 6 return c Análise de liveness é feita de trás para frente
Definições • definição = ocorrência no lado esquerdo de uma atribuição • uso = no lado direito • def(a)={n| n define a} (a é variável e n nó) • def(n)={a|n define a} • Similarmente definimos use(a) e use(n) • Liveness: • Uma variável está viva numa aresta se há um caminho dirigido desde esta aresta até um nó que a usa e que não passa por nós que a definem • Uma variável está viva num nó se ela está viva em alguma aresta que entra neste nó • Uma variável vivefora de um nó se está viva em alguma aresta de saída
Liveness estático vs. dinâmico • Note que o nó 4 nunca é alcançado. Logo a nãoestá vivo fora de 2 (liveness dinâmico). • Obs. dinâmico estático • Infelizmente, liveness dinâmico é indecidível. • Liveness estático é suficiente para realizar boas otimizações 1 a:=b*b 2 c := a+b 3 c<=b 4 5 return a return c
Interferência entre variáveis • Análise de liveness é útil para otimizações mas principalmente para alocação de registradores • Duas variáveis se interferem se não podem ser alocadas num mesmo registrador • a e b estão vivas na mesma instrução • b está viva numa instrução que define a há um caso particular para instruções MOVE t s (copia) ... x ... s ... (uso de s) ... y ... t ... (uso de t) t e s não se interferem
Grafos de interferência • Grafo de interferências • os nós são as variáveis • aresta de a para b se a e b se interferem
Departamento de Estatística e Informática Universidade Federal de Sergipe Compiladores Alocação de Registradores Giovanny Lucero giovanny@ufs.br
Alocador de registradores • Atribui aos temporários um número pequeno de registradores • Atribui uma locação de memória quando não é possível atribuir um registrador • Se possível atribui o mesmo registrador a mais de um temporário.
Alocador de registradores • Se reduz ao problema do coloreamento de grafos • Colorir o grafo de interferências (onde os nós são os temporários) • 1 cor por registrador (K registradores K cores) • nós adjacentes devem ter cores diferentes • o coloreamento se corresponde com uma atribuição de registradores que satisfaz as interferências • Se não houver coloreamento, alguns temporários são alocados em memória (spilling) • O problema é NP-Completo • Boa aproximação em tempo linear
Coloreando por simplificação • Cinco fases: Build, Simplify, Spill, Select e Start Over • Build • Construa o grafo de interferências (análise de dataflow) • Simplify (heurística simples) • Se grau(n) < k coloreie G’ e então pinte n com uma cor diferente dos seus vizinhos (implementado com uma pilha de nós). • Spill • Se todos os nós tem grau ≥ k escolha um nó candidato a eliminação (alocação em memória). Seja otimista e empilhe.