UNIVERSIDADE DO OESTE DE SANTA CATARINA CAMPUS DE SÃO MIGUEL DO OESTE CURSO: CIÊNCIAS DA COMPUTAÇÃO DISCIPLINA: COMPILADORES PROFESSOR: JOHNI DOUGLAS MARANGON Back-End Compilação 1. Compilação etapa Back-end Todo o processo de geração de código para a máquina alvo é feita na etapa de compilação conhecida como back-end ou síntese. Uma representação do código em uma estrutura de árvore (arvore sintática) é recebida das etapas do front-end após a execução da análise semântica. O que ocorre em muitos casos é que a geração de código pode ser realizada através de ações semânticas processo conhecido com Tradução Dirigida por Sintaxe. Em geral a geração de código pode ocorrer diretamente para a máquina alvo ou o que é mais muito comum e a geração de código ser realizada para uma etapa intermediaria e posteriormente ser gerado código para maquina alvo. 2. Geração de código intermediário Transforma a árvore de derivação em um segmento de código denominado código intermediário. Árvore de derivação Código intermediário (Três-endereços) Vantagens de utilização de uma representação intermediária: Otimização do código de modo a obter um código mais eficiente.
Simplificação de código pois o código intermediário representa uma versão inteligível do código. Possibilidade da tradução do código intermediário em diversas máquinas. A única desvantagem é que o compilador requer mais de uma etapa no processo de compilação. A representação intermediária deve ser fácil de ser produzida, ou seja, criada e fácil de ser traduzida para a máquina alvo. A maior principal diferença entre o código intermediário e o código final é que o código intermediário não especifica detalhes da máquina alvo, ela será uma representação da linguagem de alto nível com a linguagem assembly ou de máquina e representa todas as expressões do programa fonte. Importante: O ideal é que particularidades referentes a código fonte sejam tratadas na parte do front-end e detalhes da arquitetura alvo sejam tratados no back-end. 2.1. Código de três endereços. É uma forma de representar uma linguagem intermediaria, é a forma mais comum de ser utilizada e cada instrução faz referência a no máximo três variáveis (endereços de memória). As instruções básicas dessa linguagem são as seguintes: A := B op C A := op B A:= B goto L if A oprel B goto L Expressão A:= X + Y * Z T1:= Y * Z T2:= X + T1 A:= T2 T1 e T2 são variáveis temporárias Onde A, B, e C representam variáveis, op representa operadores, oprel um operadores relacionais e L um rótulo.
O código de três endereços é composto por uma sequência de instruções envolvendo operações binarias, unárias e atribuições, dessa forma as expressões são decompostas nessa representação em uma série de instruções, outro detalhe importante é o uso de variáveis temporárias nesse processo. Com esse tipo de representação intermediara obtemos um código mais próximo possível ao assembly o que consequentemente facilita a geração de código para a máquina alvo. Exemplo mais complexo de um código intermediário. 3. Otimização de código Essa fase é responsável por gerar um código mais eficiente que envolvem diretamente o uso de memória, espaço e velocidade de execução das instruções. Normalmente quando a ganhos de espaços a perda de tempo de execução e vice-versa. Nessa etapa são aplicados técnicas que permitem detectar processos que otimizem o código. As técnicas podem variar de compilador par compilador e devem manter o significado do programa original e executar a maior parte das melhorais de código dentro de um dentro de um determinado nível de esforço de otimização. Essa etapa tende a consumir muito tempo na compilação e deve ser implementada somente se o compilador exigir um código mais eficiente. O processo de otimização de código envolve duas tarefas:
Otimização de código intermediário: Com base no código intermediário é eliminado atribuições redundantes, expressões comuns, variáveis temporárias desnecessárias, troca de instruções de lugar. Ex: a = b + c; e = c + d + b; a := b + c _t1 := c + d e := _t1 + b a := b + c e := a + d Código Fonte Código intermediário Código otimizado while (i < 10*j) { a[i] = i + 2*j; ++i; } Código Fonte _L1: _t1 := 10 * j if i >= _t1 goto _L2 _t2 := 2 * j a[i] := i + _t2 i := i + 1 goto _L1 _L2:... _t1 := 10 * j _t2 := 2 * j _L1: if i >= _t1 goto _L2 a[i] := i + _t2 i := i + 1 goto _L1 _L2:... Código intermediário Código otimizado Otimização de código objeto: Realiza a troca de instruções de maquinas por instruções mais rápidas e de melhor utilização de registradores. Ex: Pontos := PontoGanhos + PontosRecuperados Código fonte
MOVE PontoGanhos,D0 ADDI PontosRecuperados,D0 MOVE D0, Pontos MOVE PontoGanhos,D0 ADDQ PontosRecuperados,D0 MOVE D0, Pontos Código assembly Otimização do código assembly Trocou ADDI por ADDQ pois identificou que o valor de PontosRecuperados está entre 1 e 8. No compilador gcc temos o parâmetro -O que determina um nível de otimização. Veja o exemplo: Parâmetro O que faz -O0 Nenhuma otimização -O1 Reduzir o tempo de compilação e o tamanho do executável. -O2 Não causa aumento do executável, é o melhor, mais seguro (por conta da portabilidade) e o mais usado nos dias atuais na distribuição de softwares em Linux; -O3 Opção com melhor nível de otimização; entretanto, é a que mais causa efeitos colaterais: arquivos maiores, maior uso de memória RAM e poucas chances de fazer uso de um depurador sobre o programa executável. 4. Exercícios a) Cite as principais vantagens de utilização de uma representação intermediária do código no processo de compilação. b) Cite a principal diferença entre uma representação intermediar de código e o código final. c) O que é o código de três endereços. d) Qual a principal função da etapa de otimização de código no processo de compilação. e) Quais as duas tarefas que podem ser desempenhadas no processo de otimização de código.