Vous êtes sur la page 1sur 1

Editado por Aleardo Manacero Jr.

Captulo 5 - Gerao de cdigo


1. 2. 3. 4. 5. 6. 7. Implicaes do hardware Gerao de cdigo, como faze-la Tratamento das variveis Tratamento de precedncias e associatividade Tratamento de estruturas de programao Tratamento de subrotinas Gerao de cdigo atravs da gramtica de atributos

Aps termos examinado os mdulos que compe o front-end de um compilador podemos passar ao estudo de como gerado o cdigo executvel a partir do resultado obtido nos mdulos de entrada. O back-end do compilador pode envolver mais do que uma etapa, sendo que necessariamente temos uma etapa de gerao do cdigo, que pode ou no ser acompanhada por etapas de otimizao do cdigo. Neste captulo nos ocuparemos da gerao do cdigo, que obviamente vai depender do hardware em que se v aplicar o programa executvel gerado. Entretanto, esta dependncia do hardware acaba por no ser sentida quando se discute os aspectos essenciais do processo de gerao de cdigo. A seguir faremos uma breve descrio das diferenas de hardware que devem ser levadas em conta na implementao do gerador de cdigo, para ento passarmos ao estudo dos problemas envolvidos na gerao de cdigo, independente da mquina em que se executar a compilao.

5.1 Implicaes do hardware


Como o hardware tem uma evoluo constante, temos a existncia de conjuntos de instrues diferentes para cada tipo de mquina. Assim, no momento de gerar cdigo o compilador tem que ter conhecimento da mquina para a qual ele se destina, isto , cada mquina alvo vai ter um conjunto diferente de instrues para executar uma determinada tarefa. Com isso surgem dois problemas. O primeiro est relacionado com o fato de que nem sempre uma mquina possui a instruo desejada para uma determinada funo. Assim, temos que executar esta funo atravs de um conjunto de vrias instrues. Deste fato nasce nosso segundo problema, pois pode-se ter vrios conjuntos diferentes de instrues que executem a mesma tarefa, o que nos coloca na posio de termos que escolher entre estes conjuntos por aquele que execute esta funcionalidade com a maior eficincia. Isto implica num primeiro nvel de otimizao, que seria feito ainda antes de se compilar qualquer coisa. Uma outra diferena associada aos aspectos acima que cada CPU vai apresentar, em geral, conjuntos diferentes de registradores, isto , teremos diferentes manipulaes entre memria e registradores segundo a disponibilidade dos mesmos na CPU em uso. Otimizar o nmero de acessos memria fica sendo ento um novo aspecto a ser considerado, muito embora isto possa ser feito em tempo-real durante a compilao. O problema maior entretanto quanto a forma em que so feitos os acessos (endereamentos) aos dados usados no programa. Dependendo do nmero e do tipo de acesso feito, temos mquinas que precisam de maior ou menor nmero de bits para representar uma mesma instruo, o que vai dificultar ou facilitar a gerao do cdigo. A primeira destas mquinas a que enderea duas posies na memria, conhecida como mquina de 2-endereos. Neste caso, em cada instruo temos que ter presentes o cdigo da instruo (indicando o que vai ser feito) e um par de endereos na memria. A desvantagem clara desta abordagem que o tamanho da instruo amarra o nmero mximo de posies endereaveis na memria. Uma primeira evoluo foi atravs da substituio de um dos endereos em memria por um registrador interno da CPU. Assim, apesar de ainda termos que acessar dois endereos, tinhamos que um deles poderia ser identificado com um nmero menor de bits e acessado num tempo menor, agilizando o processo e possibilitando um aumento no nmero de posies endereaveis para um mesmo tamanho de instruo. Substituindo-se agora o registrador pelo prprio acumulador da CPU vamos conseguir uma mquina com apenas um endereo. Assim, cada instruo passa a ter apenas dois campos, um contendo seu cdigo e outro contendo o nico endereo a ser acessado na memria. Isto leva a uma mquina mais eficiente em termos de velocidade e tamanho de memria. Outras melhorias foram obtidas permitindo-se uma maior variao no que poderia ser endereado. Assim, em mquinas mais recentes temos instrues acessando pares de registradores, ou mesmo posies de memria atravs de endereamento indireto via registradores. Por ltimo, uma vez que uma mquina de 1-endereo funciona melhor do que uma de 2-endereos, por que no tentar trabalhar totalmente sem endereos, isto , criar uma mquina com instrues de 0-endereos. Isso feito atravs do uso de uma pilha, cujo topo seria ento automaticamente apontado por algum registrador pr-definido dentro da CPU. Ento, cada instruo teria apenas um campo, que seria o seu cdigo. Os operandos a serem usados pela instruo seriam encontrados na pilha, cuja operao seria idntica a de um autmato a pilha. A vantagem no uso de tal mquina que quando fazemos a anlise sinttica bottom-up acabamos por gerar estruturas de operao semelhantes a notao reversa-polonesa, criada por Lukasiewicz, que pode ser aplicada de forma direta ao modo de operao da pilha numa mquina de 0-endereos. desnecessrio perdermos tempo aqui para ilustrar como o mtodo de Lukasiewicz pode ser executado atravs da operao sobre pilhas. Nas figuras a seguir temos uma descrio visual de como essas diferentes mquinas fazem acesso aos dados na memria do computador:

