Este artigo tem a finalidade de apresentar o processo de geração de código intermediário e otimização em compiladores, sendo necessário para tanto, mostrar de que forma os mesmos funcionam.
1. COMPILADORES: Geração de Código Intermediário
Elias Paulino Augusto¹
Lidiane Marcelino de Carvalho²
Phillipe Gandini Baldim³
Rafaela Zanin Ferreira4
RESUMO
Este artigo tem a finalidade de apresentar o processo de geração de código
intermediário e otimização em compiladores, sendo necessário para tanto, mostrar de que
forma os mesmos funcionam.
Segundo Price (2001), a geração de código intermediário é a transformação da árvore de
derivação em um segmento de código. Esse código pode, eventualmente, ser o código
objeto final, mas na maioria das vezes, constitui-se num código intermediário, pois a
tradução de código fonte para objeto em mais de um passo apresenta algumas vantagens.
Palavras-chave: Compilador. Linguagem de Programação. Código Intermediário.
1 INTRODUÇÃO
Este projeto tem como intuito de nos proporcionar a experiência de construção de
uma pesquisa, com um tema em uma área da computação que esteja cursando no semestre
atual. Nos proporcionando assim a análise e a compreensão do tema da matéria escolhida,
no caso da nossa equipe a matéria escolhida foi a de Compiladores, tendo como tema
Geração de Código Intermediário.
Um tradutor é um programa que recebe como dado de entrada um programa escrito
em uma linguagem de programação (a linguagem fonte) e produz como saída de seu
processamento um programa escrito em outra linguagem (a linguagem objeto). Se a
linguagem fonte é uma linguagem de alto nível como Fortran, Pascal ou C, e a linguagem
1Elias Paulino Augusto, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: elias_sgs@hotmail.com
2 Lidiane Marcelino de Carvalho, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de
2012. E-mail: lidianenep@hotmail.com
3 Phillipe Gandini Baldim, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012.
E-mail: phillipe_baldim@hotmail.com
4 Rafaela Zanin Ferreira, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: rafa_zf@hotmail.com
2. objeto é uma linguagem de baixo nível como a linguagem de montagem (assembly) ou de
máquina o tradutor é chamado de compilador.
O Compilador é um programa que lê um código escrito em uma linguagem fonte, e o traduz
em um programa equivalente numa outra linguagem, a objeto; e são ferramentas
indispensáveis para programação.
Hoje, mais de 50 anos depois dessa concepção que originou o desenvolvimento de
compiladores, a situação ainda está longe de ser ideal. Há uma grande diversidade de
linguagens de programação e ainda há especialistas numa ou noutra linguagem. Mas,
certamente, sem compiladores o desenvolvimento da programação estaria num estágio mais
primitivo do que o atual. O Compilador traz um código fonte escrito numa linguagem de
alto nível, em um código semanticamente equivalente, escrito em código de máquina.
Assim esse código alvo será enfim lido pela máquina e executado, como pode ser visto na
figura 01.
Figura 01 – Estrutura de um Compilador
Fonte: (http://campeche.inf.furb.br/tccs/2002-I/2002-1paulohenriquedasilvavf.pdf)
A geração de código intermediário é a transformação da árvore de derivação em um seg-
mento de código. Esse código pode, eventualmente, ser o código objeto final, mas, na mai-
oria das vezes, constitui-se num código intermediário.
2 ESTRUTURA DE UM COMPILADOR
Como dito anteriormente, um compilador recebe como entrada um programa fonte e
produz como saída um programa objeto na forma de um conjunto de instruções em
linguagem de máquina. Esse processo é tão complexo que não é razoável, do ponto de vista
lógico e de implementação, considerá-lo como sendo desenvolvido em um único passo. Por
essa razão, costuma-se repartir o processo de compilação em uma série de sub-processos
chamados fases, como podemos ver na figura 02.
1Elias Paulino Augusto, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: elias_sgs@hotmail.com
2 Lidiane Marcelino de Carvalho, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de
2012. E-mail: lidianenep@hotmail.com
3 Phillipe Gandini Baldim, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012.
E-mail: phillipe_baldim@hotmail.com
4 Rafaela Zanin Ferreira, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: rafa_zf@hotmail.com
3. Figura 02 – Estrutura de um compilador
Fonte: (http://www.facom.ufms.br/~ricardo/Courses/CompilerI-2009/Lectures/CompilersI_Lec01_Intro.pdf)
Uma fase é uma operação que toma como entrada uma representação do programa
fonte e produz como saída uma outra representação.
Um compilador pode ser dividido em dois componentes principais a análise e síntese. A
análise (front-end) recebe o programa fonte, verifica as partes desse programa e impõe uma
estrutura gramatical sobre elas.
A síntese (back-end) recebe uma representação intermediária do programa e constrói o
programa para a máquina alvo.
1Elias Paulino Augusto, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: elias_sgs@hotmail.com
2 Lidiane Marcelino de Carvalho, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de
2012. E-mail: lidianenep@hotmail.com
3 Phillipe Gandini Baldim, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012.
E-mail: phillipe_baldim@hotmail.com
4 Rafaela Zanin Ferreira, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: rafa_zf@hotmail.com
4. O processo de compilação opera em uma sequência de fases, onde em cada fase a
representação do programa é modificada. Porém, as fases de um compilador não são
necessariamente em sequências e ainda algumas fases podem não existir.
2.1 Geração de Código Intermediário
Certos tipos de tradutores transformam uma linguagem de programação em uma
linguagem simplificada, chamada de código intermediário, que pode ser diretamente
executado por um programa chamado interpretador. Nós podemos imaginar o código
intermediário como uma linguagem de máquina de um computador abstrato projetado para
executar o código fonte. Por exemplo, o Basic, Prolog e Java, são frequentemente
interpretadas.
Na fase de geração de código intermediário, ocorre a transformação da árvore
sintática em uma representação intermediária do código fonte. Esta linguagem
intermediária é mais próxima da linguagem objeto do que o código fonte, mas ainda
permite uma manipulação mais fácil do que se código Assembly ou código de máquina
fosse utilizado.
A geração de código intermediário pode estar estruturalmente distribuída nas fases
anteriores (análise sintática e semântica) ou mesmo não existir (tradução direta para código
objeto), no caso de tradutores bem simples.
No modelo de análise e síntese de um compilador, os módulos da vanguarda
traduzem o programa-fonte numa representação intermediária, a partir da qual os módulos
da retaguarda geram códigos-alvo. Apesar de se poder traduzir o programa-fonte
diretamente na linguagem alvo. Alguns dos benefícios em se usar forma intermediária
independente da máquina são:
1 – possibilita a otimização do código intermediário, de modo a obter-se o código
objeto final mais eficiente;
2 – simplifica a implementação do compilador, resolvendo, gradativamente, as
dificuldades da passagem de código fonte para objeto (alto-nível para baixo nível), já que o
1Elias Paulino Augusto, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: elias_sgs@hotmail.com
2 Lidiane Marcelino de Carvalho, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de
2012. E-mail: lidianenep@hotmail.com
3 Phillipe Gandini Baldim, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012.
E-mail: phillipe_baldim@hotmail.com
4 Rafaela Zanin Ferreira, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: rafa_zf@hotmail.com
5. código fonte pode ser visto como um texto condensado que “explode” em inúmeras
instruções elementares de baixo nível;
3 – possibilita a tradução de código intermediário para diversas máquinas.
A desvantagem de gerar código intermediário é que o compilador requer um passo a
mais. A tradução direta do código fonte para o objeto leva a uma compilação mais rápida.
A grande diferença entre código intermediário e o código objeto final é que o
intermediário não especifica detalhes da máquina alvo, tais como quais registradores serão
usados, quais endereços de memória serão referenciados, etc.
2.1.2 Linguagens Intermediárias
Price (2001) apresenta três categorias, nos quais os vários tipos de código
intermediários se encaixam:
1 – representações gráficas: árvore e grafo sintaxe;
2 – notação pós-fixada ou pré-fixada
3- código de três endereços: quádruplas e triplas.
Uma árvore sintática mostra a estrutura hierárquica de um programa fonte, é o
processo de determinar se uma cadeia de símbolos léxicos pode ser gerada por uma
gramática. Os três símbolos representando A + B poderiam ser agrupados em uma estrutura
sintática chamada de expressão; expressões poderiam ser posteriormente agrupadas para
formar comandos, e assim por diante.
Já a notação pós-fixa é uma representação linearizada de uma árvore sintática; é
uma lista dos nós da árvore na qual um nó aparece imediatamente após seus filhos. Sendo a
notação pós-fixa para a árvore sintática a b c uminus * b c uminus * + assign.
O atrativo da notação pós-fixa é que ela dispensa o uso de parênteses ao adotar a
noção de pilha das expressões. Assim, quando um operador binário aparece na sequência
nessa representação, ele é aplicado aos dois últimos elementos e o resultado de sua
aplicação está disponível como um novo elemento. Há algumas calculadoras eletrônicas
que adotam esse formato para sua operação.
1Elias Paulino Augusto, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: elias_sgs@hotmail.com
2 Lidiane Marcelino de Carvalho, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de
2012. E-mail: lidianenep@hotmail.com
3 Phillipe Gandini Baldim, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012.
E-mail: phillipe_baldim@hotmail.com
4 Rafaela Zanin Ferreira, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: rafa_zf@hotmail.com
6. O código de três endereços é composto por uma sequência de instruções envolvendo
operações binárias ou unárias e uma atribuição. O nome "três endereços" está associado a
especificação, em uma instrução, tendo máximo três variáveis: duas para os operadores
binários e uma para o resultado, na figura 03, abaixo pode ser visto a sua representação.
Figura 03 – Representação em triplas
Fonte: (Silva, 2001, p.40.)
Assim, expressões envolvendo diversas operações são decompostas nesse código
em uma série de instruções, eventualmente com a utilização eventualmente com a
utilização de variáveis temporárias introduzidas na tradução. Dessa forma, obtém-se um
código mais próximo da estrutura da linguagem assembly e, consequentemente, de mais
fácil conversão para a linguagem-alvo.
No exemplo abaixo A, B e C representam endereços de variáveis, op representa
operador (binário ou unário), oprel representa operador relacional, e L representa o rótulo
de uma instrução intermediária.
A:= B op C; A:= op B; A:= B; Goto L; If A oprel B goto L
O comando de atribuição a := x + y * z traduzido para o código de três endereços é
o apresentado no exemplo abaixo, onde T1 e T2 são variáveis temporárias.
T1 ;= Y * Z; T2 := X + T1; A := T2
2.1.3 Tipos de Enunciados de Três Endereços
Os enunciados de três endereços são semelhantes ao código de montagem. Os
enunciados podem ter rótulos simbólicos e existem enunciados para o fluxo de controle.
Um rótulo simbólico representa o índice de um enunciado de três endereços num array que
1Elias Paulino Augusto, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: elias_sgs@hotmail.com
2 Lidiane Marcelino de Carvalho, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de
2012. E-mail: lidianenep@hotmail.com
3 Phillipe Gandini Baldim, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012.
E-mail: phillipe_baldim@hotmail.com
4 Rafaela Zanin Ferreira, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: rafa_zf@hotmail.com
7. abriga o código intermediário. Os índices efetivos podem substituir os rótulos, quer
realizando-se uma passagem separada, que utilizando-se a “retrocorreção”.
O código de três endereços é composto por uma sequência de instruções envolvendo
operações binárias ou unárias e uma atribuição. O nome “três endereços” está associado à
especificação, em uma instrução, de no máximo três variáveis: duas para os operadores
binários e uma para o resultado. Assim, expressões envolvendo diversas operações são
decompostas nesse código em uma série de instruções, eventualmente com a utilização de
variáveis temporárias introduzidas na tradução. Dessa forma, obtêm-se um código mais
próximo da estrutura Assembly e, consequentemente, de mais fácil conversão para a
linguagem-alvo.
2.1.4 Retrocorreção
Segundo Aho (1995), a forma mais fácil de implementar as definições dirigidas pela
sintaxe é usar duas passagens. Primeiro constrói-se uma árvore sintática para a entrada e,
em seguida, caminha-se na árvore numa ordem de busca em profundidade, computando as
traduções dadas na definição. O principal problema de se gerar código para expressões
boolenas e para o fluxo de controle numa única passagem está em que, durante a mesma,
não se pode conhecer os rótulos para onde o controle deve ir, no tempo em que os
enunciados de desvio são gerados. Pode-se solucionar esse problema pela geração de uma
série de enunciados de ramificação, com os alvos dos desvios deixados temporariamente
não especificados. Cada enunciado será colocado numa lista de enunciados de desvio cujos
rótulos serão preenchidos quando o rótulo apropriado puder ser determinado. Aho (1995)
define esse preenchimento subsequente de rótulos de retrocorreção, do inglês backpatching.
2.1.5 Otimização do Código
A etapa final na geração de código pelo compilador é a fase de otimização, a fase de
otimização tenta melhorar o código intermediário, de forma que venha resultar um código
1Elias Paulino Augusto, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: elias_sgs@hotmail.com
2 Lidiane Marcelino de Carvalho, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de
2012. E-mail: lidianenep@hotmail.com
3 Phillipe Gandini Baldim, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012.
E-mail: phillipe_baldim@hotmail.com
4 Rafaela Zanin Ferreira, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: rafa_zf@hotmail.com
8. de máquina mais rápido em tempo de execução. Existem otimizações das mais básicas até
as mais complexas.
O objetivo da etapa de otimização de código é aplicar um conjunto de técnicas para detectar
tais sequências e substituí-las por outras que removam as situações de ineficiência.
Como o código gerado através da tradução orientada a sintaxe contempla expressões
independentes, diversas situações contendo sequências de código ineficiente podem
ocorrer.
As técnicas de otimização que são usadas em compiladores devem, além de manter o
significado do programa original, ser capazes de capturar a maior parte das possibilidades
de melhoria do código dentro de limites razoáveis de esforço gasto para tal fim. Em geral,
os compiladores usualmente permitem especificar qual o grau de esforço desejado no
processo de otimização.
As técnicas de otimização podem ser classificadas como independentes de máquina,
quando podem ser aplicadas antes da geração do código na linguagem Assembly, ou
dependentes de máquina, quando aplicadas na geração do código Assembly.
A otimização independente de máquina tem como requisito o levantamento dos blocos de
comandos que compõem o programa. Essa etapa da otimização é conhecida como a análise
de fluxo, que por sua vez contempla a análise de fluxo de controle e a análise de fluxo de
dados. Estratégias que podem ser aplicadas analisando um único bloco de comandos são
denominadas estratégias de otimização local, enquanto aquelas que envolvem a análise
simultânea de dois ou mais blocos são denominadas estratégias de otimização global.
3 CONCLUSÃO
Portanto, com a análise e compreensão do tema abordado chegamos ao final dele
com a capacidade de identificar e formular as melhores soluções para os problemas
encontrados, bem como o desenvolvimento da capacidade de trabalhar em equipe.
Além disso, a realização dessa pesquisa acadêmica nos auxilia posteriormente na
elaboração do nosso trabalho de conclusão de curso - TCC.
1Elias Paulino Augusto, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: elias_sgs@hotmail.com
2 Lidiane Marcelino de Carvalho, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de
2012. E-mail: lidianenep@hotmail.com
3 Phillipe Gandini Baldim, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012.
E-mail: phillipe_baldim@hotmail.com
4 Rafaela Zanin Ferreira, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: rafa_zf@hotmail.com
9. No entanto, existem, portanto muitas e variadas otimizações que podem ser utilizadas, e o
seu devido conhecimento e compreensão permitem ao programador produzir um código
mais eficiente. Principalmente, assim deve-se ter a noção do impacto da otimização no
código, o uso errado de otimizações pode resultar numa degradação de performance.
COMPULERS: Intermediate Code Generation
ABSTRACT
This article aims to present the process of generating intermediate code in compilers
and optimization, it is necessary to do so, show how they work.
According to Price (2001), intermediate code generation is the transformation tree
branch in a code segment. This code may eventually be the final object code, but most of
the time, constitutes a intermediate code, for the translation of source code to object on
more than one step has some advantages.
Keywords: Compiler. Programming Language. Intermediate Code.
4 REFERÊNCIAS
AHO, V Alfred, Ravi Sethi, and J. D. Ullman. Compiladores: Princípios, Técnicas e
Ferramentas. Rio de Janeiro, 1995. Editora Guanabara Koogan.
FAVARO, Anderson. Compiladores – Gerador de código Intermediário, 2010.
Disponível em: <http://www.slideshare.net/favaro/cfakepathcompiladores-aula06> Acesso
29 Agosto 2012.
LEHRER, Cristiano. Geração de Código Intermediário. Disponível em:
<http://www.ybadoo.com.br/ead/cmp/06/CMP_slides.pdf> Acesso 29 Agosto 2012.
PALAZZO, Luiz A M. Introdução á Compilação. Pelotas, 2001. Disponível em :
<http://ia.ucpel.tche.br/~lpalazzo/Aulas/LFA/LFA-T06.htm> Acesso 29 Agosto 2012.
1Elias Paulino Augusto, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: elias_sgs@hotmail.com
2 Lidiane Marcelino de Carvalho, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de
2012. E-mail: lidianenep@hotmail.com
3 Phillipe Gandini Baldim, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012.
E-mail: phillipe_baldim@hotmail.com
4 Rafaela Zanin Ferreira, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: rafa_zf@hotmail.com
10. PETER. Introdução Compiladores. 2005.Disponível em:
<http://www.dsc.ufcg.edu.br/~peter/cursos/cc/material/p1-introducao-2p.pdf> Acesso 29
Agosto 2012.
RICARTE, Ivan. Introdução à Compilação. 2008, Elsevier Editora LTDA.
RICARTE, M. L. Ivan. Otimização de Código. 2003. Disponível em:
<http://www.dca.fee.unicamp.br/cursos/EA876/apostila/HTML/node76.html> Acesso 10
Outubro 2012.
SANTOS, Ricardo. Compiladores I. Disponível em:
<http://www.facom.ufms.br/~ricardo/Courses/CompilerI-
2009/Lectures/CompilersI_Lec01_Intro.pdf> 2009. Acesso em 10 de Outubro de 2012.
SILVA, Paulo Henrique. Implementação de Unidades Para Processos Concorrentes no
Ambiente Furbol. Blumenau, 2002. Disponível em:
<http://campeche.inf.furb.br/tccs/2002-I/2002-1paulohenriquedasilvavf.pdf> Acesso 29
Agosto 2012.
1Elias Paulino Augusto, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: elias_sgs@hotmail.com
2 Lidiane Marcelino de Carvalho, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de
2012. E-mail: lidianenep@hotmail.com
3 Phillipe Gandini Baldim, aluno do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012.
E-mail: phillipe_baldim@hotmail.com
4 Rafaela Zanin Ferreira, aluna do 6º período de Ciência da Computação do Centro Universitário do Sul de Minas. Setembro de 2012. E-
mail: rafa_zf@hotmail.com