domingo, 18 de maio de 2014

Compiladores : Estruturação (Continuação)

Geração de Código Intermediário


Como prometido, agora irei explanar de forma sucinta e objetiva (e provavelmente entendível) como funciona  a fase de geração de código intermediário, onde o compilador agora transformará todos as representações criadas (se você ainda não leu sobre essas representações dá uma olhada no meu último post)  em uma única forma intermediária, como se fosse um programa para um computador abstrato (como fazemos algoritmos em pseudocódigo) , tendo como objetivo criar uma forma cuja tradução para o código final seja fácil. A representação é criada numa linguagem próxima ao assembly, mas diferente da geração de código final, independe do processador pois os compiladores não trabalham com o código específico de um processador nesta fase, as formas usuais para essa representação são a notação posfixa e o código de três endereços.
O código de três endereços é uma forma de representação assim chamada pois consiste em sequências parecidas com instruções assembly, utilizando apenas três variáveis por instrução, onde cada operador pode atuar como um registro, é construída geralmente da forma z = x op y, sendo utilizada uma variável para armazenar o resultado e outros dois operandos e sua respectiva operação do outro lado.  Por exemplo:

                                                               a = c + d * b

Essa expressão seria traduzida da seguinte forma:

                                                               t1 = d * b
                                                               a  = c + t1

Como você pôde perceber as instruções são separadas na sequência em que serão executadas e necessariamente são criadas variáveis temporárias (t1) para transladar o código. O código de três endereços possui alguns pontos fracos como utilizar não mais que uma operação por vez, de ordem fixa, e ainda há a necessidade de criação de variáveis temporárias. Além das operações binárias podem ser feitas operações de atribuições, chamada de funções, instruções de desvio.

A notação posfixa (ou notação polonesa reversa (RPN em inglês)), diferente da aritmética normal, que utiliza seus  operadores entre  os elementos (chamada notação infixa), coloca as operações depois dos operandos não havendo por exemplo a necessidade de parênteses. Tudo fica mais compreensível com exemplos:
Notação infixa:  
      (a + b) * c
Notação posfixa:
       a b + c *
Esse tipo de notação é muito utilizado quando se trabalha com pilhas (stack) e também internamente em linguagens de programação, tem-se a vantagem de realizar cálculos de maneira mais eficiente já que não há necessidade de verificar a precedência de operadores, já que depois de colocados na pilha e calculado um novo elemento será inserido, ex:

                                                               push a
                                                               push b
                                                               add a,b
                                                               pop a
                                                               pop b
                                                               push _result
                                                               push c
                                                               mult  _result, c
                                                               pop c
                                                               pop _result
                                                               push _result


Otimização de Código


A otimização de código é uma fase opcional na compilação, podendo ocorrer em duas fases, a primeira é depois de ser gerado o código intermediário, quando o compilador melhora o próprio código removendo registros e operações desnecessárias ou as substituindo por outra mais eficiente, a outra fase é na geração do código final. A diferença entre essas duas fases é que a primeira não depende do processador já que o código que sofre otimizações é o intermediário, já a segunda fase depende da máquina alvo pois o código final (código assembly ou de máquina) será exclusivo para aquela arquitetura. A otimização leva um tempo significativo para  ser aplicada, sendo que nem sempre vale a pena o tempo consumido.



Geração de Código


A última fase de compilação, aqui o compilador mapeia a representação intermediária do código no código alvo, se o código alvo é código de máquina então registros ou locais de memória são escolhidos para cada uma das variáveis do programa e são escolhidas as operações equivalentes para as instruções intermediárias criadas previamente. Esta é a tarefa mais complicada para o gerador de código pois ele tem de escolher a instrução mais eficiente dentre as possíveis (em máquinas CISC, por exemplo, o trabalho é ligeiramente mais complicado pois há instruções com finalidades semelhantes (soma, incrementação) dificultando assim a escolha), a escolha do local de armazenamento também é complexa tendo em vista que há a possibilidade de se armazenar nos próprios registradores na CPU,  no topo da pilha ou na memória geral. O acesso ao topo da pilha é mais rápido que na memória geral, pois a memória na pilha é organizada de forma automática, o ponto fraco do armazenamento na pilha é o tamanho de dados que é possível alocar, diferente da memória, que tem um acesso mais lento, mas não possui restrições para uso tanto quanto a pilha. Soluções heurísticas e ad hoc são utilizadas para resolver esses problemas.   
                                               
Fonte:
Compilador, Wikipédia, a Enciclopédia Livre. Disponível na Internet via <http://pt.wikipedia.org/wiki/Compilador> 

 Aho, Alfred. V. et. al, Compilers: Principles, Techniques, and Tools, Second Edition. Addison-Wesley, 2007. ISBN 978-0-321-48681-3. Capítulos 1 e 2.

SANTOS, Ricardo. Compiladores I - Capítulo 1, 20-?. PDF. Disponível na Internet via <http://www.facom.ufms.br/~ricardo/Courses/CompilerI/Lectures/Lec01.pdf>

RANGEL, José Lucas. Geração de Código. PDF. Disponível na Internet via http://www-di.inf.puc-rio.br/~rangel/comp/gercod.pdf

Faculdade de Engenharia Elétrica e Computação - Disponível na Internet via http://www.fee.unicamp.br/?q=node/145 


Gostou ? Deixa um comentário aqui embaixo, dúvidas e sugestões são bem vindas. 

Nenhum comentário:

Postar um comentário