Figura 5.1 - Acesso aos dados pelos vrios tipos de mquinas.

5.1.1 - A mquina de 0-endereos:


Para o restante deste captulo estaremos trabalhando com o conjunto de instrues apresentado a seguir, que representa de forma geral uma mquina de 0-endereos. CDIGO MNEMNICO 30 28 27 26 11 12 14 15 16 17 18 20 1 3 4 5 8 9 24 25 Zero LoadCon <value> Load Store Multiply Add Or And Equal Less Greater Negate BranchFalse Call Enter Exit to caller Dupe Swap Stop Global FUNO push the constant zero push <value> pop address; push memory(address) pop value; pop address; store value in memory (address) pop A; pop B; push B*A pop A; pop B; push B+A pop A; pop B; push (B or A) pop A; pop B; push (B ad A) pop A; pop B; push 1 if (B=A); push 0 otherwise pop A; pop B; push 1 if (B<A); push 0 otherwise pop A; pop B; push 1 if (B>A); push 0 otherwise pop A; push -A pop offset; pop value; if value = 0 then add offsetto PC pop address; push return address; go to subroutine pop number; allocate space for that many variables pop number; deallocate that many parameters andreturn push a copy of the stack top pop two values; push them back in the reverse order stop run subtract frame pointer from top of stack

Tabela 5.1 - Cdigos de instrues do processador de 0-endereos

5.2 - Gerao de cdigo, como faz-la.


O que se faz para gerar cdigo criar regras para a aplicao de conjuntos de instrues que cumpram funcionalidades declaradas nas instrues do cdigo fonte. Isto conhecido como gerao dirigida sintaticamente, uma vez que usamos a prpria rvore de derivao sinttica para definir qual ser a sequncia de operaes desejada. A seguir temos alguns exemplos de como fazer isso. Exemplo 1: Obter o resultado para 3*4+5*2: A introduo dos valores na pilha deve ser feita atravs da instruo LoadCon, onde <value> ser o valor imediato das constantes da expresso. Alm disso temos que executar duas multiplicaes e uma soma, atravs dos comandos Multiply e Add respectivamente. Entretanto, temos ainda que alterar a expresso dada para a notao posfixa, que a que se adequa ao uso da pilha e a estrutura do analisador sinttico. Assim, temos: 3*4+5*2 --> 3 4 * 5 2 * +

Cdigo: Instruo Contedo da pilha (topo a esquerda) LoadCon 3 3 LoadCon 4 4 3 Multiply 12 LoadCon 5 5 12 LoadCon 2 2 5 12 Multiply 10 12 Add 22 Exemplo 2: Uso de variveis. Apesar de que o conjunto de operaes dado acima funciona bastante bem, temos que na maioria das vezes os operandos so variveis, cujos valores reais devem ser buscados na memria. Para fazer isso lanamos mo das instrues Load e Store, que trabalham sobre endereos indexados atravs da pilha. Por exemplo, se quizermos fazer a atribuio a = b temos que fazer: Instruo Contedo da pilha (topo a esquerda) LoadCon <value> endereo da varivel a LoadCon <value> endereo de b; endereo de a Load valor de b; endereo de a Store (vazia) Exemplo 3: Uso de endereos e cdigos. A partir do conjunto de instrues da seo 5.1.1 pode-se observar que a cada instruo temos associado um mnemnico (que o que temos usado at aqui) e um cdigo. Na realidade, o computador entende apenas o cdigo, logo o mesmo que vai estar presente aps a gerao do executvel. Por outro lado, o exemplo anterior introduziu o problema de como localizar endereos das variveis presentes em cada instruo. Este problema resolvido mais adiante, atravs da tabela de smbolos, assim nos concentraremos neste exemplo a mostrar como poderia seria o cdigo sem o uso dos mnemnicos. Para tanto considere a resoluo da atribuio a = a-1 % armazena a posio de a na memriapara o retorno de seu valor final 28 a % armazena a posio de a na memriapara obter seu valor inicial 27 % obtm o valor de a 28 1 % coloca 1 na pilha 20 % nega o valor no topo da pilha (obtm -1) 12 % soma a + (-1) 26 % armazena o novo valor de a Destes exemplos j podemos vislumbrar algumas rotinas a serem seguidas sempre. Uma delas o fato de que se existir uma atribuio, temos que construir um prembulo em que ser armazenado na pilha o endereo de retorno e um desfecho, fazendo de fato esta operao, deixando ento a pilha vazia. A expresso cujo resultado vai ser atribudo fica ento entre estas duas instrues. 28 a Alm disso, como no existe a operao de subtrao, sempre que quizermos execut-la teremos que executar a operao de Negate sobre o subtrator, para ento som-lo ao subtraendo. Da mesma forma, a operao de diviso no pode ser executada com apenas uma nica instruo, alis, neste caso nem podemos faz-la com poucas instrues, pois o nico mtodo disponvel passa a ser o das subtraes sucessivas.

5.3 - Tratamento das variveis


Como j apontamos na seo anterior, temos que cada varivel na memria tem o seu endereo tratado atravs da tabela de smbolos. Porm, deixamos de indicar como isso seria feito. Aqui passamos a descrever a forma na qual trataremos os endereos de uma varivel durante a compilao. O primeiro passo alterar a tabela de smbolos para acrescentar nela a informao sobre o endereo em que uma determinada varivel (que representada por um smbolo) vai ser armazenada na memria. Este endereo ainda vai ser lgico, uma vez que endereos fsicos s surgem quando o programa est carregado na memria. Passa a ser necessrio ento um mecanismo que controle quais posies da memria esto livres para uso pelas variveis. Isto pode ser feito simplesmente atravs de um apontador para uma regio da memria em que estaro armazenadas as variveis. Assim, cada vez que fosse inserido um novo smbolo na tabela de smbolos, reservariamos o nmero de posies de memria necessrios para armazenar o contedo de tal smbolo (recebendo esta informao atravs da anlise semntica), e retornariamos ao scanner o endereo base das posies alocadas, que seria ento armazenado na tabela de smbolos.

5.4 - Tratamento de precedncias e associatividade


Como a entrada para o gerador de cdigo no contm sinais que regulem a precedncia ou associatividade dos operandos, temos que saber se a construo das operaes vai ou no ser feita na ordem desejada. Nos dois casos temos que o tratamento feito durante a anlise sinttica, ou mais precisamente, na gerao da rvore de derivao para o programa em compilao. Temos que quanto mais baixo na rvore estiver uma operao, maior ser a sua precedncia no momento de execuo, isto devido a prpria estruturao da gramtica, em que fazemos com que a partir das produes em que apaream os smbolos terminais que representem operandos, passamos primeiro pelas produes sobre operaes de maior precedncia. Assim, no momento da criao da rvore de derivao teremos automaticamente as operaes de maior precedncia prximas das folhas da rvore. J o problema da associatividade, que surge em operaes do tipo 5-2-7 que tem resultados diferentes se executamos primeiro 5-2 ou primeiro 2-7, temos tambm uma soluo relativamente trivial atravs da construo cuidadosa da gramtica, quando teriamos que deixar definida qual seria a ordem para a associao de operandos. Por exemplo, as duas gramticas a seguir representam a mesma linguagem, porm temos que a primeira associativa a esquerda enquanto a ltima associativa a direita. Obs: O smbolo [+] indica o momento em que o predicado tem seu valor calculado G1 G4 E -> E + T [+] E -> T + E [+] E -> T E -> T T -> a [+] E -> a [+] A diferena entre as duas pode ser sentida na construo da rvore de derivao. Por exemplo, a Figura 5.2 apresenta as rvores para a expresso a+a+a.

Figura 5.2 - rvores de derivao para G1 e G2 Logo vemos que ordem em que sero executadas as somas depende de como a gramtica foi arranjada.

5.5 Tratamento de estruturas de programao


At o momento nos preocupamos apenas com a gerao de cdigo estritamente linear, isto , cdigo em que a instruo i seguida imediatamente pela instruo i=1, sem desvios, condicionais ou no. Entretanto sabemos que grande parte do tempo de um programa gasto em estruturas da linguagem, tais como if-then-else, while, do-repeat, etc.. Nestes casos temos que prover ao gerador de cdigo mecanismos para o tratamento de tais eventos. O mais bsico deles prover uma instruo que lhe permita testar o valor de uma condio qualquer. O outro mecanismo, em geral associado a este, o de prover uma instruo que faa um salto condicional de uma posio da memria para outra, deixando ento de executar o conjunto de instrues que seguia linearmente a instruo atual. Na mquina definida em 5.1.1 temos que estas instrues so Equal, Less, Greater e BranchFalse. O funcionamento destas instrues e o seu uso para a gerao de cdigos tambm bastante simples, exceto quanto ao aspecto da determinao dos endereos para desvio. O problema do desvio surge quando tentamos determinar o endereo para o qual estamos saltando. Este endereo determinado (em nossa mquina) atravs do valor de offset usado na instruo BranchFalse. Acontece que na maior parte das vezes o endereo destino est localizado numa posio mais adiante da posio atual. Como o compilador ainda no passou por esta posio (e no temos como determinar o nmero exato de instrues contidas entre a origem e o destino deste salto), ficamos sem saber que valor adotar para offset. Para solucionar o problema do salto a frente temos duas abordagens. A primeira delas percorre o texto todo duas vezes, sendo que na segunda passagem que sero determinados os valores de offset para todos os pontos em que isto for necessrio. J a segunda abordagem percorre o texto apenas uma vez, mas mantm um mapa dos endereos ainda no determinados, que atualizado continuamente, voltando-se atrs no programa e acertando-se os offsets cada vez que um endereo puder ser determinado. A primeira tcnica tem a vantagem de ser mais simples, muito embora acabe tornando a compilao mais lenta pelo passo extra na obteno de endereos. Alm disso, em linguagens em que no seja necessrio fazer pr-declarao de variveis, temos que o compilador de dois passos necessrio por permitir a realizao da anlise semntica apenas no segundo passo, quando obrigatoriamente todas as variveis e endereos destino j estariam com seus valores definidos. A segunda tcnica tenta corrigir o problema de tempo a mais gasto com a segunda passagem pelo cdigo. Entretanto a necessidade da execuo do backpatching (volta atrs para corrigir os valores de offset que estavam indicados nas operaes de salto) faz com que o sistema se torne um pouco mais complexo do que o compilador de dois passos. Este mtodo prefervel se a linguagem for estruturada e no permitir a declarao de variveis posterior ao uso, tais como pascal e/ou C. De qualquer forma, o problema de determinar endereos que esto adiante no fluxo linear de instrues o maior complicador no momento de se gerar cdigo que represente estruturas que tenham saltos e ou ciclos de repetio.

5.6 Tratamento de subrotinas


No existe muita diferena entre o tratamento de uma subrotina daquele dado a uma estrutura condicional. Na realidade, podemos considerar uma chamada de subrotina como sendo um desvio incondicional a uma regio mais distante do cdigo, da qual haver num determinado momento o retrno para a posio seguinte posio de onde ocorreu o desvio. Isto implica na necessidade do uso de uma instruo que permita o armazenamento na pilha de um endereo de retrno, o que feito pela instruo Call na nossa mquina em particular. Com este tipo de instruo, durante o desvio temos que armazenar na pilha o endereo de retorno, o que nos permite ento chamar a subrotina de qualquer ponto do programa, podendo retornar ao ponto de chamada sem maiores problemas. OBS-- A seo abaixo apenas para referncia futura

5.7 Gerao de cdigo atravs da gramtica de atributos


Podemos automatizar o processo de gerao de cdigo usando para isso a estrutura de gramtica de atributos, introduzida no captulo anterior. O processo bastante simples, pois basta criar um novo smbolo no-terminal Z, representando o endereo de uma instruo, que vai ser anexado s demais produes da gramtica, de forma a poder transferir o atributo endereo (sintetizado a partir de uma funo predicado associada produo Z -> \lambda). Como Z apenas vai aparecer do lado esquerdo na produo dada acima, temos que a gramtica no alterada. Em compensao ganhamos a possibilidade de sintetizar os endereos de forma automtica na gramtica, o que nos permite tambm a gerao de cdigo de forma automtica, sem tediosa manipulao de cdigo manualmente.

Vous aimerez peut-être aussi