Escolar Documentos
Profissional Documentos
Cultura Documentos
Construindo Compiladores
Keith D. Cooper
Linda Torczon
Segunda edio
Traduo
Daniel Vieira
Reviso Tcnica
Edson Senne
CDD: 005.453
CDU: 004.4422
Construindo Compiladores aumenta seu valor como livro-texto com uma estrutura mais regular
e consistente, e uma srie de recursos de instruo: perguntas de reviso, exemplos extras, notas
em destaque e na margem. Tambm inclui diversas atualizaes tcnicas, incluindo mais sobre
linguagens no tradicionais, compiladores do mundo real e usos no tradicionais da tecnologia
de compiladores. O material sobre otimizao j uma assinatura forte tornou-se ainda mais
acessvel e claro.
Michael L. Scott
Professor do Departamento de Cincia da Computao da Universidade de Rochester
Autor de Programming Language Pragmatics
Keith Cooper e Linda Torczon apresentam um tratamento efetivo da histria e tambm um ponto
de vista do profissional sobre como os compiladores so desenvolvidos. A apresentao da
teoria, bem como de exemplos prticos do mundo real de compiladores existentes (com, LISP,
FORTRAN etc.), incluem diversas discusses e ilustraes eficazes. A discusso completa dos
conceitos, tanto introdutrios quanto avanados, de alocao e otimizao abrangem um
ciclo de vida eficaz da engenharia de compiladores. Este texto deve estar em toda prateleira
de alunos de cincia da computao, e tambm na de profissionais envolvidos com a engenharia
e o desenvolvimento de compiladores.
David Orleans
Nova Southeastern University
Sobre os Autores
Keith D. Cooper Professor Doerr de Engenharia da Computao na Rice University. Trabalhou
com uma grande coleo de problemas em otimizao de cdigo compilado, incluindo anlise de
fluxo de dados interprocedural e suas aplicaes, numerao de valores, reassociao algbrica,
alocao de registradores e escalonamento de instrues. Seu trabalho recente tem se concentrado
em um reexame fundamental da estrutura e do comportamento de compiladores tradicionais.
Tem lecionado em diversos cursos em nvel de graduao, desde introduo programao
at otimizao de cdigo em nvel de ps-graduao. associado da ACM (Association for
Computing Machinery).
Linda Torczon, Cientista de Pesquisa Snior do Departamento de Cincia da Computao na
Rice University, a principal investigadora do projeto PACE (Platform-Aware Compilation
Environment), patrocinado pela DARPA, que est desenvolvendo um ambiente de compilador
otimizador que ajusta automaticamente suas otimizaes e estratgias a novas plataformas. De
1990 a 2000, a Dra. Torczon trabalhou como diretora executiva do Center for Research on Parallel
Computation (CRPC), um Centro de Cincia e Tecnologia da National Science Foundation.
Tambm trabalhou como diretora executiva da HiPerSoft, do Instituto de Cincia da Computao
de Los Alamos e do projeto Virtual Grid Application Development Software (VGrADS).
Sobre a Capa
A capa deste livro contm uma parte do desenho The Landing of the Ark, que decora o teto
do Duncan Hall, na Rice University. Tanto o Duncan Hall quanto seu teto foram projetados pelo
arquiteto britnico John Outram. Duncan Hall uma expresso visvel dos temas arquitetnicos,
decorativos e filosficos desenvolvidos durante a carreira de Outram como arquiteto. O teto
decorado do salo cerimonial desempenha papel central no esquema decorativo do prdio. Outram
inscreveu o teto com um conjunto de ideias significativas um mito da criao. Expressando
essas ideias em um desenho alegrico de to grande tamanho e intensidade de cores, Outram
criou uma indicao que diz aos visitantes que entram no salo que, na realidade, esse prdio
no como outros.
Usando a mesma indicao na capa de Construindo Compiladores, os autores desejam sinalizar
que este trabalho contm ideias significativas que esto no centro de sua disciplina. Assim como o
prdio de Outram, este volume o auge de temas intelectuais desenvolvidos durante as carreiras
profissionais dos autores. Como o esquema decorativo de Outram, este livro um mecanismo para
comunicar ideias, que, como o teto de Outram, apresenta ideias significativas de novas maneiras.
Conectando o projeto e a construo de compiladores com o projeto e a construo de prdios,
nossa inteno transmitir as muitas semelhanas nessas duas atividades distintas. Nossas muitas
e longas discusses com Outram nos fizeram conhecer os ideais vitruvianos para a arquitetura:
comodidade, firmeza e deleite. Esses ideais aplicam-se a muitos tipos de construo. Seus
equivalentes para a construo de compiladores so temas consistentes deste texto: funo,
estrutura e elegncia. A funo importa; um compilador que gera cdigo incorreto intil. A
estrutura importa; o detalhe de engenharia determina a eficincia e a robustez de um compilador.
A elegncia importa; um compilador bem projetado, em que os algoritmos e estruturas de dados
fluem tranquilamente de um passo para outro, pode ser uma expresso de beleza.
Estamos encantados por ter a graa do trabalho de John Outram como capa deste livro.
O teto do Duncan Hall um artefato tecnolgico interessante. Outram desenhou o projeto original
em uma folha de papel, que foi fotografado e digitalizado em 1200 dpi, gerando aproximadamente
750 MB de dados. A imagem foi ampliada para formar 234 painis distintos de 0,6 x 2,4m,
criando uma imagem de 16 x 22m. Os painis foram impressos em folhas de vinil perfurado
usando uma impressora de tinta acrlica de 12 dpi. Essas folhas foram montadas com preciso
em ladrilhos acsticos de 0,6 x 2,4m e afixadas estrutura de alumnio da abbada.
bem-sucedido precisa estar acostumado com as tcnicas de melhores prticas atuais em otimizao, como a construo do formato de atribuio nica esttica, e em gerao de cdigo, como
o enfileiramento (pipelining) de software. E tambm precisam ter a base e o discernimento para
entender novas tcnicas medida que aparecerem durante os prximos anos. Finalmente, eles
precisam entender bem o suficiente as tcnicas de scanning (anlise lxica), parsing (anlise
sinttica) e elaborao semntica para construir ou modificar um front end.
Nosso objetivo para este livro tem sido criar um texto e um curso que exponha os alunos s
questes crticas dos compiladores modernos e oferecer-lhes uma base para enfrentar esses
problemas. Mantivemos, da primeira edio, o equilbrio bsico do material. Os front ends so
componentes de consumo; e podem ser comprados de um vendedor confivel ou adaptadosde
um dos muitos sistemas de cdigo aberto. Ao mesmo tempo, os otimizadores e os geradores
de cdigo so preparados para processadores especficos e, s vezes, para modelos individuais,
pois o desempenho conta muito com os detalhes especficos de baixo nvel do cdigo gerado.
Esses fatos afetam o modo como criamos compiladores hoje; e tambm devem afetar o modo
como ensinamos construo de compiladores.
Organizao
Este livro divide o material em quatro sees principais praticamente de mesmo tamanho:
A primeira, Captulos de2 a4, aborda tanto o projeto do front end de um compilador
quanto o projeto e a construo de ferramentas para construir front ends.
A segunda, Captulos de5 a7, explora o mapeamento do cdigo fonte para o formato
intermedirio do compilador ou seja, esses captulos examinam o tipo de cdigo que o
front end gera para o otimizador e o back end.
A terceira, Captulos de8 a10, apresenta o assunto de otimizao de cdigo. O Captulo8
oferece uma viso geral da otimizao. Os Captulos9 e10 contm tratamentos mais
profundos de anlise e transformao; estes dois captulos so muitas vezes omitidos em
um curso de graduao.
A final, Captulos de11 a13, concentra-se nos algoritmos usados no back end do compilador.
proveito de algoritmos velozes de reconhecimento de padres para mapear computaes abstratas em operaes em nvel de mquina. E usam equaes diofantinas lineares e a aritmtica
de Pressburger para analisar subscritos de vetores (arrays). Finalmente, os compiladores usam
um grande conjunto de algoritmos clssicos e estruturas de dados como tabelas hash, algoritmos
de grafos e implementaes de conjuntos esparsos.
Neste livro, tentamos transmitir tanto a arte quanto a cincia da construo de compiladores. O
livro inclui uma seleo de material suficientemente ampla para mostrar ao leitor que existem
dilemas reais e que o impacto das decises de projeto pode ser tanto sutil e quanto extenso. Ao
mesmo tempo, o livro omite algumas tcnicas que h muito tm sido parte de um curso de graduao em construo de compiladores, mas que se tornaram menos importantes pelas mudanas
no mercado, na tecnologia das linguagens e compiladores ou na disponibilidade de ferramentas.
Abordagem
A construo de compiladores um exerccio de projeto de engenharia. O construtor de compiladores precisa escolher um caminho atravs de um espao de projeto que repleto de alternativas
diversas, cada uma com diferentes custos, vantagens e complexidade. Cada deciso tem impacto
sobre o compilador resultante. A qualidade do produto final depende de decises bem feitas em
cada etapa ao longo do caminho.
Assim, no existe uma nica resposta certa para muitas das decises de projeto em um compilador. At mesmo dentro de problemas bem entendidos e resolvidos nuances no projeto e
implementao tm impacto sobre o comportamento do compilador e a qualidade do cdigo que
produz. Muitas consideraes devem ser feitas em cada deciso. Como exemplo, a escolha de
uma representao intermediria para o compilador tem um impacto profundo sobre o restante
do compilador, desde os requisitos de tempo e espao at a facilidade com que diferentes algoritmos podem ser aplicados. A deciso, porm, tratada muitas vezes com pouca considerao.
O Captulo5 examina o espao das representaes intermedirias e algumas das questes que
devem ser consideradas na seleo de uma delas. Levantamos a questo novamente em diversos
pontos no livro tanto diretamente, no texto, quanto indiretamente, nos exerccios.
Este livro explora o espao de projeto e transmite tanto a profundidade dos problemas quanto a
amplitude das possveis solues. Mostra algumas das formas como esses problemas tm sido
resolvidos, juntamente com as restries que tornaram essas solues atraentes. Os construtores
de compiladores precisam entender tanto os problemas quanto suas solues, alm do impacto
dessas decises sobre outras facetas do projeto do compilador. Pois s assim podem fazer escolhas informadas e inteligentes.
Filosofia
Este texto expe nossa filosofia para a construo de compiladores, desenvolvida durante mais
de vinte e cinco anos, cada um, com pesquisa, ensino e prtica. Por exemplo, representaes
intermedirias devem expor aqueles detalhes que importam no cdigo final; essa crena leva a
uma tendncia para representaes de baixo nvel. Os valores devem residir em registradores at
que o alocador descubra que no pode mant-los l; esta prtica produz exemplos que utilizam
registradores virtuais e armazenam valores na memria somente quando isso no pode ser evitado.
Todo compilador deve incluir otimizao; isto simplifica o restante do compilador. Nossas experincias durante os anos tm inspirado a seleo de material e sua apresentao.
Agradecimentos
Muitas pessoas foram envolvidas na preparao da primeira edio deste livro. Suas contribuies
foram transportadas para esta segunda edio. Muitas indicaram problemas na primeira edio,
incluindo Amit Saha, Andrew Waters, Anna Youssefi, Ayal Zachs, Daniel Salce, David Peixotto,
Fengmei Zhao, Greg Malecha, Hwansoo Han, Jason Eckhardt, Jeffrey Sandoval, John Elliot,
Kamal Sharma, Kim Hazelwood, Max Hailperin, Peter Froehlich, Ryan Stinnett, Sachin Rehki,
Sagnak Tasrlar, Timothy Harvey e Xipeng Shen. Tambm queremos agradecer aos revisores da
segunda edio, Jeffery von Ronne, Carl Offner, David Orleans, K. Stuart Smith, John Mallozzi,
Elizabeth White e Paul C. Anagnostopoulos. A equipe de produo na Elsevier, em particular
Alisa Andreola, Andre Cuello e Megan Guiney, tiveram uma participao crtica na converso
de um manuscrito bruto para sua forma final. Todas essas pessoas melhoraram este volume de
formas significativas com suas ideias e sua ajuda.
Finalmente, muitas pessoas nos deram suporte intelectual e emocional durante os ltimos cinco
anos. Em primeiro lugar, nossas famlias e nossos colegas na Rice University nos encorajaram a
cada passo do caminho. Christine e Carolyn, em particular, aguentaram milhares de longas discusses sobre tpicos de construo de compiladores. Nate McFadden guiou esta edio desde seu
incio at sua publicao, com muita pacincia e bom humor. Penny Anderson ofereceu suporte
administrativo e organizacional, que foi crtico para a concluso da segunda edio. Para todas
elas vo nossos sinceros agradecimentos.
Captulo
1.1 INTRODUO
A funo do computador na vida diria cresce a cada ano. Com a ascenso da internet,
computadores e o software neles executados fornecem comunicaes, notcias, entretenimento e segurana. Computadores embutidos tm modificado as formas como
construmos automveis, avies, telefones, televises e rdios. A computao tem
criado categorias de atividades completamente novas, de videogames a redes sociais.
Supercomputadores predizem o estado atmosfrico dirio e o curso de tempestades
violentas. Computadores embutidos sincronizam semforos e enviam e-mail para o
seu celular.
Todas essas aplicaes contam com programas de computador (software) que constroem ferramentas virtuais em cima de abstraes de baixo nvel fornecidas pelo
hardware subjacente. Quase todo este software traduzido por uma ferramenta chamada compilador, que simplesmente um programa de computador que traduz outros programas a fim de prepar-los para execuo. Este livro apresenta as tcnicas
fundamentais de traduo automtica usadas para construir compiladores. Descreve
muito dos desafios que surgem nesta construo e os algoritmos que seus construtores
usam para enfrent-los.
Compilador
Programa de computador que traduz outros programas.
Roteiro conceitual
Compilador uma ferramenta que traduz software escrito em uma linguagem para
outra. Para esta traduo, a ferramenta deve entender tanto a forma (ou sintaxe) quanto
o contedo (ou significado) da linguagem de entrada, e, ainda, as regras que controlam
a sintaxe e o significado na linguagem de sada. Finalmente, precisa de um esquema de
mapeamento de contedo da linguagem-fonte para a linguagem-alvo.
A estrutura de um compilador tpico deriva dessas observaes simples. O compilador
tem um front end para lidar com a linguagem de origem, e um back end para lidar com
a linguagem-alvo. Ligando ambos, ele tem uma estrutura formal para representar o
programa num formato intermedirio cujo significado , em grande parte, independente
de qualquer linguagem. Para melhorar a traduo, compiladores muitas vezes incluem
um otimizador, que analisa e reescreve esse formato intermedirio.
Viso geral
Programas de computador so simplesmente sequncias de operaes abstratas escritas
em uma linguagem de programao linguagem formal projetada para expressar
computao. Linguagens de programao tm propriedades e significados rgidos ao
contrrio de linguagens naturais, como chins ou portugus , e so projetadas visando
expressividade, conciso e clareza. Linguagem natural permite ambiguidade. Linguagens
de programao so projetadas para evit-la; um programa ambguo no tem significado.
Essas linguagens so projetadas para especificar computaes registrar a sequncia
de aes que executam alguma tarefa ou produzem alguns resultados.
Linguagens de programao so, em geral, projetadas para permitir que os seres
humanos expressem computaes como sequncias de operaes. Processadores de
computador, a seguir referidos como processadores, microprocessadores ou mquinas,
so projetados para executar sequncias de operaes. As operaes que um processador implementa esto, quase sempre, em um nvel de abstrao muito inferior ao
daquelas especificadas em uma linguagem de programao. Por exemplo, a linguagem
de programao normalmente inclui uma forma concisa de imprimir algum nmero
para um arquivo. Esta nica declarao da linguagem deve ser traduzida literalmente
em centenas de operaes de mquina antes que possa ser executada.
A ferramenta que executa estas tradues chamada compilador. O compilador toma
como entrada um programa escrito em alguma linguagem e produz como sada um programa equivalente. Na noo clssica de um compilador, o programa de sada expresso
nas operaes disponveis em algum processador especfico, muitas vezes chamado
mquina-alvo. Visto como uma caixa-preta, um compilador deve ser semelhante a isto:
Conjunto de Instrues
Conjunto de operaes suportadas pelo processador; o
projeto global de um conjunto de instrues muitas
vezes chamado arquitetura de conjunto de instrues ou
ISA (Instruction set architecture).
Linguagens fonte tpicas podem ser C, C++, Fortran, Java, ou ML. Linguagem alvo
geralmente o conjunto de instrues de algum processador.
Alguns compiladores produzem um programa-alvo escrito em uma linguagem de
programao orientada a humanos, em vez da assembly de algum computador. Os
programas que esses compiladores produzem requerem ainda traduo antes que possam ser executados diretamente em um computador. Muitos compiladores de pesquisa
produzem programas C como sada. Como existem compiladores para C na maioria
dos computadores, isto torna o programa-alvo executvel em todos estes sistemas ao
custo de uma compilao extra para o alvo final. Compiladores que visam linguagens
de programao, em vez de conjunto de instrues de um computador, frequentemente
so chamados tradutores fonte a fonte.
Muitos outros sistemas qualificam-se como compiladores. Por exemplo, um programa
de composio tipogrfica que produz PostScript pode assim ser considerado. Ele toma
como entrada uma especificao de como o documento aparece na pgina impressa e
produz como sada um arquivo PostScript. PostScript simplesmente uma linguagem
para descrever imagens. Como este programa assume uma especificao executvel e
produz outra tambm executvel, um compilador.
O cdigo que transforma PostScript em pixels normalmente um interpretador, no
um compilador. Um interpretador toma como entrada uma especificao executvel e
produz como sada o resultado da execuo da especificao.
1.1 Introduo 3
Algumas linguagens, como Perl, Scheme e APL, so, com mais frequncia, implementadas com interpretadores do que com compiladores.
Algumas linguagens adotam esquemas de traduo que incluem tanto compilao
quanto interpretao. Java compilada do cdigo-fonte para um formato denominado
bytecode, uma representao compacta que visa diminuir tempos de download para
aplicativos Mquina Virtual Java. Aplicativos Java so executados usando o bytecode
na Mquina Virtual Java (JVM), um interpretador para bytecode. Para complicar
ainda mais as coisas, algumas implementaes da JVM incluem um compilador, s
vezes chamado de compilador just-in-time, ou JIT, que, em tempo de execuo, traduz
sequncias de bytecode muito usadas em cdigo nativo para o computador subjacente.
Interpretadores e compiladores tm muito em comum, e executam muitas das mesmas
tarefas. Ambos analisam o programa de entrada e determinam se ou no um programa
vlido; constroem um modelo interno da estrutura e significado do programa; determinam onde armazenar valores durante a execuo. No entanto, interpretar o cdigo
para produzir um resultado bastante diferente de emitir um programa traduzido que
pode ser executado para produzir o resultado. Este livro concentra-se nos problemas
que surgem na construo de compiladores. No entanto, um implementador de interpretadores pode encontrar a maior parte do material relevante.
Mquina Virtual
um simulador para um processador, ou seja, um interpretador para o conjunto de instrues da mquina.
IR
Um compilador usa algum conjunto de estruturas de
dados para representar o cdigo que processado. Este
formato chamado representao intermediria, ou IR
(Intermediate Representation).
O compilador pode fazer mltiplas passagens pelo formato IR do cdigo antes de emitir
o programa-alvo. Esta capacidade deve levar a um cdigo melhor, pois o compilador
pode, desta forma, estudar o cdigo em uma fase e registrar detalhes relevantes. Depois,
em outras fases, pode usar os fatos registrados para melhorar a qualidade da traduo.
Esta estratgia requer que o conhecimento derivado na primeira passagem seja registrado na IR, onde outras passagens podem encontr-lo e utiliz-lo.
Retargeting
A tarefa de mudar o compilador para gerar cdigo
para um novo processador frequentemente chamada
retargeting do compilador.
Otimizador
A seo do meio de um compilador, chamada
otimizador, analisa e transforma a IR para melhor-la.
A introduo de uma IR torna possvel acrescentar mais fases compilao. O construtor de compiladores pode inserir uma terceira fase entre o front end e o back end.
Essa seo do meio, ou otimizador, toma um programa em IR como entrada e produz
outro programa em IR semanticamente equivalente como sada. Usando a IR como
interface, o construtor pode inserir esta terceira fase com o mnimo de rompimento
entre o front end e o back end, o que leva estrutura de compilador a seguir, chamada
compilador de trs fases.
O front end consiste em dois ou trs passos que tratam dos detalhes do reconhecimento de programas vlidos na linguagem-fonte e da produo do formato
IR inicial do programa. A seo do meio contm passos que realizam diferentes
otimizaes. A quantidade e a finalidade desses passos variam de um compilador
para outro. O back end consiste em uma srie de passos, cada um levando a IR do
programa mais para perto do conjunto de instrues da mquina-alvo. As trs fases
e seus passos individuais compartilham uma infraestrutura comum, apresentada
na Figura1.1.
Na prtica, a diviso conceitual de um compilador em trs fases front end, seo
intermediria, ou otimizador, e back end til. Os problemas resolvidos por essas fases so diferentes. O front end trata do entendimento do programa-fonte e do
registro dos resultados de sua anlise na forma de IR. A seo do otimizador focaliza
a melhoria do formato IR. O back end precisa mapear o programa transformado em
IR para os recursos limitados da mquina alvo de um modo que leve ao uso eficiente
desses recursos.
Dessas trs fases, o otimizador tem a descrio mais obscura. O termo otimizao
implica que o compilador descobre uma soluo tima para algum problema. As questes e os problemas que surgem nesta fase so to complexos e to inter-relacionados
que no podem, na prtica, ser solucionados de forma tima. Alm do mais, o comportamento real do cdigo compilado depende das interaes entre todas as tcnicas
aplicadas no otimizador e o back end. Assim, mesmo que uma nica tcnica possa ser
comprovadamente tima, suas interaes com outras podem produzir resultados que
no so timos. Como resultado, um bom compilador otimizante pode melhorar a
qualidade do cdigo em relao a uma verso no otimizada, mas quase sempre deixar
de produzir o cdigo timo.
A seo intermediria pode ser um nico passo monoltico que aplica uma ou mais
otimizaes para melhorar o cdigo, ou ser estruturada como uma srie de passos
menores com cada um lendo e escrevendo a IR. A estrutura monoltica pode ser mais
eficiente. A de mltiplos passos, pode servir como uma implementao menos complexa
e um mtodo mais simples de depurar o compilador. Esta tambm cria a flexibilidade
para empregar diferentes conjuntos de otimizao em diferentes situaes. A escolha
entre essas duas tcnicas depende das restries sob as quais o compilador construdo e opera.
NOTAO
Os livros sobre compiladores tratam basicamente de notao. Afinal, um compilador
traduz um programa escrito em uma notao para um programa equivalente escrito
em outra notao. Diversas dvidas de notao surgiro durante sua leitura deste livro.
Em alguns casos, elas afetaro diretamente sua compreenso do material.
Expressando algoritmos Tentamos manter os algoritmos curtos. Algoritmos so
escritos em um nvel relativamente alto, considerando que o leitor possa fornecer
detalhes da implementao, em fonte inclinada, monoespaada. O recuo
deliberado e significativo, mais importante em uma construo if-then-else.
O cdigo recuado aps um then ou um else forma um bloco. No fragmento de
cdigo a seguir
if Action [s,word]=shift si then
push word
push si
word NextWord()
else if. . .
todas as instrues entre then e else fazem parte da clusula then da construo
if-then-else. Quando uma clusula em uma construo if-then-else
contm apenas uma instruo, escrevemos a palavra-chave then ou else na mesma
linha da instruo.
Escrevendo cdigo Em alguns exemplos, mostramos o texto real do programa escrito
em alguma linguagem escolhida para demonstrar um tpico em particular. Este texto
escrito em uma fonte monoespaada.
Operadores aritmticos Por fim, abrimos mo do uso tradicional de * parae de /
para , exceto no texto do programa real. O significado deve ser claro para o leitor.
Verificando a sintaxe
Para verificar a sintaxe do programa de entrada, o compilador precisa comparar a estrutura do programa com uma definio para a linguagem. Isto exige uma definio
formal apropriada, um mecanismo eficiente para testar se a entrada atende ou no esta
definio e um plano de como proceder em uma entrada ilegal.
Sentena
Sujeito
Sujeito
Objeto
Objeto
Modificador
.. .
1
2
5
6
Sentena de prottipo
Sentena
Sujeito verbo Objeto marca de fim
substantivo verbo Objeto marca de fim
substantivo verbo Modificador substantivo marca de fim
substantivo verbo adjetivo substantivo marca de fim
Verificao de tipo
O passo do compilador que verifica a consistncia de
tipo do uso de nomes no programa de entrada.
TERMINOLOGIA
Um leitor atento notar que usamos a palavra cdigo em muitos lugares onde tanto
programa quanto procedimento poderiam se encaixar naturalmente. Os compiladores
podem ser invocados para traduzir fragmentos de cdigo que variam desde uma
nica referncia at um sistema inteiro de programas. Ao invs de especificar algum
escopo de compilao, continuaremos usando o termo ambguo, porm mais
genrico, cdigo.
1.3.2Otimizador
Quando o front end emite a IR para o programa de entrada, ele trata as instrues
uma de cada vez, na ordem em que so encontradas. Assim, o programa inicial em IR
contm estratgias de implementao gerais que funcionaro em qualquer contexto
ao redor do qual o compilador poderia gerar. Em tempo de execuo (runtime),
o cdigo ser executado em um contexto mais restrito e previsvel. O otimizador
analisa o formato IR do cdigo para descobrir fatos sobre esse contexto e usa esse
conhecimento contextual para reescrever o cdigo de modo que calcule a mesma
resposta de maneira mais eficiente.
Eficincia pode ter muitos significados. A noo clssica da otimizao reduzir o
tempo de execuo da aplicao. Em outros contextos, o otimizador poderia tentar
reduzir o tamanho do cdigo compilado ou outras propriedades, como, por exemplo,
a energia que o processador consome para avaliar o cdigo. Todas essas estratgias
visam eficincia.
Retornando ao nosso exemplo, considere-o no contexto mostrado na Figura1.2a. A
declarao ocorre dentro de um lao (ou loop). Dos valores que ele usa, somente a e
d mudam dentro do lao. Os valores 2, b e c so invariantes no lao. Se o otimizador
descobrir este fato, poder reescrever o cdigo como mostra a Figura1.2b. Nesta
verso, o nmero de multiplicaes foi reduzido de 4 .n para 2 .n+2. Para n>1, o
lao reescrito deve ser executado mais rapidamente. Este tipo de otimizao discutido
nos Captulos 8, 9 e 10.
Anlise
A maioria das otimizaes consiste em uma anlise e uma transformao. A anlise
determina onde o compilador pode aplicar a tcnica de forma segura e lucrativa.
Compiladores utilizam vrios tipos de anlise para dar suporte s transformaes.
Anlise de fluxo de dados, em tempo de compilao, raciocina sobre o fluxo de valores
em runtime. Os analisadores de fluxo de dados normalmente resolvem um sistema
Transformao
Para melhorar o cdigo, o compilador precisa ir alm da anlise; necessita usar os
resultados dessa anlise para reescrever o cdigo para um formato mais eficiente.
Inmeras transformaes foram inventadas para melhorar os requisitos de tempo
ou espao do cdigo executvel. Algumas, como descobrir computaes invariantes
no lao e mov-las para locais executados com menos frequncia, melhoram o
tempo de execuo do programa. Outras tornam o cdigo mais compacto. As
transformaes variam em seus efeitos, no escopo sobre as quais operam e na
anlise exigida para lhes dar suporte. A literatura sobre transformaes muito
rica; o assunto abrangente e profundo o suficiente para merecer um ou mais livros
separados. O Captulo10 aborda o tema de transformaes escalares ou seja,
transformaes voltadas para melhorar o desempenho do cdigo em um nico
processador e apresenta uma classificao para organizar o assunto, preenchendo
essa classificao com exemplos.
SOBRE A ILOC
Por todo este livro, os exemplos de baixo nvel so escritos em uma notao que
chamamos de ILOC acrnimo derivado de Intermediate Language for an
Optimizing Compiler. Com o passar dos anos, esta notao passou por vrias
mudanas. A verso usada neste livro descrita com detalhes no Apndice A.
Pense na ILOC como a linguagem assembly para uma mquina RISC simples. Ela
possui um conjunto padro de operaes. A maior parte delas utiliza argumentos que
so registradores. As operaes de memria, loads e stores, transferem valores entre
a memria e os registradores. Para simplificar a exposio no texto, a maior parte dos
exemplos considera que todos os dados consistem em inteiros.
Cada operao tem um conjunto de operandos e um alvo, e escrita em cinco partes:
nome de operao, lista de operandos, separador, lista de alvos e comentrio opcional.
Assim, para somar os contedos dos registradores 1 e 2, deixando o resultado no
registrador 3, o programador escreveria
r1,c2 r3
c1
r2
r1,r2 r3
r1
r2,c3
Significado
MEMORY (r1 +c2) r3
c1 r2
r1r2 r3
r1 MEMORY (r2 +c3)
t0 a2
t1 t0b
t2 t1c
t3 t2d
a t3
Seleo de instrues
O primeiro estgio da gerao de cdigo reescreve as operaes da IR em operaes
da mquina-alvo, processo chamado seleo de instrues, que mapeia cada operao da
IR, em seu contexto, para uma ou mais operaes da mquina-alvo. Considere a reescrita da nossa expresso de exemplo, a a2bcd, em cdigo para a
mquina virtual ILOC, a fim de ilustrar o processo. (Usaremos a ILOC no decorrer do
livro.) O formato IR da expresso repetido ao lado. O compilador poderia escolher
as operaes na Figura1.3. Esse cdigo considera que a, b, c e d esto localizados
nos deslocamentos (offsets) @a, @b, @c e @d a partir de um endereo contido no
registrador rarp.
O compilador escolheu uma sequncia direta de operaes. Ele carrega todos os
valores relevantes em registradores, realiza as multiplicaes em ordem e armazena o
resultado ao local da memria para a. Assume um estoque ilimitado de registradores
e os nomeia com nomes simblicos como r a para manter a e r arp para manter o
endereo onde comea o armazenamento de dados para nossos valores nomeados.
Implicitamente, o seletor de instrues conta com o alocador de registradores para
mapear esses nomes de registrador simblicos, ou registradores virtuais, aos registradores reais da mquina-alvo.
O seletor de instrues pode tirar proveito de operaes especiais na mquina-alvo. Por
exemplo, se uma operao de multiplicao imediata (multI) estiver disponvel, ele
pode substituir a operao mult ra, r2 ra por multI ra, 2 ra, eliminando
a necessidade da operao loadI 2 r2 e reduzindo a demanda por registradores.
Se a adio mais rpida do que a multiplicao, ele pode substituir mult ra, r2
ra por add ra, ra ra, evitando tanto o loadI quanto seu uso de r2, alm de
Registrador virtual
Nome de registrador simblico que o compilador usa
para indicar que um valor pode ser armazenado em um
registrador.
substituir mult por um add mais rpido. O Captulo11 apresenta duas tcnicas
diferentes para a seleo de instrues, que utilizam combinao de padres para escolher implementaes eficientes para operaes em IR.
Alocao de registradores
Durante a seleo de instrues, o compilador deliberadamente ignora o fato de que
a mquina-alvo possui um conjunto limitado de registradores. Ao invs disso, ele usa
registradores virtuais e considera que existem registradores suficientes. Na prtica,
os primeiros estgios da compilao podem criar mais demanda por registradores do
que o hardware consegue aceitar. O alocador de registradores precisa mapear esses
registradores virtuais para os registradores reais da mquina alvo. Assim, este alocador
decide, em cada ponto do cdigo, quais valores devem residir nos registradores da
mquina alvo. Depois, reescreve o cdigo para refletir suas decises. Por exemplo,
um alocador de registrador poderia minimizar o uso de registradores reescrevendo o
cdigo da Figura1.3da seguinte forma:
loadAI
add
loadAI
mult
loadAI
mult
loadAI
mult
storeAI
rarp, @a
r1, r1
rarp, @b
r1, r2
rarp, @c
r1, r2
rarp, @d
r1, r2
r1
r1
r1
r2
r1
r2
r1
r2
r1
rarp, @a
//
//
//
//
//
//
//
//
//
load a
r1 a2
load b
r1 (a2)b
load c
r1 (a2b)c
load d
r1 (a2bc)d
escrever ra de volta para a
Escalonamento de instrues
Para produzir cdigo que seja executado rapidamente, o gerador de cdigo pode
ter que reordenar operaes para refletir as restries de desempenho especficas da
mquina-alvo. O tempo de execuo das diferentes operaes pode variar. As operaes
de acesso memria podem tomar dezenas ou centenas de ciclos, enquanto algumas
operaes aritmticas, particularmente a diviso, exigem vrios ciclos. O impacto
dessas operaes com latncia mais longa sobre o desempenho do cdigo compilado
pode ser substancial.
Suponha, por enquanto, que uma operao loadAI ou storeAI exija trs ciclos;
um mult dois ciclos; e todas as outras operaes um ciclo. A tabela a seguir mostra
como o fragmento de cdigo anterior funciona sob essas suposies. A coluna Incio
mostra o ciclo em que cada operao inicia a execuo, e a coluna Fim o ciclo em
que ela termina.
Incio
1
4
5
8
10
13
15
18
20
Fim
3
4
7
9
12
14
17
19
22
loadAI
add
loadAI
mult
loadAI
mult
loadAI
mult
storeAI
rarp, @a r1
r1, r1 r1
rarp, @b r2
r1, r2 r1
rarp, @c r2
r1, r2 r1
rarp, @d r2
r1, r2 r1
r1
rarp, @a
//
//
//
//
//
//
//
//
//
load a
r1 a2
load b
r1 (a2)b
load c
r1 (a2b)c
load d
r1 (a2bc)d
escrever ra de volta para a
Esta sequncia de nove operaes gasta 22 ciclos para ser executada. A reduo no uso
de registradores no levou a uma execuo rpida.
Muitos processadores tm uma propriedade pela qual podem iniciar novas operaes
enquanto uma de longa latncia executada. Desde que os resultados de uma operao
de longa latncia no sejam referenciados at que a operao termine, a execuo prossegue normalmente. Porm, se alguma operao intermediria tentar ler o resultado de
uma operao de longa latncia prematuramente, o processador adia a operao que
precisa do valor at que a operao de longa latncia termine. Uma operao no pode
comear a executar at que seus operandos estejam prontos, e seus resultados no esto
prontos at que a operao termine.
O escalonador de instrues reordena as operaes no cdigo, e tenta minimizar o
nmero de ciclos desperdiados aguardando pelos operandos. Naturalmente, ele precisa
garantir que a nova sequncia produza o mesmo resultado da original. Em muitos casos,
o escalonador pode melhorar bastante o desempenho de um cdigo simples. Para o
nosso exemplo, um bom escalonador poderia produzir a seguinte sequncia:
Incio
1
2
3
4
5
6
7
9
11
Fim
3
4
5
4
6
8
8
10
13
loadAI
loadAI
loadAI
add
mult
loadAI
mult
mult
storeAI
rarp, @a r1
rarp, @b r2
rarp, @c r3
r1, r1 r1
r1, r2 r1
rarp, @d r2
r1, r3 r1
r1, r2 r1
r1
rarp, @a
//
//
//
//
//
//
//
//
//
load a
load b
load c
r1 a2
r1 (a2)b
load d
r1 (a2b)c
r1 (a2bc)d
escrever ra de volta para a
Esta verso do cdigo exige apenas 13 ciclos para ser executada. O cdigo usa um
registrador a mais que o nmero mnimo. E inicia uma operao em cada ciclo, exceto em 8, 10 e 12. Outros escalonamentos equivalentes so possveis, assim como
escalonamentos de mesmo tamanho que usam mais registradores. O Captulo12
apresenta vrias tcnicas de escalonamento que esto sendo muito utilizadas.
de resolver de forma tima; por isso, eles usam aproximaes, heursticas e regras
prticas, o que produz interaes complexas que levam a resultados surpreendentes
tanto bons quanto ruins.
Para colocar esta atividade em um framework de forma ordenada, a maioria dos
compiladores est organizada em trs fases principais: front end, otimizador e back end.
Cada fase tem um conjunto diferente de problemas para enfrentar, e os mtodos usados
para resolv-los tambm diferem. O front end focaliza a traduo de cdigo-fonte
em alguma IR. Os front ends contam com os resultados da teoria de linguagens formais
e da teoria de tipos, com uma boa dose de algoritmos e estruturas de dados. A seo
do meio, ou otimizador, traduz um programa em IR para outro, com o objetivo de
produzir um programa em IR que possa ser executado de modo eficiente. Otimizadores
analisam programas para derivar conhecimento sobre seu comportamento em tempo de
execuo, e depois utilizam este conhecimento para transformar o cdigo e melhorar
seu comportamento. O back end mapeia um programa em IR para o conjunto de instrues de um processador especfico. Ele aproxima as respostas aos problemas difceis
de alocao e escalonamento, e a qualidade de suas aproximaes tem impacto direto
sobre a velocidade e o tamanho do cdigo compilado.
Este livro explora cada uma dessas fases. Os Captulos 2 a 4 tratam dos algoritmos
usados no front end de um compilador. Os Captulos 5 a 7 descrevem o material de
base para a discusso da otimizao e gerao de cdigo. O Captulo8 fornece uma
introduo otimizao de cdigo. Os Captulos 9 e 10 tm um tratamento mais detalhado da anlise e otimizao para o leitor interessado. Finalmente, os Captulos 11 a 13
abordam as tcnicas usadas pelos back ends para seleo de instruo, escalonamento
e alocao de registradores.
NOTAS DO CAPTULO
Os primeiros compiladores apareceram na dcada de 1950, e mostravam uma sofisticao surpreendente. O compilador FORTRAN original era um sistema de mltiplas
passagens, que inclua um analisador lxico, um analisador sinttico e um alocador de
registradores, junto com algumas otimizaes [26, 27]. O sistema Alpha, criado por
Ershov e seus colegas, realizava otimizao local [139] e usava a colorao de grafos
para reduzir a quantidade de memria necessria para os itens de dados [140, 141].
Knuth oferece algumas recordaes importantes da construo de compiladores do
incio da dcada de 1960 [227]. Randell e Russell descrevem os primeiros esforos de
implementao para o Algol 60 [293]. Allen descreve a histria do desenvolvimento do
compilador dentro da IBM, enfatizando a interao entre teoria e prtica [14].
Muitos compiladores influentes foram criados nas dcadas de 1960 e 1970. Entre eles
esto o compilador otimizador clssico FORTRAN H [252, 307], os compiladores
Bliss-11 e Bliss-32 [72, 356], e o compilador porttil BCPL [300]. Esses compiladores
produziam cdigo de alta qualidade para uma srie de mquinas CISC. Compiladores
para alunos, por outro lado, focalizavam a compilao rpida, boas mensagens de
diagnstico e correo de erro [97, 146].
O advento da arquitetura RISC na dcada de 1980 levou a outra gerao de compiladores, que enfatizavam a forte otimizao e gerao de cdigo [24, 81, 89, 204].
Esses compiladores possuam otimizadores completos, estruturados como mostra a
Figura1.1. Os modernos compiladores RISC ainda seguem este modelo.
Durante a dcada de 1990, a pesquisa em construo de compiladores focalizava
a reao s rpidas mudanas que ocorriam na arquitetura de microprocessadores.
A dcada comeou com o processador i860da Intel desafiando os construtores de
EXERCCIOS
1. Considere um navegador Web simples, que tome como entrada uma string de
texto em formato HTML e apresente a especificada notao grfica na tela. O
processo de exibio utiliza compilao ou interpretao?
2. Ao projetar um compilador, enfrentam-se muitos dilemas. Quais so as cinco
qualidades que voc, como usurio, considera mais importantes em um compilador que adquire? Essa lista muda quando voc o construtor do compilador? O
que sua lista lhe diz, a respeito de um compilador, que voc implementaria?
3. Compiladores so usados em muitas circunstncias diferentes. Que diferenas
voc poderia esperar nos compiladores projetados para as seguintes aplicaes:
a. Um compilador Just-in-time usado para traduzir o cdigo da interface de
usurio baixado de uma rede?
b. Um compilador destinado ao processador embutido usado em um telefone
celular?
c. Um compilador usado em um curso de programao introdutrio no ensino
mdio?
d. Um compilador usado para criar simulaes de tnel de vento executadas
em um processador maciamente paralelo (onde todos os processadores so
idnticos)?
e. Um compilador que visa programas numericamente intensivos para um
grande nmero de mquinas diversas?
Captulo
Scanners
VISO GERAL DO CAPTULO
A tarefa do scanner* transformar um fluxo de caracteres em um fluxo de palavras na
linguagem de entrada. Cada palavra precisa ser classificada em uma categoria sinttica,
ou classe gramatical. O scanner o nico passo do compilador a ter contato com
cada caractere do programa de entrada. Os construtores de compilador valorizam a
velocidade na anlise lxica, em parte porque a entrada do scanner maior, em alguma
medida, do que aquela de qualquer outro passo, e, em parte, porque tcnicas altamente
eficientes so fceis de entender e implementar.
Este captulo introduz as expresses regulares, uma notao usada para descrever as
palavras vlidas em uma linguagem de programao, e desenvolve os mecanismos
formais para gerar scanners a partir destas expresses, seja manual ou automaticamente.
Palavras-chave: Scanner, Analisador lxico, Autmato finito, Expresso regular,
Ponto fixo
2.1INTRODUO
Anlise lxica (scanning) o primeiro estgio de um processo em trs partes que o
compilador utiliza para entender o programa de entrada. O scanner, ou analisador lxico, l um fluxo de caracteres e produz um fluxo de palavras. Ele agrega caracteres para
formar palavras e aplica um conjunto de regras para determinar se cada uma delas ou
no uma palavra vlida na linguagem-fonte. Se a palavra vlida, o scanner atribui-lhe
uma categoria sinttica, ou classe gramatical.
Scanner o nico passo do compilador que manipula cada caractere do programa
de entrada. Como realizam uma tarefa relativamente simples, agrupando caracteres
para formar palavras e pontuao na linguagem-fonte, eles podem muito bem ter implementaes rpidas. Ferramentas automticas para gerao de scanners so comuns,
que processam uma descrio matemtica da sintaxe lxica da linguagem e produzem
um reconhecedor rpido. Como alternativa, muitos compiladores utilizam scanners
codificados mo; como a tarefa simples, estes podem ser rpidos e robustos.
Reconhecedor
Programa que identifica palavras especficas
em um fluxo de caracteres.
*Analisador lxico.
19
20 CAPTULO 2 Scanners
Palavra-chave
Palavra que reservada para uma finalidade sinttica
em particular, e, portanto, no pode ser usada como
um identificador.
Em uma linguagem de programao tpica, algumas palavras, chamadas palavras-chave ou palavras reservadas, correspondem regra para um identificador, mas
possuem significados especiais. Tanto while quanto static so palavras-chave
em C e em Java. As palavras-chave (e os sinais de pontuao) formam suas prprias
categorias sintticas. Embora static corresponda regra para um identificador, o
scanner em um compilador C ou Java sem dvida a classificaria em uma categoria que
tem apenas um elemento, a palavra-chave static. Para reconhecer palavras-chave,
o scanner pode usar pesquisa de dicionrio ou codific-las diretamente em suas regras
da microssintaxe.
A estrutura lxica simples das linguagens de programao presta-se muito bem para
a construo de scanners eficientes. O construtor de compilador comea a partir de
uma especificao da microssintaxe da linguagem, codificando-a em uma notao
aceita por um gerador de scanners, que ento constri um scanner executvel, ou
ento usa esta especificao para montar um scanner codificado mo. Os scanners
gerados e codificados mo podem ser implementados para exigir apenas tempo O(1)
22 CAPTULO 2 Scanners
Se ele comear em s0 e alcanar s5, ento ter identificado a palavra while. O fragmento de cdigo correspondente envolveria cinco construes if-then-else aninhadas.
Para reconhecer vrias palavras, podemos criar vrias arestas que saem de um determinado estado. (No cdigo, comearamos a elaborar os caminhos tente outra coisa.)
Um reconhecedor para new e not poderia ser
O reconhecedor usa um teste comum para n que o leva de s0 para s1, indicado por
s0 n s1 . Se o prximo caractere for e, faz a transio s1 e s 2 . Se, ao invs disso,
o prximo caractere for o, faz o movimento s1 o s4 . Finalmente, um w em s2 gera
w
t
s3 , enquanto um t em s4 produz s4
s5 . O estado s3 indica que a
a transio s2
entrada foi new, enquanto s5, que foi not. O reconhecedor realiza uma transio por
caractere de entrada.
Podemos combinar o reconhecedor para new ou not com aquele para while, mesclando seus estados iniciais e rotulando novamente todos os estados.
Autmato finito
formalismo para os reconhecedores que possui um
conjunto finito de estados, um alfabeto, uma funo
de transio, um estado inicial e um ou mais estados
de aceitao.
24 CAPTULO 2 Scanners
Este FA reconhece uma classe de strings com uma propriedade comum: todos so
inteiros sem sinal, e levanta a distino entre a classe de strings e o texto de qualquer
string em particular. A classe inteiro sem sinal uma categoria sinttica, ou classe
gramatical. O texto de um inteiro sem sinal especfico, como 113, seu lexema.
Podemos simplificar o FA significativamente se permitirmos que o diagrama de
transio tenha ciclos. Podemos substituir toda a cadeia de estados comeando em s2
por uma nica transio de s2 para si mesmo.
Este diagrama de transio cclico faz sentido como um FA. Porm, sob o ponto de
vista da implementao, mais complexo do que os diagramas de transio acclicos
mostrados anteriormente. No podemos traduzir isto diretamente para um conjunto
de construes if-then-else aninhadas. A introduo de um ciclo no grafo de
transies cria a necessidade de um fluxo de controle cclico. Podemos implement-lo
com um lao while, conforme mostra a Figura2.2. E especificar d de modo eficiente
usando uma tabela:
d
Outro
s0
s1
s2
se
s1
se
s2
se
s2
se
s2
se
s2
se
s2
se
s2
se
s2
se
s2
se
s2
se
s2
se
s2
se
s2
se
s2
se
s2
se
s2
se
s2
se
s2
se
s2
se
s2
se
se
se
se
se
Lexema
Texto real para uma palavra reconhecida por um FA.
26 CAPTULO 2 Scanners
representadas juntas, deixando uma tabela com trs colunas: 0, 19, e outro. Um
exame mais atento da estrutura de cdigo mostra que ele informa uma falha assim
que entra em se, de modo que nunca referencia essa linha da tabela. A implementao
pode ignorar a linha inteira, deixando uma tabela com apenas trs linhas e trs colunas.
Podemos desenvolver FAs semelhantes para inteiros com sinal, nmeros reais e nmeros
complexos. Uma verso simplificada da regra que controla nomes de identificadores em
linguagens tipo Algol, como C ou Java, poderia ser: o identificador consiste em um caractere alfabtico seguido por zero ou mais caracteres alfanumricos. Esta definio permite
um conjunto infinito de identificadores, mas pode ser especificada com o FA simples em
dois estados, mostrado ao lado. Muitas linguagens de programao estendem a noo de
caractere alfabtico para incluir caracteres especiais designados, como o sublinhado.
FAS podem ser vistos como especificaes para um reconhecedor. Porm, no so
especificaes particularmente concisas. Para simplificar a implementao do scanner,
precisamos de uma notao concisa para especificar a estrutura lxica das palavras, e
um modo de transformar estas especificaes em um FA e no cdigo que implementa
o FA. As prximas sees deste captulo desenvolvem exatamente essas ideias.
REVISO DA SEO
O mtodo caractere a caractere para a anlise lxica ocasiona clareza algortmica.
Podemos representar scanners caractere a caractere com um diagrama de transio,
que, por sua vez, corresponde a um autmato finito. Pequenos conjuntos de palavras
so facilmente codificados em diagramas de transio acclicos. Conjuntos infinitos,
como o de inteiros ou o de identificadores em uma linguagem tipo Algol, exigem
diagramas de transio cclicos.
QUESTES DE REVISO
Construa um FA para aceitar cada uma das seguintes linguagens:
1. Um identificador de seis caracteres consistindo de um caractere alfabtico seguido
por zero a cinco caracteres alfanumricos.
2. Uma string de um ou mais pares, na qual cada par consiste em um abre-parnteses
seguido por um fecha-parnteses.
3. Um comentrio em Pascal, que consiste em uma abre-chaves, {, seguida por zero ou
mais caracteres retirados a partir de um alfabeto, O, seguido por uma fecha-chaves, }.
Para tornar esta discusso concreta, considere alguns exemplos que ocorrem nas linguagens de programao mais importantes. Os sinais de pontuao, como dois-pontos,
ponto e vrgula, vrgulas, parnteses, colchetes e chaves, podem ser representados por
seus smbolos em caractere. Suas REs tm a mesma grafia dos prprios sinais de
pontuao. Assim, as seguintes REs poderiam ocorrer na especificao lxica para
uma linguagem de programao:
28 CAPTULO 2 Scanners
U i= 0 R i
Por convenincia, s vezes usamos uma notao para fechamento finito. A notao Ri
indica de um a i ocorrncias de R. Um fechamento finito sempre pode ser substitudo
por uma enumerao das possibilidades; por exemplo, R3 simplesmente (R | RR |
RRR). O fechamento positivo, indicado por R+, simplesmente RR*, e consiste em uma
ou mais ocorrncias de R. Como todos esses fechamentos podem ser reescritos com as
trs operaes bsicas, vamos ignor-los na discusso a seguir.
Usando as trs operaes bsicas alternao, concatenao e fechamento , podemos definir o conjunto de REs sobre um alfabeto O da seguinte maneira:
1. Se a O, ento a tambm uma RE indicando o conjunto contendo apenas a.
2. Se r e s so REs, indicando conjuntos L(r) e L(s), respectivamente, ento r | s
uma RE indicando a unio, ou alternao, de L(r) e L(s); rs uma RE indicando
a concatenao de L(r) e L(s), respectivamente; e r* uma RE indicando o
fechamento de Kleene de L(r).
3. uma RE indicando o conjunto contendo apenas a string vazia.
Para eliminar qualquer ambiguidade, parnteses possuem a precedncia mais alta,
seguidos pelo fechamento, concatenao e alternao, nesta ordem.
Como uma abreviao conveniente, especificaremos intervalos de caracteres com o
primeiro e o ltimo elementos conectados por reticncias, . Para que esta abreviao se destaque, vamos cerc-la com um par de colchetes. Assim, [09] representa
o conjunto de dgitos decimais, que sempre poder ser reescrito como (0 | 1 | 2 | 3 | 4
| 5 | 6 | 7 | 8 | 9).
EXPRESSES REGULARES NA VIDA VIRTUAL
Expresses regulares so usadas em muitas aplicaes para especificar padres em
strings de caracteres. Parte do trabalho inicial na traduo de REs para cdigo foi feita
para oferecer um modo flexvel de especificar strings no comando find (ou localizar)
de um editor de textos. A partir desta gnese inicial, a notao passou para muitas
aplicaes diferentes.
Unix e outros sistemas operacionais utilizam o asterisco como um curinga para
corresponder substrings a nomes de arquivo. Aqui, * uma abreviao para a RE
O*, especificando zero ou mais caracteres retirados do alfabeto inteiro de caracteres
vlidos. (Como poucos teclados tm a tecla O, a abreviao foi adotada) Muitos
sistemas usam ? como curinga que corresponde a um nico caractere.
A famlia de ferramentas grep, e seus aparentados em sistemas no Unix, implementa a
correspondncia de padres de expresso regular. (Na verdade, grep um acrnimo para
global regular-expression pattern match and print padro de expresso regular global).
As expresses regulares encontraram uso generalizado porque so facilmente escritas
e entendidas, e uma das tcnicas escolhidas quando um programa precisa reconhecer
um vocabulrio fixo. Funcionam bem para linguagens que se encaixam em suas
regras limitadas. E facilmente traduzidas para uma forma executvel, e o reconhecedor
resultante rpido.
2.3.2Exemplos
O objetivo deste captulo mostrar como podemos usar tcnicas formais para automatizar a construo de scanners de alta qualidade e codificar a microssintaxe das linguagens de programao nesse formalismo. Antes de prosseguir,
alguns exemplos tomados a partir das linguagens de programao reais so
adequados.
1. A regra simplificada dada anteriormente para identificadores em linguagens
tipo Algol, um caractere alfabtico seguido por zero ou mais caracteres
alfanumricos, simplesmente ([AZ] | [az]) ([AZ] | [az] | [09])*.
A maioria das linguagens tambm permite alguns caracteres especiais, como
sublinhado (_), sinal de porcentagem (%) ou E comercial (&) nos identificadores.
Se a linguagem limitar o tamanho mximo de um identificador, podemos usar o
fechamento finito apropriado. Assim, identificadores limitados a seis caracteres
poderiam ser especificados como ([AZ] | [az]) ([AZ] | [az] | [09])5. Se
tivssemos que escrever a expanso completa do fechamento finito, a RE seria
muito maior.
2. Um inteiro sem sinal pode ser descrito como zero ou um dgito diferente de
zero seguido por zero ou mais dgitos. A RE 0 | [19] [09]* mais concisa.
Na prtica, muitas implementaes admitem uma classe maior de strings como
inteiros, aceitando a linguagem [09]+.
3. Nmeros reais sem sinal so mais complexos que os inteiros. Uma RE possvel
poderia ser (0 | [19] [09]*) ( |. [09]*). A primeira parte simplesmente
a RE para um inteiro. O restante gera, ou a string vazia, ou um ponto decimal
seguido por zero ou mais dgitos.
As linguagens de programao normalmente estendem os nmeros reais para a
notao cientfica, como em (0 | [19] [09]*) ( |. [09]*) E ( |+| ) (0 |
[19] [09]*).
Esta RE descreve um nmero real, seguido por um E, seguido por um inteiro
para especificar um expoente.
4. As strings de caracteres entre aspas possuem suas prpria complexidade. Na
maioria das linguagens, qualquer caractere pode aparecer dentro de uma string.
Embora possamos escrever uma RE para strings usando apenas os operadores
bsicos, este o nosso primeiro exemplo no qual um operador de complemento
simplifica a RE. Usando o complemento, uma string de caracteres em C ou Java
pode ser descrita como ( )*.
C e C++ no permitem que uma string se estenda por vrias linhas no
cdigo-fonte ou seja, se o scanner alcanar o final de uma linha enquanto
estiver dentro de uma string, ele termina a string e emite uma mensagem de
erro. Se representarmos uma nova linha pela sequncia de escape \n, no
estilo C, ento a RE ( ( | \n) )* reconhecer uma string corretamente
formada e levar a uma transio de erro em uma string que inclui uma nova
linha.
5. Comentrios aparecem em diversas firmas. C++ e Java oferecem ao programador
duas formas de escrev-los. O delimitador // indica um comentrio que vai at
o final da linha de entrada atual. A RE para este estilo de comentrio simples:
//(^\n)* \n, onde \n representa o caractere newline.
Comentrios em mltiplas linhas em C, C++ e Java comeam com o delimitador
/* e terminam com */. Se pudssemos no permitir * em um comentrio, a RE
seria simples: /* (^*)* */. Com *, a RE mais complexa: /* ( ^* | *+^/ )*
*/. Um FA para implementar esta RE o seguinte:
Operador de complemento
A notao ^c especifica o conjunto {O c}, o complemento de c em relao a O. O complemento tem
precedncia maior do que *, | ou +.
Sequncia de escape
Dois ou mais caracteres que o scanner traduz para
outro caractere. Sequncias de escape so usadas para
caracteres que no possuem um glifo, como newline
(nova linha) ou tab (tabulao), e para aqueles que
ocorrem na sintaxe, como uma aspa de incio ou fim.
30 CAPTULO 2 Scanners
Este reconhecedor aceita r29 e rejeita s29. Ele tambm aceita r99999, embora
atualmente nenhum computador disponvel tenha 100.000 registradores.
Em um computador real, porm, o conjunto de nomes de registrador bastante limitado
digamos, para 32, 64, 128 ou 256 registradores. Um modo do scanner verificar a
validade de um nome de registrador convertendo os dgitos para um nmero e testando se ele se encontra ou no no intervalo de nmeros vlidos para o registrador. A
alternativa adotar uma especificao de RE mais precisa, como:
Esta RE especifica uma linguagem muito menor, limitada aos nmeros de registrador
de 0 a 31 com um 0 inicial opcional nos nomes de registrador com nico dgito. Ela
aceita r0, r00, r01 e r31, mas rejeita r001, r32 e r99999. O FA correspondente
se parece com este:
Qual FA melhor? Ambos fazem uma nica transio em cada caractere de entrada.
Assim, tm o mesmo custo, embora um segundo FA verifique uma especificao
mais complexa. O mais complexo tem mais estados e transies, de modo que sua
representao exige mais espao. Porm, seus custos operacionais so os mesmos.
Este ponto crtico: o custo da operao de um FA proporcional ao tamanho da entrada, e no ao tamanho ou complexidade da RE que o gera. REs mais complexas produzem
FAs com mais estados, que, por sua vez, necessitam mais espao. O custo de gerar um
FA a partir de uma RE tambm pode aumentar com o aumento na complexidade da
RE. Mas o custo da operao do FA permanece uma transio por caractere de entrada.
Podemos melhorar nossa descrio do especificador de registradores? A RE anterior
complexa e contraintuitiva. Uma alternativa mais simples poderia ser:
Esta RE conceitualmente mais simples, porm muito maior do que a verso anterior.
O FA resultante ainda exige uma transio por smbolo de entrada. Assim, se pudermos
controlar o crescimento no nmero de estados, poderamos preferir esta verso da RE,
pois clara e bvia. Porm, quando nosso processador, de repente, tem 256 ou 384
registradores, a enumerao tambm pode ser tediosa.
LINGUAGENS DE PROGRAMAO VERSUS LINGUAGENS NATURAIS
A anlise lxica destaca uma das formas sutis de como as linguagens de programao
diferem das naturais, como ingls ou chins. Nestas, o relacionamento entre a
representao de uma palavra grafia ou pictograma e seu significado no
bvio. Em ingls, are verbo, enquanto art substantivo, embora as palavras sejam
diferentes apenas no ltimo caractere. Alm do mais, nem todas as combinaes de
caracteres so palavras legtimas. Por exemplo, arz difere muito pouco de are e art, mas
no ocorre como palavra no uso normal do ingls.
Um scanner para o ingls poderia usar tcnicas baseadas em FA para reconhecer palavras
em potencial, pois todas so retiradas de um alfabeto restrito. Depois disso, porm, ele
precisa consultar uma provvel palavra num dicionrio para determinar se , de fato, uma
palavra. Se a palavra tiver uma classe gramatical exclusiva, a pesquisa no dicionrio tambm
resolver este problema. Porm, muitas palavras em ingls podem ser classificadas com
diversas classes gramaticais. Exemplo so love e stress; ambas podem ser substantivo ou
verbo. Para estas, a classe gramatical depende do contexto ao redor. Em alguns casos,
entender o contexto gramatical suficiente para classificar a palavra. Em outros, isto exige
um conhecimento do significado, tanto da palavra quanto do seu contexto.
Ao contrrio, as palavras em uma linguagem de programao so quase sempre
especificadas de forma lxica. Assim, qualquer string em [19][09]* um inteiro
positivo. A RE [az]([az]|[09])* define um subconjunto dos identificadores em
Algol; arz, are e art so todos identificadores, sem necessidade de qualquer pesquisa
para estabelecer este fato. Por certo, alguns identificadores podem ser reservados
como palavras-chave. Porm, essas excees tambm podem ser especificadas
lexicamente. Nenhum contexto necessrio.
Esta propriedade resulta de uma deciso deliberada no projeto da linguagem de
programao. A escolha de fazer que a grafia tenha uma nica classe gramatical
simplifica a anlise lxica, a anlise sinttica e, aparentemente, abre mo de pouca
coisa na expressividade da linguagem. Algumas linguagens tm permitido palavras
com duas classes gramaticais por exemplo, PL/I no possui palavras-chave
reservadas. O fato de que as linguagens mais recentes tenham abandonado a ideia
sugere que as complicaes so maiores que a flexibilidade lingustica extra.
32 CAPTULO 2 Scanners
Expresses regulares e as linguagens que geram tm sido assunto de muito estudo. Elas
possuem muitas propriedades interessantes e teis. Algumas destas desempenham um
papel crtico nas construes que criam reconhecedores a partir de REs.
Expresses regulares so fechadas sob muitas operaes ou seja, se aplicarmos a operao a uma RE ou a uma coleo de REs, o resultado uma RE.
Alguns exemplos bvios so concatenao, unio e fechamento. A concatenao
de duas REs x e y simplesmente xy. A unio, x | y. O fechamento de Kleene
de x simplesmente x*. Pela definio de uma RE, todas essas expresses
tambm so REs.
Essas propriedades de fechamento desempenham papel fundamental no uso
das REs para a criao de scanners. Suponha que tenhamos uma RE para cada
categoria sinttica na linguagem-fonte, a 0, a 1, a 2,. . ., a n. Ento, para construir
uma RE para todas as palavras vlidas na linguagem, podemos junt-las com
alternao como a0 | a1 | a2 |. .. | an. Como as REs so fechadas sob unio, o resultado uma RE. Qualquer coisa que pudermos fazer em uma RE para uma nica
categoria sinttica ser igualmente aplicvel RE para todas as palavras vlidas na
linguagem.
O fechamento sob unio implica que qualquer linguagem finita uma linguagem regular. Podemos construir uma RE para qualquer coleo finita de palavras listando-as em
uma grande alternao. Como o conjunto de REs fechado sob unio, essa alternao
uma RE, e a linguagem correspondente regular.
O fechamento sob concatenao nos permite montar REs complexas, a partir de
outras mais simples, concatenando-as. Esta propriedade parece bvia e pouco
importante. Porm, nos permite juntar REs de formas sistemticas. O fechamento
garante que ab uma RE desde que tanto a quanto b sejam REs. Assim, quaisquer tcnicas que possam ser aplicadas a a ou a b podem ser aplicadas a ab;
isto inclui construes que geram automaticamente um reconhecedor a partir
de REs.
Expresses regulares tambm so fechadas sob os fechamentos de Kleene e finitos.
Esta propriedade nos permite especificar tipos particulares de conjuntos grandes,
ou mesmo infinitos, com padres finitos. O fechamento de Kleene permite-nos
especificar conjuntos infinitos com padres finitos concisos; alguns exemplos
incluem os inteiros e identificadores de tamanho ilimitado. J os fechamentos
finitos nos possibilitam especificar conjuntos grandes, porm finitos, com a mesma facilidade.
A prxima seo mostra uma sequncia de construes que criam um FA para reconhecer a linguagem especificada por uma RE. A Seo 2.6 mostra um algoritmo que
faz o contrrio, de um FA para uma RE. Juntas, estas construes estabelecem a equivalncia entre REs e FAs. O fato de que REs so fechadas sob alternao, concatenao
e fechamento so crticos para essas construes.
FA completo
FA que inclui explicitamente todas as transies de erro.
REVISO DA SEO
Expresses regulares so uma notao concisa e poderosa para especificar a microssintaxe das linguagens de programao. REs baseiam-se em trs operaes bsicas
sobre alfabetos finitos: alternao, concatenao e fechamento de Kleene. Outras
operaes convenientes, como fechamento finito, fechamento positivo, e complemento, derivam das trs operaes bsicas. Expresses regulares e autmatos
finitos se relacionam; qualquer RE pode ser concretizada em um FA, e a linguagem
aceita por qualquer FA pode ser descrita com uma RE. A prxima seo formaliza este
relacionamento.
QUESTES DE REVISO
1. Lembre-se da RE para um identificador de seis caracteres, escrita usando um
fechamento finito.
Crie uma RE e um FA para reconhecer strings em PL/I. Suponha que as strings comecem e terminem com aspas e contenham apenas smbolos retirados de um alfabeto,
designado como O. As aspas so o nico caso especial.
34 CAPTULO 2 Scanners
-transio
Transio sobre a string vazia, , que no avana a
entrada.
Com uma -transio, a definio de aceitao precisa mudar ligeiramente para permitir uma ou mais -transies entre dois caracteres quaisquer na string de entrada.
Por exemplo, em s1, o FA faz a transio s1 s2 sem consumir qualquer caractere
de entrada. Esta uma mudana pequena, mas parece intuitiva. A inspeo mostra que
podemos combinar s1 e s2 para eliminar a -transio.
A fuso de dois FAs com uma -transio pode complicar nosso modelo de como os
FAs funcionam. Considere os FAs para as linguagens a* e ab.
FA no determinstico
FA que permite transies sobre a string vazia, , e
estabelece que possui vrias transies no mesmo
caractere.
FA determinstico
DFA um FA onde a funo de transio tem valor
nico. DFAs no permitem -transies.
Para um NFA fazer sentido, precisamos de um conjunto de regras que descrevam seu
comportamento. Historicamente, dois modelos distintos foram dados para o comportamento de um NFA.
1. Toda vez que o NFA precisa fazer uma escolha no determinstica, ele segue
a transio que leva a um estado de aceitao para a string de entrada, se esta
transio existir. Esse modelo, usando um NFA onisciente, interessante
porque mantm (na superfcie) o mecanismo de aceitao bem definido do DFA.
Basicamente, o NFA escolhe a transio correta em cada ponto.
2. Toda vez que o NFA precisa fazer uma escolha no determinstica, ele clonado
para buscar cada transio possvel. Assim, para determinado caractere de entrada,
o NFA est em um conjunto especfico de estados, tomados a partir de todos os seus
clones. Neste modelo, o NFA busca todos os caminhos simultaneamente. Em qualquer ponto, chamamos o conjunto especfico de estados em que o NFA est ativo de
sua configurao. Quando ele alcana uma configurao em que esgotou a entrada e
um ou mais dos clones alcanaram um estado de aceitao, ele aceita a string.
Em qualquer modelo, o NFA (S, O, d, s0, SA) aceita uma string de entrada x1 x2 x3 xk se,
e somente se, houver pelo menos um caminho pelo diagrama de transio que comea
em s0 e termina em algum sk SA tal que os rtulos de aresta ao longo do caminho
combinem com a string de entrada. (As arestas rotuladas com so omitidas.) Em
outras palavras, o i-simo rtulo de aresta precisa ser xi. Esta definio consistente
com qualquer modelo do comportamento do NFA.
Configurao de um NFA
O conjunto de estados ativos simultaneamente
de um NFA.
36 CAPTULO 2 Scanners
Conjunto potncia de N
Conjunto de todos os subconjuntos de N, indicado
por 2N.
estados do que o NFA. Embora SDFA, o conjunto de estados no DFA, possa ser grande,
ele finito. Alm do mais, o DFA ainda faz uma transio por smbolo de entrada.
Assim, o DFA que simula o NFA ainda executa em tempo proporcional extenso da
string de entrada. A simulao de um NFA em um DFA tem um problema de espao
em potencial, mas no de tempo.
Como os NFAs e os DFAs so equivalentes, podemos construir um DFA para a*ab:
Ele conta com a observao de que a*ab especifica o mesmo conjunto de palavras
que aa*b.
de modo que, em seguida, ela constri o fechamento, (b|c)*. Finalmente, ela concatena
o NFA para a ao NFA para (b|c)*.
Os NFAs derivados da construo de Thompson possuem vrias propriedades especficas que simplificam uma implementao. Cada NFA tem um estado inicial e um estado
de aceitao. Nenhuma transio, alm da inicial, entra no estado inicial. E nenhuma
sai do estado de aceitao. Uma -transio sempre conecta dois estados que eram,
neste processo, o inicial e o de aceitao de NFAs para algumas REs componentes.
Finalmente, cada estado tem no mximo dois -movimentos de entrada e dois de sada,
e no mximo um movimento de entrada e um de sada sobre um smbolo no alfabeto.
Juntas, essas propriedades simplificam a representao e a manipulao dos NFAs. Por
exemplo, a construo s precisa lidar com um nico estado de aceitao, ao invs de
percorrer um conjunto de estados aceitveis no NFA.
A Figura2.5 mostra o NFA que a construo de Thompson monta para a(b|c)*. Ele tem
muito mais estados do que o DFA provavelmente produzido por um humano, mostrado
ao lado. Ele tambm contm muitos -movimentos que so obviamente desnecessrios.
Outros estgios na construo os eliminaro.
38 CAPTULO 2 Scanners
apenas -transies. Esses estados so equivalentes, pois podem ser alcanados sem
consumir entrada.
Para construir q0 a partir de n0, o algoritmo calcula -closure(n0), e usa, como entrada, um conjunto S de estados do NFA. Ele retorna um conjunto de estados do NFA
construdos a partir de S da seguinte forma: -closure examina cada estado si S e
acrescenta a S qualquer estado alcanvel seguindo uma ou mais -transies a partir de
si. Se S for o conjunto de estados que podem ser alcanados a partir de n0 seguindo os
caminhos rotulados com abc, ento -closure(S) o conjunto de estados que podem
ser alcanados a partir de n0 seguindo os caminhos rotulados abc*. Inicialmente, Q
tem apenas um membro, q0, e a WorkList contm q0.
O algoritmo prossegue removendo um conjunto q da worklist (lista de trabalho). Cada
q representa uma configurao vlida do NFA original. O algoritmo constri, para cada
caractere c no alfabeto O, a configurao que o NFA alcanaria se lesse c enquanto
estiver na configurao q. Essa computao usa uma funo Delta(q,c), que aplica a
funo de transio do NFA a cada elemento de q, e retorna UsqidN(s,c).
O lao while remove repetidamente uma configurao q da worklist e usa Delta para
calcular suas transies em potencial. Ele aumenta essa configurao calculada com
quaisquer estados que possam ser alcanados pelas -transies seguintes, e acrescenta
quaisquer novas configuraes geradas dessa forma a Q e worklist. Quando descobre
uma nova configurao t alcanvel a partir de q sobre o caractere c, o algoritmo
registra essa transio na tabela T. O lao interno, que percorre o alfabeto para cada
configurao, realiza uma busca completa.
Observe que Q cresce monotonicamente. O lao while acrescenta conjuntos a Q, mas
nunca os remove. Como o nmero de configuraes do NFA limitado e cada configurao s aparece uma vez na worklist, o lao while deve parar. Ao parar, Q contm
todas as configuraes vlidas do NFA e T mantm todas as transies entre elas.
Q pode se tornar grande pode chegar a |2N| estados distintos. A quantidade de no
determinismo encontrada no NFA determina quanta expanso de estado ocorre. Lembre-se,
40 CAPTULO 2 Scanners
porm, que o resultado um DFA que faz exatamente uma transio por caractere de entrada, independente do nmero de estados no DFA. Assim, qualquer expanso introduzida
pela construo de subconjunto no afeta o tempo de execuo do DFA.
De Q a D
Quando a construo de subconjunto para, ter construdo um modelo do DFA
desejado, que simula o NFA original. A construo do DFA a partir de Q e T simples. Cada qi Q precisa de um estado di D para represent-lo. Se qi contm um
estado de aceitao do NFA, ento di um estado de aceitao do DFA. Podemos
construir a funo de transio, dD, diretamente de T, observando o mapeamento
de qi para di. Finalmente, o estado construdo a partir de q0 torna-se d0, o estado
inicial do DFA.
Exemplo
Considere o NFA construdo para a(b|c)* na Seo 2.4.2 e mostrado na Figura2.7a com seus estados renumerados. A tabela na Figura2.7b esboa as etapas
seguidas pelo algoritmo de construo de subconjunto. A primeira coluna mostra
o nome do conjunto em Q sendo processado em determinada iterao do lao
while. A segunda, o nome do estado correspondente no novo DFA. A terceira,
o conjunto de estados do NFA contidos no conjunto atual de Q. As trs colunas
finais exibem resultados do clculo de -closure de Delta no estado para
cada caractere em O.
O algoritmo realiza as seguintes etapas:
1. A inicializao define q0 como -closure({n0}), que simplesmente n0. A
primeira iterao calcula -closure(Delta(q0, a)), que contm seis estados do
NFA, e -closure(Delta(q0,b)) e -closure(Delta(q0,c)), que so vazios.
2. A segunda iterao do lao while examina q1; produz duas configuraes e as
chama de q2 e q3.
3. A terceira iterao do lao while examina q2 e constri duas configuraes, que
so idnticas a q2 e q3.
4. A quarta iterao do lao while examina q3. Assim como a terceira iterao, esta
reconstri q2 e q3.
A Figura2.7c mostra o DFA resultante; os estados correspondem aos do DFA a partir
da tabela, e as transies so dadas pelas operaes Delta que geram esses estados.
Como os conjuntos q1, q2 e q3 contm n9 em cada um deles (o estado de aceitao do
NFA), todos os trs se tornam estados de aceitao no DFA.
Funo monotnica
A funo f no domnio D monotnica se x , y D,
xy f (x )f (y )
42 CAPTULO 2 Scanners
Partio de conjunto
Partio de conjunto de S uma coleo de subconjuntos
disjuntos, no vazios, de S, cuja unio exatamente S.
44 CAPTULO 2 Scanners
Exemplos
Considere um DFA que reconhece a linguagem fee | fie, mostrada na Figura2.11a. Por
inspeo, podemos ver que os estados s3 e s5 servem mesma finalidade. Ambos esto
aceitando estados entrados apenas por uma transio na letra e. Nenhum tem transio
que sai do estado. Espera-se que o algoritmo de minimizao de DFA descubra este
fato e os substitua por um nico estado.
A Figura2.11b mostra as etapas significativas que ocorrem na minimizao deste
DFA. A partio inicial, mostrada como a etapa 0, separa os estados de aceitao dos
de no aceitao. Supondo que o lao while no algoritmo percorra os conjuntos de P
em ordem, e sobre os caracteres em O={e, f, i} em ordem, ento, primeiro examina
o conjunto {s3, s5}. Como nenhum estado tem transio de sada, no dividido em
qualquer caractere. Na segunda etapa, ele examina {s0, s1, s2, s4}; no caractere e, divide
{s2, s4} do conjunto. Na terceira, examina {s0, s1} e o divide devido ao caractere f.
Nesse ponto, a partio {{s3, s5}, {s0}, {s1}, {s2, s4}}. O algoritmo faz uma passagem
final sobre os conjuntos na partio, no divide nenhum deles, e termina.
Para construir o novo DFA, devemos construir um estado para representar cada conjunto na partio final, acrescentar as transies apropriadas a partir do DFA original
e designar estados inicial e de aceitao. A Figura2.11c mostra o resultado para este
exemplo.
Como segundo exemplo, considere o DFA para a(b|c)*, produzido pela construo de
Thompson, e pela construo de subconjunto, mostrado na Figura2.12a. A primeira
etapa do algoritmo de minimizao constri uma partio inicial {{d0}, {d1, d2, d3}},
como mostramos ao lado. Como p1 tem apenas um estado, no pode ser dividido.
Quando o algoritmo examina p2, no encontra transies sobre a a partir de qualquer
46 CAPTULO 2 Scanners
estado em p2. Para b e c, cada estado tem uma transio de volta para p2. Assim, nenhum smbolo em O divide p2, e a partio final {{d0}, {d1, d2, d3}}.
O DFA mnimo resultante aparece na Figura2.12b. Lembre-se de que este o DFA
que sugerimos que um humano derivaria. Aps a minimizao, as tcnicas automticas
produzem o mesmo resultado.
Este algoritmo outro exemplo de computao de ponto fixo. P finito; no mximo,
pode conter |D| elementos. O lao while divide conjuntos em P, mas nunca os combina.
Assim, |P| cresce monotonicamente. O lao termina quando alguma iterao no divide
conjuntos em P. O comportamento de pior caso ocorre quando cada estado no DFA
comporta-se de modo diferente; neste caso, o lao while termina quando P tem um
conjunto distinto para cada di D. Isto ocorre quando o algoritmo aplicado a um
DFA mnimo.
REVISO DA SEO
Dada uma expresso regular, podemos derivar um DFA mnimo para reconhecer a
linguagem especificada pela RE usando as seguintes etapas: (1) aplicar a construo
de Thompson para montar um NFA para a RE; (2) usar a construo de subconjunto
para derivar um DFA que simula o comportamento da RE; e (3) usar o algoritmo de
Hopcroft para identificar estados equivalentes no DFA e construir um DFA mnimo.
Esse trio de construes produz um reconhecedor eficiente para qualquer linguagem
que possa ser especificada com uma RE.
Tanto a construo de subconjunto quanto o algoritmo de minimizao do DFA so
computaes de ponto fixo, caracterizadas pela aplicao repetida de uma funo
monotnica a um conjunto at certo ponto; as propriedades do domnio desempenham papel importante no raciocnio a respeito do trmino e da complexidade desses
algoritmos. Veremos mais computaes de ponto fixo em captulos mais adiante.
48 CAPTULO 2 Scanners
QUESTES DE REVISO
1. Considere a RE who | what | where. Use a construo de Thompson para montar
um NFA a partir da RE. Use a construo de subconjunto para montar um DFA a
partir do NFA. Minimize o DFA.
2. Minimize o seguinte DFA:
A Figura2.14 exibe um scanner controlado por tabela para a RE r [0.. . 9]+, que foi
nossa primeira tentativa de uma RE para nomes de registrador ILOC. O lado esquerdo
da figura mostra o esqueleto de scanner, enquanto o lado direito, as tabelas para r [0.. .9]+
e o DFA bsico. Observe a semelhana entre o cdigo aqui mostrado e o reconhecedor
mostrado na Figura2.2.
O esqueleto de scanner dividido em quatro sees: inicializaes, um lao de anlise
que modela o comportamento do DFA, um lao de rollback caso o DFA ultrapasse o
final da palavra reconhecida pelo scanner (conhecida como token), e uma seo final
que interpreta e relata os resultados. O lao de anlise repete as duas aes bsicas de
um scanner: l um caractere e simula a ao do DFA, e para quando o DFA entra no
estado de erro, se. Duas tabelas, CharCat e d, codificam todo o conhecimento sobre
o DFA. O lao de rollback usa uma pilha de estados para reverter o scanner ao seu
estado de aceitao mais recente.
O esqueleto de scanner usa a varivel state para manter o estado atual do DFA
simulado, e atualiza state usando um processo de pesquisa em tabela, em duas
etapas. Primeiro, classifica char em uma de um conjunto de categorias usando a
tabela CharCat. O scanner para r [0.. .9]+ possui trs categorias: Registrador, Dgito
e Outro. Em seguida, usa o estado atual e a categoria de caracteres como ndices para
a tabela de transio, d.
Essa traduo em duas etapas, de caractere para categoria, depois estado e categoria
para novo estado, permite que o scanner use uma tabela de transio compactada.
O compromisso entre acesso direto em uma tabela maior, e acesso indireto na
tabela compactada direto. A tabela completa eliminaria o mapeamento atravs
de CharCat, mas aumentaria o requisito de memria da tabela. A tabela de
transio no compactada cresce com o produto entre o nmero de estados no DFA
e o nmero de caracteres em O; e pode crescer ao ponto em que no permanecer
na cache.
Com um conjunto de caracteres pequeno e compacto, como ASCII, CharCat pode
ser representada como uma simples pesquisa em tabela. Suas partes relevantes devem
permanecer na cache. Neste caso, a compactao da tabela acrescenta uma referncia
de cache por caractere de entrada. medida que o conjunto de caracteres aumenta (por
50 CAPTULO 2 Scanners
exemplo, Unicode), implementaes mais complexas de CharCat podem ser necessrias. O compromisso preciso entre os custos por caractere das tabelas compactada
e no compactada depender das propriedades da linguagem e do computador que
executa o scanner.
Para oferecer uma interface caractere a caractere para o fluxo de entrada, o esqueleto
de scanner usa uma macro, NextChar, que define seu nico parmetro para conter o
prximo caractere no fluxo de entrada. Uma macro correspondente, RollBack, recua
o fluxo de entrada em um caractere. (A Seo 2.5.3 examina ambas.)
Se o scanner for muito longe na leitura, state no ir conter um estado de aceitao
ao final do primeiro lao while. Neste caso, um segundo lao while usa o registro
de estados na pilha para retornar o estado, lexema e fluxo de entrada de volta ao
estado de aceitao mais recente. Na maioria das linguagens, o excesso do scanner
ser limitado. Porm, o comportamento patolgico poder fazer com que o scanner
examine caracteres individuais muitas vezes, aumentando significativamente o custo
global da anlise. Na maioria das linguagens de programao, a quantidade de rollback pequena em relao aos tamanhos de palavra. Em linguagens nas quais ocorrem
grandes quantidades de rollback, torna-se justificado um mtodo mais sofisticado para
este problema.
52 CAPTULO 2 Scanners
while, o cdigo testa cada par estado, posio de entrada e sai do lao de anlise
sempre que for tentada uma transao com falha.
As otimizaes podem reduzir drasticamente os requisitos de espao deste esquema.
(Veja, por exemplo, o Exerccio 16 ao final deste captulo.) A maior parte das linguagens de programao possui uma microssintaxe simples o bastante para que este tipo
de rollback quadrtico no possa ocorrer. Porm, se voc estiver criando um scanner
para uma linguagem que possa exibir este comportamento, o scanner pode evit-lo
com um pequeno overhead adicional por caractere.
Alterando linguagens
Para modelar outro DFA, o construtor de compilador pode simplesmente fornecer
novas tabelas. Neste captulo, trabalhamos com uma segunda especificao, mais restrita, para os nomes de registrador ILOC, dada pela RE: r( [02] ([09] | ) | [49]
| (3 (0 | 1 | )) ), que fez surgir o seguinte DFA:
Como ele tem mais estados e transies do que a RE para r [0.. .9]+, devemos esperar
uma tabela de transio maior.
s0
s1
s2
s3
s4
s5
s6
se
0,1
49
Outro
s1
se
se
se
se
se
se
se
se
s2
s3
se
se
s6
se
se
se
s2
s3
se
se
se
se
se
se
s5
s3
se
se
se
se
se
se
s4
s3
se
se
se
se
se
se
se
se
se
se
se
se
se
Como exemplo final, o DFA mnimo para a RE a (b|c)* tem a seguinte tabela:
a
s0
s1
b,c
s1
se
se
s1
Tabela de Transio
Outro
se
se
54 CAPTULO 2 Scanners
onde @d0 uma constante relacionada ao endereo inicial de d na memria, e w o nmero de bytes por elemento de d. Novamente, o scanner precisa realizar uma operao
load para recuperar os dados armazenados nesse endereo.
Assim, o scanner controlado por tabela realiza dois clculos de endereo e duas operaes load para cada caractere que processa. As melhorias de velocidade em um scanner
codificado diretamente vm da reduo desse overhead.
56 CAPTULO 2 Scanners
controlado por tabela, o cdigo muda para cada conjunto de REs. Como esse cdigo
gerado diretamente a partir das REs, a diferena no deve importar para o construtor
de compiladores.
O cdigo no estilo da Figura2.16 normalmente
chamado cdigo espaguete, em homenagem ao seu
fluxo de controle confuso.
Classificando caracteres
O exemplo que estamos usando, r[09]+, divide o alfabeto de caracteres de entrada
em exatamente quatro classes. Um r cai na classe Registrador. Os dgitos 0, 1, 2,
3, 4, 5, 6, 7, 8 e 9 caem na classe Dgito, o caractere especial retornado
quando NextChar esgota sua entrada cai na classe EndOfFile, e qualquer outra coisa
cai na classe Outro.
Sequncia de classificao
Ordem alfabtica dos caracteres em um alfabeto,
determinada pelos inteiros atribudos a cada caractere.
Para ler um caractere, o scanner incrementa o ponteiro, mdulo 2n, e retorna o caractere
nesse local. Para reverter um caractere, o programa decrementa o ponteiro de entrada, mdulo 2n. E tambm precisa gerenciar o contedo do buffer, lendo caracteres
adicionais do fluxo de entrada conforme a necessidade.
Tanto NextChar quanto RollBack possuem implementaes simples e eficientes,
como mostra a Figura2.17. Cada execuo de NextChar carrega um caractere,
incrementa o ponteiro Input e testa se preenche ou no o buffer. A cada n caracteres, ele preenche o buffer. O cdigo pequeno o suficiente para ser includo
em linha, talvez gerado a partir de uma macro. Este esquema amortiza o custo de
preenchimento do buffer sobre n caracteres. Escolhendo um tamanho razovel para
n, como 2048, 4096, ou mais, o construtor de compiladores pode manter baixo o
overhead de E/S.
RollBack ainda menos dispendioso. Ele realiza um teste para garantir que o
contedo do buffer vlido e depois decrementa o ponteiro de entrada. Novamente, a
Buffering duplo
O esquema que usa dois buffers de entrada em um
padro de mdulo para fornecer rollback limitado
normalmente chamado buffering duplo.
58 CAPTULO 2 Scanners
Gerando lexemas
O cdigo mostrado para os scanners controlados por tabela e codificados diretamente
acumulou os caracteres de entrada em uma string lexeme. Se a sada apropriada
para cada categoria sinttica uma cpia textual do lexema, ento esses esquemas so
eficientes. Em alguns casos comuns, porm, o parser, que consome a sada do scanner,
precisa da informao em outro formato.
Por exemplo, em muitas circunstncias a representao natural para um nmero de
registrador um inteiro, ao invs de uma string de caracteres consistindo em um r
e uma sequncia de dgitos. Se o scanner montar uma representao de caracteres,
ento, em algum ponto na interface essa string precisa ser convertida para um inteiro.
Um modo tpico de realizar esta converso usa uma rotina de biblioteca, como atoi
na biblioteca C-padro, ou uma rotina de E/S baseada em strings, como sscanf. Um
modo mais eficiente de resolver este problema seria acumular o valor do nmero inteiro
usando um dgito de cada vez.
No exemplo que estamos usando, o scanner poderia inicializar uma varivel,
RegNum, em zero no seu estado inicial. Toda vez que reconhecesse um dgito,
poderia multiplicar RegNum por 10 e somar o novo dgito. Quando alcanasse um
estado de aceitao, RegNum teria o valor necessrio. Para modificar o scanner
onde tanto char quanto 0 so tratados como seus valores ordinais na sequncia de
classificao ASCII. Acumular assim o valor provavelmente tem um overhead menor
do que montar a string e convert-la no estado de aceitao.
Para outras palavras da linguagem, o lexema implcito e, portanto, redundante.Para
palavras de um nico caractere, como um sinal de pontuao ou um operador, a
categoria sinttica equivalente ao lexema. De modo semelhante, muitos scanners
reconhecem comentrios e espaos em branco e os descartam. Novamente, o conjunto
de estados que reconhece o comentrio no precisa acumular o lexema. Embora as
economias individuais sejam pequenas, o efeito agregado criar um scanner mais
rpido e mais compacto.
Esta questo surge porque muitos geradores de scanner permitem que o construtor de
compiladores especifique aes a serem realizadas em um estado de aceitao, mas no
permitem aes em cada transio. Os scanners resultantes precisam acumular uma
cpia em caracteres do lexema para cada palavra, seja esta cpia necessria ou no.
Se o tempo de compilao importa (e deveria), ento atentar a estes pequenos detalhes
algortmicos leva a um compilador mais rpido.
60 CAPTULO 2 Scanners
Na maior parte dos casos, deixar o reconhecimento de palavras-chave para o DFA faz
mais sentido do que usar uma tabela de pesquisa separada.
REVISO DA SEO
A construo automtica de um scanner funcional a partir de um DFA mnimo
simples. O gerador de scanner pode adotar um mtodo controlado por tabela, no
qual usa um esqueleto de scanner genrico e tabelas especficas da linguagem, ou,
ento, pode gerar um scanner codificado diretamente, que encadeia um fragmento
de cdigo para cada estado do DFA. Em geral, o mtodo de cdigo direto produz um
scanner mais rpido, pois tem menor overhead por caractere.
Apesar do fato de que todos os scanners baseados em DFA possuem pequenos
custos constantes por caractere, muitos construtores de compilador escolhem
codificar um scanner manualmente. Esta tcnica serve para a implementao
cuidadosa das interfaces entre o scanner e o sistema de E/S e entre o scanner e o
parser.
QUESTES DE REVISO
1. Dado o DFA mostrado a seguir, complete o seguinte:
a. Desenhe o classificador de caracteres que voc usaria em uma implementao
controlada por tabela desse DFA.
b. Construa a tabela de transio com base no diagrama de transio e no seu
classificador de caracteres.
c. Escreva um scanner codificado diretamente equivalente.
62 CAPTULO 2 Scanners
64 CAPTULO 2 Scanners
para implementar uma funo hash perfeita. Para um pequeno conjunto de chaves,
esta tcnica produz um reconhecedor eficiente. medida que o nmero de estados
cresce (em um reconhecedor codificado diretamente), ou o tamanho da chave cresce
(em um reconhecedor controlado por tabela), a implementao pode ficar mais lenta,
devido s restries de tamanho de cache. Em algum ponto, o impacto das falhas de
cache tornar uma implementao eficiente de uma funo hash mais tradicional mais
atraente do que a construo incremental de um DFA acclico.
Os DFAs assim produzidos no tm garantias de ser mnimos. Considere o DFA acclico que seria produzido para as REs deed, feed e seed, apresentado ao lado. Ele tem
trs caminhos distintos que reconhecem, cada um, o sufixo eed. Logicamente, esses
caminhos podem ser combinados para reduzir o nmero de estados e transies no
DFA. A minimizao combinar os estados (s2, s6, s10), (s3, s7, s11) e (s4, s8, s12) para
produzir um DFA com sete estados.
O algoritmo constri DFAs que so mnimos em relao aos prefixos de palavras na
linguagem. Qualquer duplicao toma a forma de vrios caminhos para o mesmo sufixo.
NOTAS DO CAPTULO
Originalmente, a separao da anlise lxica, ou scanning, da anlise sinttica, ou
parsing, foi justificada com o argumento da eficincia. Como o custo da anlise lxica
cresce linearmente com o nmero de caracteres, e os custos constantes so baixos, levar
a anlise lxica do parser para um scanner separado reduziu o custo da compilao.
O advento de tcnicas de anlise sinttica eficientes enfraqueceu este argumento, mas
a prtica de construo de scanners persiste, pois oferece uma separao clara de interesses entre as estruturas lxica e sinttica.
Como a construo de scanner desempenha papel secundrio na criao de um compilador real, tentamos manter este captulo conciso. Assim, ele omitiu muitos teoremas
sobre linguagens regulares e autmatos finitos, que o leitor ambicioso poderia apreciar.
Os muitos e bons textos sobre este assunto podem oferecer um tratamento muito mais
profundo dos autmatos finitos e expresses regulares e suas muitas propriedades teis
[194,232,315].
EXERCCIOS
Seo 2.2
1. Descreva, informalmente, as linguagens aceitas pelos seguintes FAs:
a.
b.
c.
66 CAPTULO 2 Scanners
Dica
Nem todas as especificaes descrevem expresses
regulares.
Seo 2.3
4. Diferentes linguagens de programao utilizam diferentes notaes para representar inteiros. Construa uma expresso regular para cada um dos seguintes:
a. Inteiros no negativos em C representados nas bases 10 e 16.
b. Inteiros no negativos em VHDL que possam incluir sublinhados (underscores), em que um caractere de sublinhado no pode ocorrer como primeiro
ou ltimo caractere.
c. Moeda, em reais, representada como um nmero decimal positivo arredondado at o centsimo mais prximo. Esse nmero comea com o caractere
$, tem pontos separando cada grupo de trs dgitos esquerda da vrgula
decimal, e termina com dois dgitos direita da vrgula decimal; por exemplo: $8.937,43 e $7.777.777,77.
5. Escreva uma expresso regular para cada uma das seguintes linguagens:
a. Dado um alfabeto O={0, 1}, L o conjunto de todas as strings de pares de
0s e pares de 1s alternados.
b. Dado um alfabeto O={0, 1}, L o conjunto de todas as strings de 0s e 1s
que contm um nmero par de 0s ou de 1s.
c. Dado o alfabeto ingls minsculo, L o conjunto de todas as strings em que
as letras aparecem em ordem lexicogrfica crescente.
d. Dado um alfabeto O={a, b, c, d}, L o conjunto de strings xyzwy, onde x
e w so strings de um ou mais caracteres em O; y qualquer caractere nico
de em O; e z o caractere z, tomado de fora do alfabeto. (Cada string xyzwy
contm duas palavras xy e wy criadas a partir das letras em O. As palavras
terminam na mesma letra, y. E so separadas por z.)
e. Dado um alfabeto O={+, , , , (, ), id}, L o conjunto de expresses algbricas usando adio, subtrao, multiplicao, diviso e parnteses sobre ids.
6. Escreva uma expresso regular para descrever cada uma das seguintes construes de linguagem de programao:
a. Qualquer sequncia de tabulaes e espaos (s vezes chamados espao em
branco).
b. Comentrios na linguagem de programao C.
c. Constantes de string (sem caracteres de escape).
d. Nmeros de ponto flutuante.
Seo 2.4
7. Considere as trs expresses regulares:
Captulo
3.1INTRODUO
A anlise sinttica (parsing) o segundo estgio do front end do compilador. O parser
trabalha com o programa transformado pelo scanner; ele v um fluxo de palavras,
onde cada palavra est associada a uma categoria sinttica (semelhante sua classe
gramatical). Ele deriva uma estrutura sinttica para o programa, encaixando as palavras
em um modelo gramatical da linguagem de programao-fonte. Se o parser determina
que o fluxo de entrada um programa vlido, constri um modelo concreto do programa
para uso pelas ltimas fases da compilao. Caso contrrio, ele informa o problema e
a informao de diagnstico apropriada ao usurio.
Como um problema, a anlise sinttica tem muitas semelhanas com a lxica. O problema formal foi bastante estudado como parte da teoria de linguagens formais; este trabalho forma a base terica para as tcnicas prticas de anlise sinttica usadas na maioria
dos compiladores. A velocidade importa; todas as tcnicas que estudaremos levam um
tempo proporcional ao tamanho do programa e de sua representao. Os detalhes de
baixo nvel afetam o desempenho; os mesmos compromissos de implementao surgem
tanto na anlise sinttica quanto na lxica. As tcnicas neste captulo so receptivas
implementao de parsers controlados por tabela, codificados diretamente ou codificados
mo. Ao contrrio dos scanners, para os quais a codificao mo comum, os parsers
gerados por ferramenta so mais comuns do que os codificados mo.
Roteiro conceitual
A tarefa principal do parser determinar se o programa de entrada ou no uma sentena sintaticamente vlida na linguagem-fonte. Antes que possamos construir parsers
que respondam a esta pergunta, precisamos de um mecanismo formal para especificar
a sintaxe da linguagem-fonte e um mtodo sistemtico para determinar a condio
de pertinncia (membership), ou seja, de ser membro dessa linguagem especificada
formalmente. Restringindo a forma da linguagem-fonte a um conjunto de linguagens
chamadas linguagens livres de contexto, podemos garantir que o parser poder responder de modo eficiente questo sobre a condio de pertinncia. A Seo3.2
69
Viso geral
Parsing
Dado um fluxo s de palavras e uma gramtica G, encontrar uma derivao em G que produza s.
Esta RE pode produzir uma expresso delimitada por parnteses, mas no uma com
parnteses internos para indicar precedncia. Todas as ocorrncias internas de ( acontecem antes de uma varivel; de modo semelhante, todas as ocorrncias internas de )
acontecem aps uma varivel. Esta observao sugere a seguinte RE:
Sentena
String de smbolos que podem ser derivados das regras
de uma gramtica.
Produo
Cada regra em uma CFG chamada produo.
Smbolo no terminal
Varivel sinttica usada nas produes de uma
gramtica.
A primeira regra, ou produo, lida como: SomOvelha pode derivar a palavra baa
seguida por mais SomOvelha. Aqui, SomOvelha uma varivel sinttica representando
o conjunto de strings que podem ser derivados da gramtica. Chamamos esta varivel
de smbolo no terminal. Cada palavra na linguagem definida pela gramtica um
smbolo terminal. A segunda regra lida como: SomOvelha tambm pode derivar a
string baa.
Smbolo terminal
Palavra que ocorre em uma sentena.
Uma palavra consiste em um lexema e sua categoria
sinttica. As palavras so representadas em uma
gramtica por sua categoria sinttica.
FORMA DE BACKUS-NAUR
A notao tradicional usada por cientistas de computao para representar uma
gramtica livre de contexto chamada forma de Backus-Naur, ou BNF (Backus-Naur
Form). BNF indica os smbolos no terminais envolvendo-os em colchetes especiais,
como em SomOvelha, e os terminais sublinhados. O smbolo ::= significa deriva e |,
tambm deriva. Em BNF, a gramtica do som da ovelha torna-se:
Para derivar uma sentena, comeamos com uma string prottipo que contm apenas o
smbolo alvo, SomOvelha. Escolhemos um smbolo no terminal, a; na string prottipo,
uma regra da gramtica, a b, e reescrevemos a como b. Repetimos este processo
de reescrita at que a string prottipo no contenha mais no terminais, ponto em
que consiste inteiramente de palavras, ou smbolos terminais, e seja uma sentena na
linguagem.
Derivao
Sequncia de etapas de reescrita que comea com o
smbolo de incio da gramtica e termina com uma
sentena na linguagem.
Em cada ponto nesse processo de derivao, a string uma coleo de smbolos terminais ou no terminais. Essa string chamada forma sentencial se ocorrer em alguma
etapa de uma derivao vlida. Qualquer forma sentencial pode ser derivada do smbolo
de incio em zero ou mais etapas. De modo semelhante, a partir de qualquer forma
sentencial, podemos derivar uma sentena vlida em zero ou mais etapas. Assim, se
comearmos com SomOvelha e aplicarmos reescritas sucessivas usando as duas regras
da gramtica, em cada etapa no processo, a string apresentar uma forma sentencial.
Quando tivermos alcanado o ponto em que a string contm apenas smbolos terminais,
ela se torna uma sentena em L(SN).
Forma sentencial
String de smbolos que ocorrem como uma etapa em
uma derivao vlida.
(Colchete)
|()
Colchete
[ Parntese ]
|[]
Neste caso, a escolha do smbolo inicial determina a forma dos delimitadores externos.
O uso de Parntese como S garante que cada sentena tenha um par mais externo
de parnteses, enquanto o uso de Colchete fora um par de colchetes. Para permitir
qualquer um deles, precisaramos introduzir um novo smbolo Incio e as produes
Incio Parntese | Colchete.
Algumas ferramentas que manipulam gramticas exigem que S no aparea no lado
direito de qualquer produo, que torna S fcil de descobrir.
Para derivar uma sentena em SN, comeamos com a string que consiste em um
smbolo, SomOvelha. Podemos reescrever SomOvelha com a regra 1 ou a regra 2. Se
reescrevermos SomOvelha com a regra 2, a string torna-se baa e no possui outras
oportunidades para reescrita. A reescrita mostra que baa uma sentena vlida em
L(SN). A outra escolha, reescrever a string inicial com a regra 1, leva a uma string com
dois smbolos: baa SomOvelha. Esta string tem um no terminal restante; reescrev-lo
com a regra 2 leva string baa baa, que uma sentena em L(SN). Podemos representar essas derivaes na forma tabular:
Regra
Forma sequencial
SomOvelha
baa
Regra
Forma sequencial
1
2
SomOvelha
baa SomOvelha
baa baa
Expr
2
3
( Expr )
Expr Op nome
nome
6
7
|
|
Op
Comeando com o smbolo inicial, Expr, podemos gerar dois tipos de subtermos:
subtermos entre parnteses, com a regra 1, ou subtermos simples, com a regra 2. Para
gerar a sentena (a+b)c, podemos usar a seguinte sequncia de reescrita
(2,6,1,2,4,3), mostrada esquerda. Lembre-se de que a gramtica lida com categorias
sintticas, como nome, em vez de lexemas, como a, b ou c.
Regra
Forma sentencial
2
6
1
2
4
3
Expr
Expr Op nome
Exprnome
( Expr )nome
( Expr Op nome )nome
( Expr+nome )nome
( nome+nome )nome
Esta CFG simples para expresses no pode gerar uma sentena com parnteses
no balanceados ou indevidamente aninhados. Somente a regra 1 pode gerar um
parntese de abertura (abre-parnteses), e tambm gera o parntese de fechamento
(fecha-parnteses). Assim, ela no pode gerar strings como a+( bc ou
a+b )c), e um parser montado a partir da gramtica no aceitar tais strings.
(A melhor RE na Seo3.2.1 combinava com essas duas strings.) Claramente, as
CFGs nos oferecem a capacidade de especificar construes que as REs no permitem.
A derivao de (a+b)c reescreveu, a cada etapa, o smbolo no terminal
restante mais direita. Esse comportamento sistemtico foi uma escolha, mas
outras so possveis. Uma alternativa bvia reescrever o no terminal mais esquerda a cada etapa. O uso das escolhas mais esquerda produziria uma sequncia
de derivao diferente para a mesma sentena. A derivao mais esquerda de
(a+b)c seria:
Regra
Forma sentencial
2
1
2
3
4
6
Expr
Expr Op nome
( Expr ) Op nome
( Expr Op nome ) Op nome
( nome Op nome ) Op nome
( nome+nome ) Op nome
( nome+nome )nome
3
4
|
|
Atribuio
...outros comandos. . .
Comando
tem duas derivaes mais direita distintas. A diferena entre elas simples. A primeira
tem Atribuio2 controlada pelo if mais interno, de modo que Atribuio2 executada
quando Expr1 verdadeira e Expr2 falsa:
A segunda derivao associa a clusula else com o primeiro if, de modo que
Atribuio2 executada quando Expr1 falsa, independente do valor de Expr2:
Comando
WithElse
|
|
A soluo restringe o conjunto de comandos que podem ocorrer na parte then de uma
construo if-then-else. Ela aceita o mesmo conjunto de sentenas da gramtica
original, mas garante que cada else tenha uma correspondncia no ambgua com um
if especfico. Ela codifica na gramtica uma regra simples vincular cada else ao
if no fechado mais interno; e s tem uma derivao mais direita para o exemplo.
Regra
Forma sentencial
1
2
3
5
Comando
if Expr then Comando
if Expr then if Expr then WithElse else Comando
if Expr then if Expr then WithElse else Atribuio
if Expr then if Expr then Atribuio else Atribuio
Forma sentencial
2
6
2
4
3
Expr
Expr Op nome
Exprnome
Expr Op nome nome
Expr+nome nome
nome + nome nome
Derivao de a+bc
Podemos usar este efeito para codificar nveis de precedncia de operador na gramtica.
Primeiro, precisamos decidir quantos nveis de precedncia so exigidos. Na gramtica
de expresso simples, temos trs: a precedncia mais alta para ( ), a mdia parae
, e a mais baixa para + e . Em seguida, agrupamos os operadores em nveis distintos e usamos um no terminal para isolar a parte correspondente da gramtica. A
Figura3.1 mostra a gramtica resultante, que inclui um nico smbolo inicial, Alvo, e
uma produo para o smbolo terminal num que usaremos em outros exemplos.
Nesta gramtica, Expr representa o nvel para+e ; Termo, o nvel parae ; e
Fator, o nvel para ( ). Desta forma, a gramtica deriva uma rvore sinttica para a
+ b c que consistente com a precedncia algbrica padro, como vemos a seguir.
Regra
Forma sentencial
1
4
6
9
9
3
6
9
Expr
Expr+Termo
Expr+TermoFator
Expr+Termonome
Expr+Fatornome
Expr+nomenome
Termo+nomenome
Fator+nomenome
nome+nomenome
Derivao de a+bc
Um percurso em ps-ordem sobre esta rvore sinttica primeiro avaliar bc, e depois
somar o resultado a a, processo este que implementa as regras padro da precedncia
aritmtica. Observe que o acrscimo de no terminais para impor a precedncia acrescenta ns interiores rvore. De modo semelhante, substituir os operadores individuais
por ocorrncias de Op, os remover.
Outras operaes exigem precedncia alta. Por exemplo, os subscritos de array devem
ser aplicados antes das operaes aritmticas padro. Isso garante, por exemplo, que
a + b[i] avalie b[i] para um valor antes de som-lo a a, ao invs de tratar i como
um subscrito em algum array cujo local calculado como a + b. De modo semelhante,
as operaes que mudam o tipo de um valor, conhecidas como type casts em linguagens
como C ou Java, possuem precedncia mais alta do que a aritmtica, porm mais baixa
do que os parnteses ou operaes de subscrito.
Se a linguagem permitir atribuio dentro das expresses, o operador de atribuio
deve ter precedncia baixa, a fim de garantir que o cdigo avalie completamente tanto
o lado esquerdo quanto o direito da atribuio antes de realiz-la. Se a atribuio ()
tivesse a mesma precedncia da adio, por exemplo, a expresso ab + c atribuiria
o valor de b a a antes de realizar a adio, considerando uma avaliao da esquerda
para a direita.
CFGs arbitrrias exigem mais tempo para fazer a anlise do que as gramticas LR(1) e
LL(1), mais restritas. Por exemplo, o algoritmo de Earley analisa sintaticamente as CFGs
arbitrrias, no pior caso, em tempo O(n3), onde n o nmero de palavras no fluxo de
entrada. Naturalmente, o tempo de execuo real pode ser melhor. Historicamente, os
construtores de compilador tm se esquivado de tcnicas universais, devido sua
ineficincia percebida.
As gramticas LR(1) incluem um grande subconjunto de CFGs no ambguas. Elas
podem ser analisadas, de baixo para cima (bottom-up), em uma varredura linear
da esquerda para a direita, examinando no mximo uma palavra frente
do smbolo de entrada atual. A grande disponibilidade de ferramentas que derivam
parsers a partir de gramticas LR(1) transformou os parsers LR(1) nos favoritos de todos.
Gramticas LL(1) so um subconjunto importante das gramticas LR(1), e podem ser
analisadas, de cima para baixo (top-down), em uma varredura linear da esquerda para
a direita, com antecipao (lookahead) de uma palavra. Gramticas LL(1) podem
ser analisadas com um parser de descida recursiva, codificado mo, ou com um
parser LL(1) gerado. Muitas linguagens de programao podem ser escritas em uma
gramtica LL(1).
As gramticas regulares (RGs) so CFGs que geram linguagens regulares. Estas so
uma CFG na qual as produes so restritas a duas formas, ou A a ou A aB, onde
A, B NT, e a T. Gramticas regulares so equivalentes a expresses regulares; e
codificam exatamente aquelas linguagens que podem ser reconhecidas por um
DFA. O uso principal para as linguagens regulares na construo de compiladores
especificar scanners.
Quase todas as construes de linguagens de programao podem ser expressas na
forma LR(1), e frequentemente na forma LL(1). Assim, a maioria dos compiladores usa
um algoritmo de anlise rpida baseado em uma dessas duas classes restritas de CFG.
FIGURA 3.3 Anlise sinttica top-down mais esquerda de a+bc com escolha oracular.
Forma sentencial
Entrada
1
1
1
Expr
Expr+Termo
Expr+Termo+Termo
Ele comea com Expr e tenta a correspondncia com a. Aplica a regra 1 para criar
a forma sentencial Expr+Termo na borda. Agora, enxerga o no terminal Expr e a
palavra de entrada a, novamente. Pela escolha consistente, aplica a regra 1 para substituir Expr por Expr+Term. Naturalmente, ele ainda tem pela frente Expr e a palavra
de entrada a. Com essa gramtica e escolhendo consistentemente as regras, o parser
continuar a expandir a borda indefinidamente, pois essa expanso nunca gera um
smbolo terminal no incio.
Recurso esquerda
Uma regra em uma CFG recursiva esquerda se o
primeiro smbolo no seu lado direito for o mesmo
smbolo no seu lado esquerdo, ou se puder derivar esse
smbolo.
O primeiro caso chamado recurso esquerda direta;
o segundo, recurso esquerda indireta.
Este problema surge porque a gramtica apresenta recurso esquerda nas produes
1, 2, 4 e 5. Com recurso esquerda, um parser top-down pode aplicar indefinidamente
as regras sem gerar um smbolo terminal no incio que ele possa corresponder (e avanar
a entrada). Felizmente, podemos reformular uma gramtica recursiva esquerda, de
modo que ela use recurso direita uma recurso que envolve o smbolo mais
direita em uma regra.
A traduo de recurso esquerda para recurso direita mecnica. Para a recurso
esquerda direta, como a mostrada a seguir no lado esquerdo, podemos reescrever as
produes individuais para usar a recurso direita, conforme indicado no lado direito.
Fee
Fee a
Fee
b Fee9
Fee9
|
a Fee9
Termo
Transformada
|
|
|
|
Expr+Termo
Expr Termo
Termo
TermoFator
Termo Fator
Fator
Expr
Expr9
|
|
Termo Expr9
+ Termo Expr9
Termo Expr9
Termo
Termo
|
|
Fator Termo9
Fator Termo9
Fator Termo9
FIGURA 3.5 Anlise sinttica top-down mais esquerda de a+bc com a gramtica de expresso
recursiva direita.
Ao final deste processo, (i=n), toda a recurso esquerda indireta foi eliminada por
meio da aplicao repetitiva do lao interno, e toda a recurso esquerda imediata foi
eliminada na etapa final de cada iterao.
FIRST
num
num
nome
nome
+
+
(
(
)
)
eof
eof
Em seguida, o algoritmo passa pelas produes, usando os conjuntos FIRST para o lado
direito de uma produo para derivar o conjunto FIRST do no terminal em seu lado
esquerdo. Este processo termina quando ele alcana um ponto fixo. Para a gramtica
de expresso recursiva direita, os conjuntos FIRST dos no terminais so:
FIRST
Expr
(,
nome,
num
Expr9
+, ,
Termo
(,
nome,
num
Termo9
, ,
Fator
(,
nome,
num
Conjunto FIRST
Para um smbolo a da gramtica, FIRST(a) o
conjunto de terminais que podem aparecer no incio de
uma sentena derivada de a;
eof ocorre implicitamente no final de cada sentena
da gramtica. Assim, ele est no domnio e no contradomnio de FIRST.
2
3
4
Expr9
|
|
+ Termo Expr9
Termo Expr9
Quando o parser tenta expandir uma Expr9, usa o smbolo de antecipao e os conjuntos
FIRST para escolher entre as regras 2, 3 e 4. Com uma antecipao de +, ele expande
pela regra 2 porque+est em FIRST(+ Term Expr9), e no em FIRST( Term Expr9),
ou FIRST(). De modo semelhante, uma antecipao de dita a escolha da regra 3.
A regra 4, -produo, apresenta um problema um pouco mais difcil. FIRST()
simplesmente {}, que no corresponde a qualquer palavra retornada pelo scanner.
FOLLOW
Expr
eof, )
Expr
eof, )
Termo
eof, +, , )
Termo
eof, +, , )
Fator
eof, +, ,
, , )
Conjunto FOLLOW
Para um no terminal a, FOLLOW(a) contm o
conjunto de palavras que podem ocorrer imediatamente aps a em uma sentena.
O parser pode usar FOLLOW(Expr9) quando tentar expandir uma Expr9. Se o smbolo
de antecedncia for +, aplica a regra 2. Se o smbolo de antecedncia for , a regra
3. Se o smbolo de antecedncia estiver em FOLLOW(Expr9), que contm eof e ),
aplica a regra 4. Qualquer outro smbolo causa um erro de sintaxe.
Usando FIRST e FOLLOW, podemos especificar exatamente a condio que torna uma
gramtica livre de retrocesso para um parser top-down. Para uma produo A ,
defina seu conjunto FIRST aumentado, FIRST+, da seguinte forma:
Agora, uma gramtica livre de retrocesso tem a propriedade de que, para qualquer no
terminal A com mltiplos lados direitos, Ab1 | b2 | | bn
Produo
Expr9
Termo9
4
8
Conjunto FIRST
{}
{}
Conjunto FIRST+
{ , eof, ) }
{ , eof, +, , ) }
A aplicao da condio livre de retrocesso a cada conjunto de lados direitos alternativos prova que a gramtica, realmente, livre de retrocesso.
Fatorao esquerda
Processo para extrair e isolar prefixos comuns em um
conjunto de produes.
11
12
13
15
16
17
Fator
ListaArgs
MaisArgs
|
|
nome
nome [ ListaArgs ]
nome ( ListaArgs )
Expr MaisArgs
, Expr MaisArgs
Como as produes 11, 12 e 13 comeam todas com nome, possuem conjuntos FIRST+
idnticos. Quando o parser tenta expandir uma ocorrncia de Fator com uma anteci-
pao de nome, no tem base para escolher entre 11, 12 e 13. O construtor de compiladores pode implementar um parser que escolhe uma regra e retrocede quando est
errado. Como alternativa, podemos transformar essas produes para criar conjuntos
FIRST+ disjuntos.
A seguinte reescrita das produes 11, 12 e 13 descreve a mesma linguagem, mas
produz conjuntos FIRST+ disjuntos:
11
12
13
14
Fator
Argumentos
|
|
nome Argumentos
[ ListaArgs ]
( ListaArgs )
Produo
2
3
4
Expr9
|
|
+ Termo Expr9
Termo Expr9
FIRST+
{+}
{}
{, eof, ) }
Para reconhecer ocorrncias de Expr9, criaremos uma rotina EPrime(). Ela segue
um esquema simples: escolher entre as trs regras (ou um erro de sintaxe) com base
nos conjuntos FIRST+ dos seus lados direitos. Para cada lado direito, o cdigo testa
diretamente quaisquer outros smbolos.
Para testar a presena de um no terminal, digamos, A, o cdigo chama o procedimento que corresponde a A. Para testar um smbolo terminal, como nome, realiza
uma comparao direta e, se tiver sucesso, avana o fluxo de entrada chamando o
scanner, NextWord(). Se ele corresponder a uma -produo, o cdigo no chama
NextWord(). A Figura3.9 mostra uma implementao direta de EPrime(). Ela
combina as regras 2 e 3, pois ambas terminam com o mesmo sufixo, Termo Expr9.
A estratgia para construir um parser de descida recursiva completo clara. Para cada
no terminal, construmos um procedimento para reconhecer seus lados direitos alternativos. Esses procedimentos chamam-se uns aos outros para reconhecer no terminais, e
reconhecem terminais pela correspondncia direta. A Figura3.10 mostra um parser de
descida recursiva para a verso recursiva direita da gramtica de expresso clssica
mostrada na Figura3.4. O cdigo para os lados direitos semelhantes foi combinado.
Para uma gramtica pequena, um construtor de compiladores pode preparar rapidamente um parser de descida recursiva. Com um pouco de cautela, este parser pode
produzir mensagens de erro precisas e informativas. O local natural para gerar essas
mensagens quando o parser deixa de encontrar um smbolo terminal esperado
dentro de EPrime, TPrime e Fator no exemplo.
Gerador de parser
Ferramenta que constri um parser a partir de especificaes, normalmente uma gramtica em uma
notao tipo BNF.
Geradores de parser tambm so chamados
compiladores de compiladores.
REVISO DA SEO
Parsers preditivos so simples, compactos e eficientes. Podem ser implementados
de diversas maneiras, incluindo parsers codificados mo, com descida recursiva e
parsers LL(1) gerados, sejam dirigidos por tabela ou codificados diretamente. Como
esses parsers conhecem, em cada ponto na anlise, o conjunto de palavras que pode
ocorrer como o prximo smbolo em uma string de entrada vlida, podem produzir
mensagens de erro precisas e teis.
A maior parte das construes de linguagens de programao pode ser expressa em
uma gramtica livre de retrocesso. Assim, essas tcnicas tm aplicao generalizada. A
restrio de que os lados direitos alternativos para um no terminal possurem conjuntos FIRST+ disjuntos no limita seriamente a utilidade das gramticas LL(1). Conforme
veremos na Seo 3.5.4, a principal desvantagem dos parsers top-down preditivos
est em sua incapacidade de lidar com a recurso esquerda. Gramticas recursivas
esquerda modelam a associatividade da esquerda para a direita dos operadores de
expresso de forma mais natural do que as gramticas recursivas direita.
QUESTES DE REVISO
1. Para construir um parser top-down eficiente, o construtor de compiladores precisa
expressar a linguagem-fonte de uma forma um tanto restrita. Explique as restries
sobre a gramtica da linguagem-fonte que so exigidas para torn-la receptiva
anlise sinttica top-down eficiente.
2. Cite duas vantagens em potencial de um parser com descida recursiva codificado
mo sobre um gerado, dirigido por tabela LL(1), e duas vantagens do parser LL(1)
sobre a implementao com descida recursiva.
Para estender a fronteira para cima, o parser examina a fronteira atual em busca de uma
substring que corresponda ao lado direito de alguma produo A b. Se encontrar b
na fronteira, com sua extremidade direita em k, pode substituir b por A, para criar uma
nova fronteira. Se a substituio de A por b na posio k for a prxima etapa em uma
derivao vlida para a string de entrada, ento o par A b,k um leque (handle)
na derivao atual, e o parser dever substituir b por A. Essa substituio chamada
reduo, porque reduz o nmero de smbolos na fronteira, a menos que |b|=1. Se o
parser estiver montando uma rvore sinttica, constri um n para A, acrescenta-o
rvore e conecta os ns representando b como filhos de A.
Encontrar handles a questo-chave que surge na anlise bottom-up. As tcnicas
apresentadas nas prximas sees formam um mecanismo de localizao de handle
particularmente eficiente. Retornaremos a esta questo periodicamente no decorrer da
Seo 3.4. Mas, primeiro, vamos terminar nossa descrio de alto nvel dos parsers
bottom-up.
O parser bottom-up repete um processo simples. Encontra um handle A b,k
na fronteira, e substitui a ocorrncia de b em k por A. Esse processo continua at
que ele: (1) reduza a fronteira para um nico n que representa o smbolo-alvo
da gramtica, ou (2) no possa encontrar um handle. No primeiro caso, o parser
encontrou uma derivao; se tambm tiver consumido todas as palavras no fluxo
de entrada (ou seja, a prxima palavra eof), ento a anlise tem sucesso. No
segundo caso, ele no pode montar uma derivao para o fluxo de entrada e deve
relatar esta falha.
Uma anlise bem-sucedida passa por cada etapa da derivao. Quando uma anlise
falha, o parser deve usar o contexto acumulado na derivao parcial para produzir uma
mensagem de erro significativa. Em muitos casos, o parser pode se recuperar do erro
e continuar analisando, de modo que descubra o mximo de erros sintticos possveis
em uma nica anlise (ver Seo3.5.1).
O relacionamento entre a derivao e a anlise desempenha um papel crtico para
tornar a anlise bottom-up tanto correta quanto eficiente. O parser bottom-up funciona
a partir da sentena final em direo ao smbolo-alvo, enquanto uma derivao comea
no smbolo-alvo e trabalha em direo sentena final. O parser, ento, descobre as
etapas da derivao na ordem contrria. Para uma derivao
o parser bottom-up descobre gi gi+1 antes de gi1 gi. O modo como monta a rvore sinttica fora esta ordem. O parser precisa acrescentar o n para gi na fronteira
antes que possa corresponder gi.
O scanner retorna palavras classificadas na ordem da esquerda para a direita. Para
reconciliar esta ordem do scanner com a derivao reversa construda pelo scanner, um
parser bottom-up procura uma derivao mais direita. Nesta, a folha mais esquerda
considerada por ltimo. Reverter esta ordem leva ao comportamento desejado: folha
mais esquerda primeiro e folha mais direita por ltimo.
Em cada ponto, o parser opera na fronteira da rvore sinttica parcialmente construda;
a fronteira atual um prefixo da forma sentencial correspondente na derivao. Como
cada forma sentencial ocorre em uma derivao mais direita, o sufixo no examinado
consiste inteiramente de smbolos terminais. Quando o parser precisa de mais contexto
da direita, chama o scanner.
Handle
Um par, A b, k, tal que b aparece na fronteira
com sua extremidade direita na posio k, e a substituio b por A a prxima etapa na anlise.
Reduo
A reduo da fronteira de uma anlise bottom-up
usando A b substitui b por A na fronteira.
Com uma gramtica no ambgua, a derivao mais direita nica. Para uma grande
classe de gramticas no ambguas, gi-1 pode ser determinado diretamente de gi (a
fronteira superior da rvore sinttica) e uma quantidade limitada de antecipao no
fluxo de entrada. Em outras palavras, dada uma fronteira gi e um nmero limitado de
palavras classificadas adicionais, o parser pode encontrar o handle que leva gi para gi-1.
Para tais gramticas, podemos construir um localizador de handle eficiente usando
uma tcnica chamada anlise sinttica LR. Esta seo examina um tipo em particular
de parser LR, chamado parser LR(1) dirigido por tabela.
Um parser LR(1) varre a entrada da esquerda para a direita para montar uma derivao
mais direita reversa. Em cada etapa, toma decises com base na histria da anlise
e uma antecipao de, no mximo, um smbolo. O nome LR(1) deriva destas propriedades: varredura da esquerda para a direita (Left-to-right), derivao mais direita
Reversa, e um (1) smbolo de antecedncia.
Informalmente, diremos que uma linguagem tem a propriedade LR(1) se ela puder ser
analisada em uma nica varredura da esquerda para a direita para montar uma derivao
mais direita reversa, usando apenas um smbolo de antecipao para determinar as
aes de anlise. Na prtica, o teste mais simples para determinar se uma gramtica
tem a propriedade LR(1) permitir que um gerador de parser tente construir o parser
LR(1). Se este processo falhar, a gramtica no tem a propriedade LR(1). O restante
desta seo apresenta os parsers LR(1) e sua operao. A Seo3.4.2 apresenta a
construo de um algoritmo para montar as tabelas que codificam um parser LR(1).
Para encontrar o prximo handle, o parser LR(1) desloca smbolos para a pilha at
que o autmato encontre a extremidade direita de um handle no topo da pilha. Quando
tiver um handle, o parser o reduz pela produo correspondente. Para fazer isto, ele
retira os smbolos de b da pilha e empilha o lado esquerdo correspondente, A. As
tabelas Action e Goto encadeiam aes de deslocar (shift) e reduzir (reduce) em
uma sequncia dirigida pela gramtica, que encontra uma derivao mais direita
reversa, se existir.
Para concretizar isto, considere a gramtica mostrada na Figura3.16a, que descreve a
linguagem de parnteses devidamente aninhados. A Figura3.16b mostra as tabelas Action e Goto para esta gramtica, onde acc significa aceitar (accept), sj significa deslocar (shift) e passar para o estado j, e rj significa reduzir (reduce) usando a produo j.
Quando usadas com o esqueleto de parser LR(1), elas criam um parser para a linguagem
de parnteses.
Para entender o comportamento do esqueleto de parser LR(1), considere a sequncia
de aes que ele toma na string de entrada ( ).
Iterao
inicial
1
2
3
4
5
Estado
0
3
7
2
1
Palavra
(
(
)
eof
eof
eof
Pilha
$
$
$
$
$
$
0
0
0
0
0
0
(3
(3
Par
Lista
)7
2
1
Handle
nada
nada
nada
()
Par
Lista
Ao
s3
s7
r5
r3
acc
A primeira linha mostra o estado inicial do parser. As linhas subsequentes, mostram seu
estado no incio do lao while, junto com a ao que ele toma. No incio da primeira
iterao, a pilha no contm um handle, de modo que o parser desloca o smbolo de
antecipao, (, e o empilha. Pela tabela Action, ele sabe deslocar e passar para o
estado 3. No incio da segunda iterao, a pilha ainda no contm um handle, de modo
que o parser desloca ) e o empilha para acumular mais contexto. E, ento, passa para
o estado 7.
Em um parser LR, o handle sempre posicionado
no topo da pilha e a cadeia de handles produz uma
derivao mais direita reversa.
Na terceira iterao, a situao mudou. A pilha contm um handle, Par ( ),t, onde t
o topo da pilha. A tabela Action direciona o parser para reduzir ( ) a Par. Usando
o estado abaixo de Par na pilha, 0, e Par, o parser passa para o estado 2 (especificado
por Goto[0,Par]). No estado 2, com Par no topo da pilha e eof como seu smbolo
de antecipao, ele encontra o handle Lista Par,t e reduz, o que deixa o parser
no estado 1 (especificado por Goto[0,Lista]). Finalmente, no estado 1, com Lista no
topo da pilha e eof como smbolo de antecipao, o parser descobre o handle Alvo
Lista,t. A tabela Action codifica esta situao como uma ao aceitar (acc), e,
assim, a anlise sinttica termina.
Este parse exigiu dois deslocamentos (shifts) e trs redues (reduces). Parsers LR(1)
tomam um tempo proporcional ao tamanho da entrada (um deslocamento por palavra retornada do scanner) e o comprimento da derivao (uma reduo por etapa na
derivao). Em geral, no podemos esperar descobrir a derivao para uma sentena
em menos etapas do que isto.
A Figura3.17 mostra o comportamento do parser sobre a string de entrada, ( ( ) )
( ). Ele realiza seis shifts, cinco reduces e um accept nesta entrada. A Figura3.18
mostra o estado da rvore sinttica parcialmente montada no incio de cada iterao
do lao while do parser. O topo de cada desenho mostra um nmero de iterao e uma
barra cinza que contm a fronteira superior da rvore sinttica parcial. No parser LR(1),
essa fronteira aparece na pilha.
Localizao de handles
As aes do parser derramam conhecimento adicional no processo de localizao de
handles. Considere suas aes sobre a string ( ), como mostra a tabela no incio
desta seo. O parser encontra um handle em cada uma das iteraes 3, 4 e 5. Na
iterao 3, a fronteira de ( ) corresponde claramente ao lado direito da produo 5.
Pela tabela Action, vemos que uma antecipao de eof ou ( implica uma reduo
pela produo 5. Depois, na iterao 4, o parser reconhece que Par, seguido por uma
antecipao de eof ou (, constitui um handle para a reduo por Lista Par. O
handle final do parser, Lista com antecipao de eof no estado 1, dispara a ao de
aceitao (accept).
Para entender como os estados preservados na pilha mudam o comportamento do parser,
considere as aes deste parser sobre nossa segunda string de entrada, ( ( ) )
( ), como mostra a Figura3.17. Inicialmente, o parser desloca (, (, e ) para a pilha, nas iteraes 1 a 3. Na iterao 4, reduz pela produo 5; ento, substitui os dois
smbolos do topo na pilha, ( e ), por Par e passa para o estado 5.
Entre estes dois exemplos, o parser reconheceu a string ( ) no topo da pilha como
um handle trs vezes. Ele se comportou de forma diferente em cada caso, com base no
contexto esquerdo anterior codificado na pilha. A comparao dessas trs simulaes
expe como os estados empilhados controlam a direo futura da anlise sinttica.
Com o primeiro exemplo, ( ), o parser estava em s7 com uma antecipao de eof
quando encontrou o handle. A reduo revela s0 abaixo de ( ), e Goto[s0,Par] s2.
Em s2, uma antecipao de eof leva a outra reduo seguida por uma ao accept.
Uma antecipao de ) em s2 produz um erro.
O segundo exemplo, ( ( ) ) ( ), encontra um handle para ( ) duas vezes. O
primeiro ocorre na iterao 4. O parser est em s10 com uma antecipao de ). Antes,
ele deslocou (, ( e ) para a pilha. A tabela Action indica r5, de modo que o parser
reduz usando Par ( ). A reduo revela s3 abaixo de ( ), e Goto[s3,Par] s5,
um estado em que outros )s so vlidos. A segunda vez que ele encontra ( ) como
um handle ocorre na iterao 10. A reduo revela s1 abaixo de ( ) e leva o parser
para s4. Em s4, uma antecipao de eof ou ( dispara uma reduo de Lista Par para
Lista, enquanto uma antecipao de ) um erro.
As tabelas Action e Goto, junto com a pilha, fazem com que o parser rastreie antes do
contexto da esquerda anterior e permitem que ele tome aes diferentes com base nesse
contexto. Assim, o parser trata corretamente cada uma das trs ocorrncias nas quais
encontrou um handle para ( ). Vamos retornar a esta questo quando examinarmos
a construo de Action e Goto.
Iterao
inicial
1
2
3
Estado
0
3
7
word
(
(
)
)
Pilha
$0
$0
$0
$0
(3
(3
)7
Handle
nada
nada
nada
nada
Ao
s3
s7
erro
O uso de parsers LR
A chave para a anlise LR est na construo das tabelas Action e Goto.
Elas codificam todas as sequncias de reduo vlidas que podem surgir em uma
derivao mais direita reversa para determinada gramtica. Embora o nmero
dessas sequncias seja muito grande, a gramtica em si restringe a ordem em que
as redues podem ocorrer.
O construtor de compiladores pode montar tabelas Action e Goto manualmente.
Porm, o algoritmo para esta construo exige um tratamento meticuloso; este um
exemplo importante do tipo de tarefa que deve ser automatizada e relegada a umcomputador. Existem muitos programas que automatizam esta construo. A prxima
seo apresenta um algoritmo que pode ser usado para construir tabelas de anlise
sinttica LR(1).
Com um gerador de parser LR(1), o papel do construtor de compiladores definir
a gramtica e garantir que ela tenha a propriedade LR(1). Na prtica, o gerador de
tabelas LR(1) identifica as produes que so ambguas ou expressas de um modo
que exija mais de uma palavra de antecipao para distinguir entre uma ao de
deslocamento (shift) e uma de reduo (reduce). Conforme estudarmos o algoritmo
de construo de tabelas, veremos como esses problemas surgem, como resolv-los
e como entender os tipos de informao de diagnstico que os geradores de parser
LR(1) produzem.
1
2
3
4
5
Alvo Lista
Lista Lista Par
| Par
Par ( Par )
|()
Itens LR(1)
Em um parser LR(1), as tabelas Action e Goto codificam informaes sobre os
handles em potencial em cada etapa da anlise. O algoritmo de construo de tabelas,
portanto, precisa de uma representao concreta tanto para handles como para handles
em potencial, e seus smbolos de antecipao associados. Representamos cada handle em
potencial com um item LR(1). Um item LR(1) [Ab g, a] consiste em uma produo
A bg; um marcador de lugar, , que indica a posio do topo da pilha no lado direito
da produo; e um smbolo terminal especfico, a, como um smbolo de antecipao.
O algoritmo de construo de tabelas usa itens LR(1) para construir um modelo dos
conjuntos de estados vlidos para o parser, a coleo cannica de conjuntos de itens
LR(1). Designamos a coleo cannica CC={cc0, cc1, cc2, ..., ccn }. O algoritmo constri CC seguindo possveis derivaes na gramtica; na coleo final, cada conjunto cci
em CC contm o conjunto de handles em potencial em alguma configurao de parser
possvel. Antes de nos aprofundarmos na construo da tabela, preciso explicar mais
sobre os itens LR(1).
Para uma produo A bg e um smbolo de antecipao a, o marcador de lugar pode
gerar trs itens distintos, cada um com sua prpria interpretao. Em cada caso, a
presena do item em algum conjunto cci na coleo cannica indica que uma entrada
que o parser viu consistente com a ocorrncia de um A seguido por um a na gramtica.
A posio de no item distingue entre os trs casos.
1. [A bg,a] indica que um A seria vlido e que reconhecer um b em seguida
seria uma etapa em direo descoberta de um A. Chamamos tal item de possibilidade, pois representa um trmino possvel para a entrada j vista.
2. [A b g,a] indica que o parser prosseguiu do estado [A bg,a] reconhecendo b. O b consistente com o reconhecimento de um A. Uma etapa seguinte
vlida seria reconhecer um g. Chamamos tal item de parcialmente completo.
O procedimento closure
Para calcular o estado inicial completo do parser, cc0, a partir do seu ncleo, o algoritmo
deve acrescentar a ele todos os itens implicados por aqueles presentes no ncleo. A
Figura3.20 mostra um algoritmo para este clculo. O procedimento closure percorre todos os itens no conjunto s. Se um marcador de lugar em um item precede
imediatamente algum no terminal C, ento closure deve acrescentar um ou mais
itens para cada produo que pode derivar C. Closure coloca o marcador na posio
inicial de cada item construdo desta forma.
O raciocnio para closure claro. Se [A b Cd,a] s, ento uma string que reduz
a C seguida por da completar o contexto da esquerda. O reconhecimento de um C
seguido por da deve causar uma reduo para A, pois isto completa o lado direito da
produo (Cd) e em seguida h um smbolo de antecipao vlido.
Para construir os itens para uma produo Cg, closure insere o marcador de lugar
antes de g e acrescenta os smbolos de antecipao apropriados cada terminal que
pode aparecer como o smbolo inicial em da. Isto inclui cada terminal em FIRST(d).
Se FIRST(d), ele tambm inclui a. A notao FIRST(da) no algoritmo representa
esta extenso do conjunto FIRST para uma string desta forma. Se d for , isto recai
em FIRST(a)={a}.
Para a gramtica de parnteses, o item inicial [Alvo Lista, eof]. Aplicando
closure a este conjunto, os seguintes itens so acrescentados:
Esses oito itens, junto com [Alvo Lista, eof], constituem o conjunto cc 0 na
coleo cannica. A ordem em que closure acrescenta os itens depende de como
a implementao de conjunto gerencia a interao entre o repetidor for each item e a
unio de conjunto no lao mais interno.
O procedimento closure outra computao de ponto fixo. O lao triplamente
aninhado, ou acrescenta itens a s, ou deixa s intacto. Ele nunca remove um item de s.
Como o conjunto de itens LR(1) finito, esse lao deve terminar. O lao triplamente
aninhado oneroso. Porm, um exame de perto revela que cada item em s precisa
ser processado somente uma vez. Uma verso bem elaborada do algoritmo poderia
aproveitar este fato.
O procedimento goto
A segunda operao fundamental para a construo da coleo cannica a funo
goto. O procedimento goto usa como entrada um modelo de um estado do parser,
representado como um conjunto cci na coleo cannica, e um smbolo x da gramtica.
Ele calcula, a partir de cci e x, um modelo do estado do parser que resultaria do reconhecimento de um x no estado i.
Para encontrar o conjunto de estados que deriva diretamente de algum estado como cc0,
o algoritmo pode calcular goto(cc0, ) para cadaque ocorre aps um marcador
em um item em cc0. Isto produz todos os conjuntos que esto um smbolo distante de
cc0. Para calcular a coleo cannica completa, simplesmente repetimos este processo
at um ponto fixo.
O algoritmo
Para construir a coleo cannica de conjuntos de itens LR(1), o algoritmo calcula o
conjunto inicial, cc0, e depois encontra sistematicamente todos os conjuntos de itens
LR(1) que podem ser alcanados a partir de cc0. E repetidamente aplica goto aos novos
conjuntos em CC; goto, por sua vez, usa closure. A Figura3.22 mostra o algoritmo.
Para uma gramtica com a produo alvo SS, o algoritmo comea inicializando
CC para conter cc0, conforme j descrito. Em seguida, sistematicamente estende CC
procurando qualquer transio de um estado em CC para um estado que ainda no
est em CC. Ele faz isto construtivamente, montando cada estado possvel, temp, e
testando se temp um membro de CC. Se temp for novo, ele acrescenta temp a CC.
Sendo temp novo ou no, ele registra a transio de cci para temp para uso posterior
na montagem da tabela Goto do parser.
Para garantir que processe cada conjunto cci apenas uma vez, o algoritmo usa um esquema de marcao simples. Ele cria cada conjunto em uma condio no marcada e
marca o conjunto medida que o conjunto processado. Isto reduz drasticamente o
nmero de vezes que ele chama goto e closure.
Essa construo uma computao de ponto fixo. A coleo cannica, CC, um
subconjunto do conjunto potncia dos itens LR(1). O lao while monotnico, acrescenta novos conjuntos a CC e nunca os remove. Se o conjunto de itens LR(1) tiver
n elementos, ento CC no poder crescer para mais do que 2n itens, de modo que a
computao deve parar.
Esse limite superior no tamanho de CC muito frouxo. Por exemplo, a gramtica
de parnteses tem 33 itens LR(1) e produz apenas 12 conjuntos em CC. O limite
superior seria 233, um nmero muito maior. Para gramticas mais complexas, |CC|
um problema, principalmente porque as tabelas Action e Goto crescem com
|CC|. Conforme descrevemos na Seo 3.6, tanto o construtor de compilador quanto
o construtor de gerador de parser podem efetuar etapas para reduzir o tamanho
dessas tabelas.
Como cada item tem o marcador no incio do seu lado direito, cc0 contm apenaspossibilidades. O que apropriado, pois o estado inicial do parser. A primeira iterao
do lao while produz trs conjuntos, cc1, cc2 e cc3. Todas as outras combinaes na
primeira iterao produzem conjuntos vazios, conforme indicado na Figura3.23, que
rastreiam a construo de CC.
O conjunto cc2 representa as configuraes do parser aps ele ter reconhecido um Par
inicial. Os dois itens so handles para uma reduo usando Lista Par.
O contexto esquerdo para este conjunto cc1, que representa um estado onde o parser
reconheceu uma ou mais ocorrncias de Lista. Quando ento reconhece um Par, ele
entra nesse estado. Os dois itens representam uma reduo usando Lista Lista Par.
Se, no estado 3, o parser encontrar um ), faz a transio para cc7. Os dois itens especificam uma reduo usando Par ( ).
A terceira iterao do lao while tenta derivar novos conjuntos a partir de cc4, cc5,
cc6 e cc7. Trs das combinaes produzem novos conjuntos, enquanto uma produz uma
transio para um estado existente.
Este conjunto contm um item, que especifica uma reduo para Par.
A quarta iterao do lao while tenta derivar novos conjuntos a partir de cc8, cc9 e
cc10. Somente uma combinao cria um conjunto no vazio.
A iterao final do lao while tenta derivar novos conjuntos a partir de cc 11. Ela
encontra apenas conjuntos vazios, de modo que a construo termina com 12 conjuntos,
cc0 at cc11.
Como o parser LR(1) pode usar um DFA para encontrar os handles quando sabemos
que a linguagem dos parnteses no uma linguagem regular? Este parser conta
com uma observao simples: o conjunto de handles finito. O conjunto de handles
exatamente o conjunto de itens LR(1) completos aqueles com o marcador no
extremo direito da produo do item. Qualquer linguagem com um conjunto finitode
sentenas pode ser reconhecida por um DFA. Como o nmero de produes e o
de smbolos de antecipao so ambos finitos, o nmero de itens completos tambm
o , e a linguagem de handles uma linguagem regular.
Quando o parser LR(1) executado, intercala dois tipos de aes: shifts e reduces. As
aes shift simulam as etapas no DFA de localizao de handles. O parser realiza uma
ao shift por palavra no fluxo de entrada. Quando o DFA de localizao de handles
alcana um estado final, o parser LR(1) realiza uma ao reduce, que reinicia o estado
do DFA de localizao de handles para refletir o fato de que o parser reconheceu um
handle e o substituiu por um no terminal. Para tanto, o parser remove o handle e seu
estado da pilha, revelando um estado mais antigo. O parser usa esse estado mais antigo,
o smbolo de antecipao e a tabela Goto para descobrir o estado no DFA a partir do
qual a localizao de handle deve continuar.
As aes reduce unem fases de localizao de handles sucessivas. A reduo utiliza
o contexto da esquerda o estado revelado pela reduo resume a histria anterior
da anlise sinttica para reiniciar o DFA de localizao de handles em um estado
que reflita o no terminal que o parser acabou de reconhecer. Por exemplo, na anlise
sinttica de ( ( ) ) ( ), o parser empilhou uma ocorrncia do estado 3 para
cada ( que encontrou. Esses estados empilhados permitem que o algoritmo faa a
correspondncia dos parnteses de abertura e fechamento.
Observe que o DFA de localizao de handles tem transies tanto nos smbolos
terminais quanto nos smbolos no terminais. O parser percorre os limites de um no
terminal apenas em uma ao reduce. Cada uma dessas transies, mostradas em cinza
na Figura3.25, corresponde a uma entrada vlida na tabela Goto. O efeito combinado
das aes de terminal e de no terminal chamar o DFA recursivamente toda vez que
tiver de reconhecer um no terminal.
Alvo
Cmd
|
|
Cmd
if expr then Cmd
if expr then Cmd else Cmd
assign
Ela tem dois smbolos no terminais, Alvo e Cmd, e seis smbolos terminais, if,
expr, then, else, assign e eof implcito.
A construo comea inicializando cc0 para o item [Alvo Cmd, eof] e executando
closure para produzir o primeiro conjunto.
A segunda iterao examina transies a partir desses trs novos conjuntos. Apenas
uma combinao produz um novo conjunto, a partir de cc2 com o smbolo expr.
A prxima iterao calcula transies de cc4; ela cria cc5 como goto(cc4, then).
A quarta iterao examina transies a partir de cc5. Ela cria novos conjuntos para
Cmd, para if e para assign.
A quinta iterao examina cc 6, cc7 e cc8. Embora a maior parte das combinaes
produza o conjunto vazio, duas delas levam a novos conjuntos. A transio em else
a partir de cc6 leva a cc9, e a sobre expr a partir de cc7 cria cc10.
A stima iterao cria cc13 a partir de cc12 em Cmd. E recria cc7 e cc8.
A iterao oito encontra um novo conjunto, cc14, a partir de cc13 na transio para else.
A iterao nove gera cc15 a partir de cc14 na transio para Cmd, junto com duplicatas
de cc7 e cc8.
A iterao final examina cc15. Como o marcador est no final de cada item em cc15,
ele s pode gerar conjuntos vazios. Neste ponto, nenhum conjunto adicional de itens
pode ser acrescentado coleo cannica, de modo que o algoritmo alcanou um
ponto fixo, e para.
A ambiguidade na gramtica torna-se aparente durante o algoritmo de preenchimento
de tabela. Os itens nos estados de cc0 a cc12 no geram conflitos. O estado cc13 contm
quatro itens:
1. [Cmd if
2. [Cmd if
3. [Cmd if
4. [Cmd if
expr
expr
expr
expr
O item 1 gera uma entrada reduce para cc13 e a antecipao else; o item 3 gera uma
entrada shift para o mesmo local na tabela. Claramente, a entrada da tabela no pode
manter as duas aes. Esse conflito shift-reduce indica que a gramtica ambgua. Os
itens 2 e 4 geram um conflito shift-reduce semelhante com uma antecipao de eof.
Quando o algoritmo de preenchimento de tabela encontra esse conflito, a construo ter
falhado. O gerador de tabela deve relatar o problema uma ambiguidade fundamental
entre as produes nos itens LR(1) especficos ao construtor de compiladores.
Neste caso, o conflito surge porque a produo 2 na gramtica um prefixo da produo
3. O gerador de tabela poderia ser projetado para resolver esse conflito em favor do
deslocamento (shift); isto fora o parser a reconhecer a produo mais longa e vincula
o else ao if mais prximo (mais interno).
Uma gramtica ambgua tambm pode produzir um conflito reduce-reduce, que pode
ocorrer se ela contiver duas produes A g d e B g d com o mesmo lado direito
g d. Se um estado contm os itens [A g d , a] e [B g d , a], ento ele gerar
duas aes reduce em conflito para o smbolo de antecipao a uma para cada
produo. Novamente, esse conflito reflete uma ambiguidade fundamental na gramtica
bsica; o construtor de compiladores deve remodelar a gramtica para elimin-la (ver
Seo3.5.3).
Como existem muitos geradores de parser que automatizam este processo, o mtodo
de escolha para determinar se uma gramtica tem a propriedade LR(1) invocar um
gerador de parser LR(1) sobre ela. Se o processo tiver sucesso, a gramtica tem a
propriedade LR(1).
REVISO DA SEO
Parsers LR(1) so muito usados nos compiladores construdos tanto na indstria como
nas universidades. Eles aceitam uma grande classe de linguagens e usam um tempo
proporcional ao tamanho da derivao que constroem. Existem muitas ferramentas que
geram um parser LR(1) em uma grande variedade de linguagens de implementao.
O algoritmo de construo de tabela LR(1) uma aplicao elegante da teoria na
prtica. Ele constri sistematicamente um modelo do DFA de reconhecimento de
handles e depois traduz esse modelo em um par de tabelas que controlam o esqueleto de parser. A construo de tabela um empreendimento complexo, que
exige ateno cuidadosa aos detalhes. Este exatamente o tipo de tarefa que deve ser
automatizada geradores de parser so melhores para seguir essas longas cadeias
de computaes do que os humanos. Apesar disso, um construtor de compiladores
habilidoso deve entender os algoritmos de construo de tabela, pois oferecem percepes de como os parsers funcionam, quais tipos de erros o gerador de parser pode
encontrar, como esses erros surgem e como eles podem ser remediados.
QUESTES DE REVISO
1. Mostre as etapas que o esqueleto de parser LR(1), com as tabelas para a gramtica
de parnteses, assumiria com a string de entrada ( ( ) ( ) ) ( ).
2. Monte as tabelas LR(1) para a gramtica SomOvelha, dada na Seo 3.2.2, e mostre
as aes do esqueleto de parser para a entrada baa baa baa.
uma ou mais palavras que o parser possa usar para sincronizar a entrada com seu estado interno. Quando o parser encontra um erro, descarta os smbolos de entrada at
encontrar uma palavra de sincronismo, e depois reinicia seu estado interno para um
coerente com a palavra de sincronismo.
Em uma linguagem tipo Algol, com ponto e vrgula como separador de comandos,
este caractere normalmente usado como palavra de sincronismo. Quando ocorre um
erro, o parser chama o scanner repetidamente at encontrar um ponto e vrgula. Depois,
muda de estado para um que teria resultado do reconhecimento bem-sucedido de um
comando completo, ao invs de um erro.
Em um parser com descida recursiva, o cdigo pode simplesmente descartar palavras at
encontrar um ponto e vrgula. Nesse ponto, pode retornar o controle para o ponto onde
a rotina que analisa os comandos relata o sucesso. Isso pode envolver a manipulao
da pilha de execuo ou o uso de um salto no local como setjmp e longjmp da
linguagem C.
Em um parser LR(1), este tipo de ressincronizao mais complexo. O parser descarta a entrada at que encontre um ponto e vrgula. Em seguida, varre a pilha de
anlise a partir do topo at encontrar um estado s tal que Goto[s, Comando] seja uma
entrada vlida, sem erro. O primeiro estado desse tipo na pilha representa o comando
que contm o erro. A rotina de recuperao de erro, ento, descarta entradas na pilha
acima deste estado, empilha o estado Goto[s, Comando] e retoma a anlise normal.
Em um parser controlado por tabela, seja LL(1) ou LR(1), o compilador precisa de
um modo de dizer ao gerador de parser onde sincronizar. Isto pode ser feito usando
produes de erro uma produo cujo lado direito inclui uma palavra reservada que
indica um ponto de sincronizao de erro e um ou mais tokens de sincronizao. Com
tal construo, o gerador de parser pode construir rotinas de recuperao de erro que
implementem o comportamento desejado.
Naturalmente, as rotinas de recuperao de erro devem tomar medidas para garantir
que o compilador no tente gerar e otimizar cdigo para um programa sintaticamente
invlido. Isto exige um protocolo simples entre o aparato de recuperao de erro e o
controlador de alto nvel que invoca as diversas partes do compilador.
O scanner, sem dvida, classifica fee como um nome em qualquer caso. Uma chamada
de funo e uma referncia de array podem aparecer em muitas das mesmas situaes.
Nenhuma dessas construes aparece na gramtica de expresso clssica. Podemos
acrescentar produes que as derivam a partir de Fator.
Fator
RefernciaFuno
RefernciaArray
|
|
|
|
RefernciaFuno
RefernciaArray
( Expr )
num
nome
nome ( ListaArg )
nome ( ListaArg )
Como as duas ltimas produes possuem lados direitos idnticos, essa gramtica
ambgua, que cria um conflito reduce-reduce em um gerador de tabela LR(1).
Resolver essa ambiguidade exige conhecimento extrassinttico. Em um parser de descida
recursiva, o construtor de compiladores pode combinar o cdigo para RefernciaFuno
e RefernciaArray e acrescentar o cdigo extra exigido para verificar o tipo declarado
de nome. Em um parser controlado por tabela, construdo por um gerador de parser, a
soluo deve funcionar dentro do framework fornecido pelas ferramentas.
Duas tcnicas diferentes tm sido usadas para solucionar este problema. O construtor de compiladores pode reescrever a gramtica para combinar tanto a chamada
de funo quanto a referncia de array em uma nica produo. Nesse esquema, a
questo adiada at uma etapa posterior na traduo, quando pode ser resolvida com
informaes obtidas das declaraes. O parser precisa construir uma representao que
preserva todas as informaes necessrias por qualquer resoluo; a etapa posterior,
ento, reescrever a referncia na sua forma apropriada como uma referncia de array
ou uma chamada de funo.
Como alternativa, o scanner pode classificar identificadores com base em seus tipos declarados, ao invs de suas propriedades microssintticas. Essa classificao exige algum
protocolo entre o scanner e o parser; a coordenao no difcil de arranjar, desde que
a linguagem tenha uma regra do tipo: definir antes de usar. Como a declarao
analisada sintaticamente antes que ocorra o uso, o parser pode disponibilizar sua tabela
de smbolos interna para o scanner para classificar identificadores em classes distintas,
como nome-varivel e nome-funo. As produes relevantes tornam-se:
Profundidade de pilha
Em geral, a recurso esquerda pode levar a profundidades de pilha menores. Considere duas gramticas alternativas para uma construo de lista simples, mostrada nas
Figuras3.28a e3.28b. (Observe a semelhana com a gramtica de SomOvelha.) O
uso dessas gramticas para produzir uma lista de cinco elementos leva s derivaes
mostradas nas Figuras3.28c e3.28d, respectivamente. Um parser LR(1) construiria
essas sequncias de forma reversa. Assim, se lermos a derivao da linha inferior para
a superior, poderemos seguir as aes do parser para cada gramtica.
1. Gramtica recursiva esquerda. Esta gramtica desloca elt1 para sua pilha
e a reduz imediatamente para Lista. Em seguida, desloca elt2 para a pilha e
o reduz para Lista. Ela prossegue at que tenha deslocado cada um dos cinco
eltis para a pilha e os tenha reduzido para Lista. Assim, a pilha alcana uma
profundidade mxima de dois e uma profundidade mdia de 10 = 1 2 .
6
2. Gramtica recursiva direita. Esta verso desloca todos os cinco eltis para sua
pilha. Em seguida, reduz elt5 para Lista usando a regra dois, e os eltis restantes
usando a regra um. Assim, sua profundidade de pilha mxima ser cinco e sua
mdia ser 20 = 3 1 .
6
A gramtica recursiva direita exige mais espao de pilha; sua profundidade de pilha
mxima est limitada apenas pelo comprimento da lista. Ao contrrio, a profundidade
de pilha mxima com a gramtica recursiva esquerda depende da gramtica, ao invs
do fluxo de entrada.
Para listas curtas, isto no problema. Porm, se a lista representa a lista de instrues
de um longo cdigo, ela pode ter centenas de elementos. Neste caso, a diferena no
espao pode ser muito grande. Se todos os demais aspectos forem iguais, a menor
altura de pilha uma vantagem.
Associatividade
rvore sinttica abstrata (AST Abstract
Syntax Tree)
Uma AST uma contrao da rvore sinttica. Veja a
Seo 5.2.1.
|
|
Expr+Operando
Expr Operando
Operando
Expr
|
|
Operando+Expr
Operando Expr
Operando
REVISO DA SEO
A criao de um compilador envolve mais do que apenas transcrever a gramtica de
alguma definio de linguagem. Na escrita da gramtica, surgem muitas escolhas que
tm impacto sobre a funo e a utilidade do compilador resultante. Esta seo tratou
de uma srie de questes, variando desde como realizar a recuperao de erro at a
relao de compromisso entre a recurso esquerda e a recurso direita.
QUESTES DE REVISO
1. A linguagem de programao C usa colchetes para indicar um subscrito de array
e parnteses para indicar uma lista de argumentos de procedimento ou funo.
Como isto simplifica a construo de um parser para C?
2. A gramtica para valor absoluto unrio introduziu um novo smbolo terminal como
o operador unrio. Considere a incluso do menos unrio gramtica de expresso clssica. O fato do mesmo smbolo terminal ocorrer como menos unrio ou
como menos binrio gera complicaes? Justifique sua resposta.
Encolhimento da gramtica
Em muitos casos, o construtor de compiladores pode recodificar a gramtica para
reduzir o nmero de produes que ela contm. Isto normalmente ocasiona tabelas
menores. Por exemplo, na gramtica de expresso clssica, a distino entre um
nmero e um identificador irrelevante para as produes Alvo, Expr, Termo e Fator.
A substituio das duas produes Fator num e Fator nome por uma nica
produo Fator val encolhe a gramtica em uma produo. Na tabela Action,
cada smbolo terminal tem sua prpria coluna. Transformar num e nome em um
nico smbolo, val, remove uma coluna da tabela Action. Para que isto funcione
na prtica, o scanner precisa retornar a mesma categoria sinttica, ou palavra, para
num e nome.
Argumentos semelhantes podem ser dados para a combinao dee em um nico
terminal muldiv, e para a combinao de+e - em um nico terminal addsub. Cada
uma dessas substituies remove um smbolo terminal e uma produo. Essas trs
mudanas produzem a gramtica de expresso reduzida mostrada na Figura3.33a, que
gera um CC menor, removendo linhas da tabela. Por ter menos smbolos terminais, ela
tambm tem menos colunas.
As tabelas Action e Goto resultantes aparecem na Figura3.33b. A tabela Action
contm 132 entradas, e a tabela Goto, 66, gerando um total de 198 entradas. Isto se
compara favoravelmente com as tabelas para a gramtica original, com suas 384 entradas. A mudana da gramtica produziu uma reduo de 48% no tamanho da tabela.
As tabelas ainda contm oportunidades para mais redues. Por exemplo, as linhas 0,
6 e 7 na tabela Action so idnticas, assim como as linhas 4, 11, 15 e 17. De modo
semelhante, a tabela Goto tem muitas linhas que s possuem a entrada de erro. Se
o tamanho da tabela for um problema srio, linhas e colunas podem ser combinadas
depois de encurtar a gramtica.
Outras consideraes podem limitar a capacidade do construtor de compiladores em
combinar produes. Por exemplo, o operadorpoderia ter tantos usos que tornassem
sua combinao com impraticvel. De modo semelhante, o parser poderia usar
produes distintas para permitir tratar duas construes sintaticamente semelhantes
de maneiras diferentes.
executar mais rpido do que o parser correspondente, dirigido por tabela. Com tcnicas
de layout de cdigo apropriadas, o parser resultante pode exibir localidade forte tanto
na cache de instrues como no sistema de paginao. Por exemplo, devemos colocar
todas as rotinas para a gramtica de expresso juntas em uma nica pgina, na qual
no possam entrar em conflito umas com as outras.
por tabela. {O esquema de codificao direta para LR(1) pode superar esta vantagem
de velocidade.} Em um parser de descida recursiva, o construtor de compiladores pode
contornar com mais facilidade as ambiguidades na linguagem fonte que poderiam
causar problemas para um parser LR(1) como em uma linguagem em que os nomes
de palavra-chave podem aparecer como identificadores. Um construtor de compiladores
que queira construir um parser codificado mo, por qualquer motivo, aconselhado
a usar o mtodo de descida recursiva.
Entre gramticas LR(1) e LL(1), a escolha recai nas ferramentas disponveis. Na prtica,
poucas, ou nenhuma, construes de linguagem de programao caem na lacuna entre
as gramticas LR(1) e LL(1). Assim, comear com um gerador de parser disponvel
sempre melhor do que implementar um a partir do zero.
Tambm existem algoritmos de anlise sinttica mais genricos. Na prtica, porm,
as restries impostas sobre gramticas livres de contexto pelas classes LR(1) e LL(1)
no causam problemas para a maioria das linguagens de programao.
NOTAS DO CAPTULO
Os compiladores mais antigos usavam parsers codificados mo [27,227,314]. A
riqueza sinttica do Algol 60 desafiou os primeiros construtores de compilador, que
tentaram diversos esquemas para analisar a linguagem sintaticamente. Randell e Russell oferecem uma viso geral fascinante dos mtodos utilizados em uma srie de
compiladores Algol 60 [293, Captulo1].
Irons foi um dos primeiros a separar a noo de sintaxe da traduo [202]. Lucas parece
ter introduzido a noo de anlise sinttica de descida recursiva [255]. Conway aplica
ideias semelhantes a um compilador eficiente de passo nico para COBOL [96].
As ideias por trs da anlise sinttica LL e LR apareceram na dcada de 1960.
Lewis e Stearns introduziram as gramticas LL(k) [245]; Rosenkrantz e Stearns
descreveram suas propriedades com mais profundidade [305]. Foster desenvolveu
um algoritmo para transformar uma gramtica para o formato LL(1) [151]. Wood
formalizou a noo de fatorao esquerda de uma gramtica e explorou as questes tericas envolvidas na transformao de uma gramtica para o formato LL(1)
[353,354,355].
Knuth estabeleceu a teoria por trs da anlise LR(1) [228]. DeRemer etal. desenvolveram tcnicas, os algoritmos de construo de tabela SLR e LALR, que tornaram o
uso de geradores de parser LR prtico nos computadores com memria limitada da
poca [121,122]. Waite e Goos descrevem uma tcnica para eliminar automaticamente
produes inteis durante o algoritmo de construo de tabela LR(1) [339]. Penello
sugeriu a codificao direta das tabelas para cdigo executvel [282]. Aho e Ullman [8]
so uma referncia definitiva sobre as anlises sintticas LL e LR. Bill Waite forneceu
a gramtica de exemplo no Exerccio 3.7.
Diversos algoritmos para a anlise de gramticas livres de contexto arbitrrias apareceram na dcada de 1960 e no incio dos anos 1970. Os algoritmos de Cocke e
Schwartz [91], Younger [358], Kasami [212] e Earley [135] tinham todos complexidade
computacional semelhante. O algoritmo de Earley merece uma observao especial,
devido sua semelhana com o algoritmo de construo de tabela LR(1); ele deriva o
conjunto de estados de anlise possveis em tempo de anlise, ao invs de em tempo
de execuo, onde as tcnicas LR(1) pr-calculam esses estados em um gerador de
parser. Numa viso de alto nvel, os algoritmos LR(1) parecem ser uma otimizao
natural do algoritmo de Earley.
EXERCCIOS
Seo 3.2
1. Escreva uma gramtica livre de contexto para a sintaxe de expresses regulares.
2. Escreva uma gramtica livre de contexto para a notao no formato Backus-Naur
(BNF) para gramticas livres de contexto.
3. Quando perguntados sobre a definio de uma gramtica livre de contexto no
ambgua em um exame, dois alunos deram respostas diferentes. O primeiro
a definiu como uma gramtica na qual cada sentena tem uma nica rvore
sinttica por derivao mais esquerda. O segundo, uma gramtica na qual
cada sentena tem uma nica rvore sinttica por qualquer derivao. Qual
deles est correto?
Seo 3.3
4. A gramtica a seguir no adequada para um parser preditivo top-down.
Identifique o problema e o corrija, reescrevendo a gramtica. Mostre que sua
nova gramtica satisfaz condio LL(1).
L
Ra
Q ba
|
|
aba
caba
R bc
bbc
bc
Ba
dab
Cb
cB
Ac
Esta gramtica satisfaz condio LL(1)? Justifique sua resposta. Se no, reescreva-a como uma gramtica LL(1) para a mesma linguagem.
6. As gramticas que podem ser analisadas de forma descendente (top-down), em
uma varredura linear da esquerda para a direita, com uma antecipao de k palavras so chamadas gramticas LL(k). No texto, a condio LL(1) descrita em
termos dos conjuntos FIRST. Como voc definiria os conjuntos FIRST necessrios para descrever uma condio LL(k)?
7. Suponha que um elevador seja controlado por dois comandos: para mov-lo
um andar acima e para mov-lo um andar abaixo. Suponha que o prdio tenha
um tamanho qualquer e que o elevador comece no andar .
Escreva uma gramtica LL(1) que gere sequncias de comando arbitrrias que
(1) nunca faam que o elevador desa abaixo do andar , e (2) sempre retornem
o elevador ao andarao final da sequncia. Por exemplo, e so
sequncias de comando vlidas, mas e no so. Por convenincia,
voc pode considerar uma sequncia nula como vlida. Prove que a sua gramtica LL(1).
Seo 3.4
8. Os parsers top-down e bottom-up criam rvores sintticas em diferentes ordens.
Escreva um par de programas, TopDown e BottomUp, que tomam uma rvore
sinttica como entrada e imprimem os ns em ordem de construo. TopDown
dever exibir a ordem para um parser top-down, enquanto BottomUp,
a ordem para um parser bottom-up.
Incio
S
A
B
C
S
Aa
BC
BCf
b
c
Incio
A
B
A
B
(A)
a
(B>
b
Seo 3.6
13. Escreva uma gramtica para expresses que possam incluir operadores binrios
(+ e ), menos unrio (-), autoincremento (++) e autodecremento (--) com sua
precedncia normal. Suponha que os menos unrios repetidos no sejam permitidos, mas sim os operadores de autoincremento e autodecremento repetidos.
Seo 3.7
14. Considere a tarefa de construir um parser para a linguagem de programao
Scheme. Compare o esforo exigido para um parser de descida recursiva com o
que necessrio para um parser LR(1) dirigido por tabela. {Suponha que voc
j tenha um gerador de tabela LR(1)}
15. O captulo descreve uma tcnica manual para eliminar produes inteis em
uma gramtica.
a. Voc conseguiria modificar o algoritmo de construo de tabela LR(1) de
modo que ele automaticamente elimine os encargos (overhead) de produes inteis?
b. Embora uma produo seja sintaticamente intil, ela pode servir para uma
finalidade prtica. Por exemplo, o construtor de compiladores poderia
associar uma ao dirigida pela sintaxe (ver Captulo4) com a produo
intil. Como seu algoritmo de construo de tabela modificado dever lidar
com uma ao associada a uma produo intil?
Captulo
4.1INTRODUO
A tarefa final do compilador traduzir o programa de entrada para um formato que
possa ser executado diretamente na mquina-alvo. Para esta finalidade, ele precisa de
conhecimento sobre o programa de entrada que vai bem alm da sintaxe. O compilador
precisa acumular uma grande base de conhecimento sobre a computao detalhada que
est codificada no programa de entrada; saber quais valores so representados, onde
residem e como fluem de um nome para outro; entender a estrutura da computao e
analisar como o programa interage com arquivos e dispositivos externos. Tudo isto pode
ser derivado do cdigo-fonte, usando conhecimento contextual. Assim, o compilador
precisa realizar uma anlise mais profunda, que tpica para um scanner ou um parser.
Esses tipos de anlise so, ou realizados ao longo da anlise sinttica, ou em um passo
posterior que percorre a IR produzida pelo parser. Chamamos esta anlise de anlise
sensvel ao contexto, para diferenci-la da anlise sinttica, ou de elaborao semntica, pois elabora a IR. Este captulo explora duas tcnicas para organizar este tipo de
anlise em um compilador: uma abordagem automatizada com base nas gramticas
de atributo, e uma abordagem ad hoc, que se baseia em conceitos semelhantes.
Roteiro conceitual
Para acumular o conhecimento contextual necessrio para traduo adicional, o compilador precisa desenvolver meios para visualizar o programa que no sejam pela
sintaxe. Ele usa abstraes que representam algum aspecto do cdigo, como um sistema de tipos, um mapa de armazenamento ou um grafo de fluxo de controle. Ele
precisa entender o espao de nomes do programa: os tipos de dados representados no
programa, os tipos de dados que podem ser associados a cada nome e cada expresso,
e o mapeamento desde o aparecimento de um nome no cdigo at a ocorrncia especfica desse nome; e entender o fluxo de controle, tanto dentro dos procedimentos
quanto entre os procedimentos. O compilador ter uma abstrao para cada uma dessas
categorias de conhecimento.
141
Este captulo concentra-se nos mecanismos que os compiladores utilizam para obter
conhecimento sensvel ao contexto. Apresenta, ainda, uma das abstraes que o compilador manipula durante a elaborao semntica, o sistema de tipos. (Outras sero
introduzidas em captulos posteriores.) E, em seguida, apresenta um mtodo automtico
com bons princpios para implementar essas computaes na forma de gramticas
de atributo. Depois, apresenta a tcnica mais utilizada, traduo ad hoc dirigida pela
sintaxe, e compara os pontos fortes e fracos dessas duas ferramentas. A seo de tpicos
avanados inclui breves descries de situaes que apresentam problemas mais difceis
na inferncia de tipo, junto com um exemplo final de traduo ad hoc dirigida pela sintaxe.
Viso geral
Considere um nico nome usado no programa sendo compilado; vamos cham-lo de
x. Antes que o compilador possa emitir um cdigo executvel na mquina-alvo para as
computaes envolvendo x, ele precisa ter respostas para muitas perguntas.
j
na qual os no terminais possuem os significados bvios, garante que todas as declaraes ocorrero antes de quaisquer comandos executveis. Esta restrio sinttica no
faz nada para verificar a regra mais profunda que o programa realmente declare
cada varivel antes do seu primeiro uso em um comando executvel. E tambm no
fornece um modo bvio de lidar com a regra em C++ que exige a declarao antes
do uso para algumas categorias de variveis, mas permite que o programador misture
declaraes e comandos executveis.
Impor a regra declarar antes de usar exige um nvel de conhecimento mais profundo do
que pode ser codificado na gramtica livre de contexto. Esta gramtica lida com categorias
sintticas ao invs de palavras especficas. Assim, ela pode especificar as posies em
uma expresso na qual um nome de varivel pode ocorrer. O parser pode reconhecer que
a gramtica permite que um nome de varivel ocorra, e dizer que ele ocorreu. Porm, a
gramtica no tem como corresponder uma ocorrncia de um nome de varivel com outra;
isto exigiria que a gramtica especificasse um nvel de anlise muito mais profundo
uma anlise que possa considerar o contexto e possa examinar e manipular informaes
em um nvel mais profundo do que a sintaxe livre de contexto.
Para resolver esse problema, o compilador normalmente cria uma tabela de nomes. Ele insere um nome na
declarao; e pesquisa o nome a cada referncia. Uma
falha na pesquisa indica a falta de uma declarao.
Essa soluo ad hoc exige muito do parser, mas
utiliza mecanismos que esto bem fora do escopo das
linguagens livres de contexto.
Para conseguir isto, o compilador deve, primeiro, inferir um tipo para cada expresso.
Esses tipos inferidos expem situaes em que um valor incorretamente interpretado,
como usar um nmero de ponto flutuante no lugar de um valor booleano. Segundo,
precisa verificar os tipos dos operandos de cada operador em relao s regras que
definem o que a linguagem permite. Em alguns casos, essas regras podem exigir que
o compilador converta valores de uma representao para outra. Em outros, podem
proibir tal converso e simplesmente declarar que o programa est malformado, e,
portanto, no executvel.
Em muitas linguagens, o compilador pode inferir um tipo para cada expresso. FORTRAN 77 tem um sistema de tipos particularmente simples com apenas alguns poucos
tipos. A Figura4.1 mostra todos os casos que podem surgir para o operador +. Dada
uma expresso a+b e os tipos de a e b, a tabela especifica o tipo de a+b. Para um
inteiro a e um b de preciso dupla, a+b produz um resultado de preciso dupla. Se,
ao invs disso, a fosse complexo, a+b seria ilegal. O compilador detectaria essa
Desreferenciao Operao que corresponde a obter o valor apontado por um ponteiro.
Converso implcita
Muitas linguagens especificam regras que permitem
que um operador combine valores de tipos diferentes e
exigem que o compilador insira converses conforme
a necessidade.
A melhoria da expressividade
Um sistema de tipo bem construdo permite que o projetista da linguagem especifique
o comportamento com mais preciso do que possvel com regras livres de contexto.
Esta capacidade permite que o projetista inclua recursos que seriam impossveis de
especificar em uma gramtica livre de contexto. Um exemplo excelente a sobrecarga
de operador, que d significados dependentes do contexto a um operador. Muitas
linguagens de programao usam+para indicar vrios tipos de adio. A interpretao
de+depende dos tipos de seus operandos. Em linguagens tipadas, muitos operadores
so sobrecarregados. A alternativa, em uma linguagem no tipada, fornecer operadores lexicamente diferentes para cada caso.
Por exemplo, em BCPL, o nico tipo uma clula. Uma clula pode conter qualquer
padro de bits; a interpretao desse padro de bits determinado pelo operador aplicado clula. Como as clulas so basicamente no tipadas, os operadores no podem
ser sobrecarregados. Assim, BCPL usa+para adio de inteiros e #+ para adio de
Sobrecarga de operador
Um operador que tem significados diferentes com base
nos tipos dos seus argumentos est sobrecarregado.
FIGURA 4.3 Esquema para implementar adio com verificao de tipo em tempo de execuo.
Esta verificao de tipo impe um grande overhead sobre a aritmtica simples e sobre
outras operaes que manipulam dados. A substituio de uma nica adio, ou uma
converso e uma adio, pelo aninhamento de cdigo if-then-else da Figura4.3 tem
impacto significativo sobre o desempenho. O tamanho do cdigo na Figura4.3 sugere
fortemente que operadores, como adio, sejam implementados como procedimentos, e
que cada ocorrncia de um operador seja tratada como uma chamada de procedimento.
Em uma linguagem que exija verificao de tipo em runtime, os custos desta verificao
podem facilmente superar os custos das operaes reais.
A realizao de inferncia e verificao de tipo em tempo de compilao eliminam
este tipo de overhead, e podem substituir o cdigo complexo da Figura4.3 pelo cdigo
rpido e compacto da Figura4.2. Sob o ponto de vista do desempenho, a verificao
de tipo em tempo de compilao sempre prefervel. Porm, o projeto da linguagem
determina se isto possvel ou no.
Verificao de tipo
Para evitar o overhead da verificao de tipo em tempo de execuo, o compilador
precisa analisar o programa e atribuir um tipo a cada nome e a cada expresso, e, ainda
verificar esses tipos para garantir que sejam usados em contextos nos quais sejam
vlidos. Juntas, essas atividades normalmente so chamadas de verificao de tipo.
Este no um nome apropriado, pois trata juntas as atividades distintas de inferncia
de tipo e identificao de erros relacionados ao tipo.
O programador deve entender como a verificao de tipo realizada em determinada
linguagem e compilador. Uma linguagem fortemente tipada, estaticamente verificvel, pode ser implementada com (ou sem) verificao em tempo de execuo. Uma
linguagem no tipada pode ser implementada de um modo que capture certos tipos
de erros. Tanto ML quanto Modula-3 so bons exemplos de linguagens fortemente
tipadas que podem ser estaticamente verificadas. Common Lisp tem um sistema
de tipos forte, que deve ser verificado dinamicamente. ANSI C uma linguagem
tipada, mas algumas implementaes realizam um trabalho fraco de identificao
de erros de tipo.
A teoria por trs dos sistemas de tipos abrange um corpo de conhecimento grande
e complexo. Esta seo fornece uma viso geral desses sistemas de tipos e introduz
alguns problemas simples na verificao de tipo. As sees subsequentes usam problemas simples de inferncia de tipo como exemplos de computaes sensveis ao
contexto.
Tipos bsicos
A maior parte das linguagens de programao inclui tipos bsicos para alguns dos (se
no todos) seguintes tipos de dados: nmeros, caracteres e booleanos. Estes tipos so
admitidos diretamente pela maioria dos processadores. Nmeros normalmente podem
ter vrios formatos, como inteiros e de ponto flutuante. As linguagens individuais
acrescentam outros tipos bsicos. Lisp inclui tanto um tipo nmero racional quanto
um tipo recursivo cons. Nmeros racionais so, basicamente, pares de inteiros interpretados como fraes. Um cons definido como o valor designado nil ou como
(cons first rest), onde first um objeto, rest um cons, e cons cria uma
lista a partir de seus argumentos.
As definies precisas para os tipos bsicos, e os operadores definidos para eles, variam
entre as linguagens. Algumas delas refinam esses tipos bsicos para criar mais tipos; por
exemplo, muitas distinguem entre vrios tipos de nmeros em seus sistemas de tipos.
Outras no possuem um ou mais desses tipos bsicos. Por exemplo, C no possui o tipo
string, de modo que os programadores C usam um array de caracteres em seu lugar.
Quase todas as linguagens incluem facilidades para construir tipos mais complexos a
partir de seus tipos bsicos.
Nmeros
Quase todas as linguagens de programao incluem um ou mais tipos de nmeros
como tipos bsicos. Normalmente, admitem inteiros de intervalo limitado e nmeros
reais aproximados, usualmente chamados de nmeros de ponto flutuante. Muitas
linguagens de programao expem a implementao de hardware subjacente criando
tipos distintos para diferentes implementaes de hardware. Por exemplo, C, C++ e
Java distinguem entre inteiros com e sem sinal.
FORTRAN, PL/I e C expem o tamanho dos nmeros. Tanto C quanto FORTRAN especificam o tamanho dos itens de dados em termos relativos. Por exemplo, um double
em FORTRAN tem o dobro do tamanho de um real. As duas linguagens, porm, do
ao compilador o controle sobre o tamanho da menor categoria de nmero. Ao contrrio, declaraes PL/I especificam um tamanho em bits. O compilador mapeia esse
tamanho desejado para uma das representaes do hardware. Assim, a implementao
IBM 370da PL/I mapeava tanto uma varivel fixed binary(12) quanto uma
varivel fixed binary(15) para um inteiro de 16 bits, enquanto um fixed
binary(31) tornava-se um inteiro de 32 bits.
Algumas linguagens especificam as implementaes em detalhes. Por exemplo, Java
define tipos distintos para inteiros com sinal com tamanhos de 8, 16, 32 e 64 bits,
que, respectivamente, so byte, short, int e long. De modo semelhante, o tipo
float em Java especifica um nmero de ponto flutuante IEEE de 32 bits, enquanto
seu tipo double, um nmero de ponto flutuante IEEE de 64 bits. Esta tcnica garante
um comportamento idntico em arquiteturas diferentes.
Scheme usa uma abordagem diferente. A linguagem define uma hierarquia de tipos
numricos, mas permite que o implementador selecione um subconjunto para dar
suporte. Porm, o padro faz uma distino cuidadosa entre nmeros exatos e inexatos, e
especifica um conjunto de operaes que dever retornar um nmero exato quando todos
os seus argumentos forem exatos. Isto oferece um grau de flexibilidade ao implementador, enquanto lhe permite raciocinar sobre quando e onde a aproximao pode ocorrer.
Caracteres
Muitas linguagens incluem um tipo caractere. De modo abstrato, caractere uma nica
letra. Durante anos, devido ao tamanho limitado dos alfabetos ocidentais, isto levou a
uma representao de um nico byte (8 bits) para os caracteres, normalmente mapeados
para o conjunto de caracteres ASCII. Recentemente, mais implementaes tanto
de sistema operacional quanto de linguagem de programao comearam a dar
suporte a conjuntos de caracteres maiores, expressos no formato-padro do Unicode,
que exige 16 bits. A maioria das linguagens considera que o conjunto de caracteres
ordenado, de modo que operadores de comparao-padro, como <,=e >, funcionam
intuitivamente, forando a ordenao lexicogrfica. A converso entre um caractere
e um inteiro aparece em algumas linguagens. Poucas outras operaes fazem sentido
sobre dados de caractere.
Booleanos
A maioria das linguagens de programao inclui um tipo booleano que assume dois
valores: true (verdadeiro) e false (falso). As operaes-padro fornecidas para booleanos incluem and, or, xor e not. Os valores booleanos, ou expresses com valor
booleano, normalmente so usadas para determinar o fluxo de controle. C considera
valores booleanos como uma subfaixa dos inteiros sem sinal, restrita aos valores zero
(false) e um (true).
Arrays
Arrays esto entre os objetos agregados mais utilizados. Um array agrupa mltiplos
objetos do mesmo tipo e oferece um nome distinto a cada um deles apesar de ser
Strings
Algumas linguagens de programao tratam as strings como um tipo construdo. PL/I,
por exemplo, tem strings de bits e de caracteres. Propriedades, atributos e operaes
definidas nesses dois tipos so semelhantes, e so propriedades de uma string. O
intervalo de valores permitidos em qualquer posio difere entre uma string de bits e
outra de caracteres. Assim, visualiz-los como uma string de bits e string de caracteres
apropriado. (A maioria das linguagens que admite strings limita o suporte embutido
a um nico tipo de string a de caracteres.) Outras linguagens, como C, admitem
strings de caracteres tratando-as como arrays de caracteres.
Um verdadeiro tipo string difere de um tipo array de vrias maneiras importantes.
As operaes que fazem sentido em strings, como concatenao, traduo e clculo
do tamanho, podem no ter correspondentes para arrays. Conceitualmente, a
comparao de strings deve funcionar a partir da ordem lexicogrfica, de modo que
a<boo e fee<fie. Os operadores de comparao-padro podem
ser sobrecarregados e usados na forma natural. A implementao da comparao
para um array de caracteres sugere uma comparao equivalente para um array de
nmeros ou de estruturas, em que a analogia com strings pode no ser mantida.
De modo semelhante, o tamanho real de uma string pode diferir do seu tamanho alocado, embora a maioria dos usos de um array utilize todos os elementos
alocados.
Tipos enumerados
Muitas linguagens permitem que o programador crie um tipo que contm um conjunto
especfico de valores constantes. O tipo enumerado, introduzido em Pascal, permite
que o programador use nomes autodocumentveis para pequenos conjuntos de constantes. Exemplos clssicos incluem dias da semana e meses. Em sintaxe C, estes
poderiam ser:
Estruturas e variantes
Estruturas, ou registros, agrupam vrios objetos de um tipo qualquer. Os elementos,
ou membros, da estrutura normalmente recebem nomes explcitos. Por exemplo, um
programador implementando uma rvore sinttica em C poderia precisar de ns com
um e dois filhos.
O tipo de uma estrutura o produto ordenado dos tipos dos elementos individuais
que ela contm. Assim, poderamos descrever o tipo de um Node1 como (Node1
*)unsignedint, enquanto um Node2 seria (Node2 *)(Node2
*)unsignedint. Esses novos tipos devero ter as mesmas propriedades
essenciais que um tipo bsico tem. Em C, o autoincremento de um ponteiro
para um Node1 ou a converso de um ponteiro para um Node1 * tem o efeito
desejado o comportamento semelhante ao que acontece para um tipo
bsico.
Muitas linguagens de programao permitem a criao de um tipo que a unio de
outros tipos. Por exemplo, alguma varivel x pode ter o tipo integer ou boolean
ou DiaSemana. Em Pascal, isto feito com registros variantes registro o termo
em Pascal para uma estrutura. Em C, isto feito com uma union. O tipo de uma
union uma unio dos seus tipos componentes; assim, nossa varivel x tem o tipo
integer boolean DiaSemana. Unies tambm podem incluir estruturas
de tipos distintos, mesmo quando os tipos de estruturas individuais tm tamanhos
diferentes. A linguagem precisa oferecer um mecanismo para referenciar cada campo
de forma no ambgua.
Ponteiros
Estes so endereos de memria abstratos, que permitem que o programador manipule
quaisquer estruturas de dados. Muitas linguagens incluem um tipo ponteiro. Ponteiros
permitem que um programa salve um endereo e mais tarde examine o objeto que
ele enderea. Ponteiros so criados quando os objetos so criados (new em Java ou
malloc em C). Algumas linguagens oferecem um operador que retorna o endereo
de um objeto, como o operador & em C.
Para evitar que os programadores usem um ponteiro para o tipo t a fim de referenciar
uma estrutura do tipo s, algumas linguagens restringem a atribuio de ponteiro para
tipos equivalentes. Nelas, o ponteiro no lado esquerdo de uma atribuio precisa ter
o mesmo tipo da expresso no lado direito. Um programa pode legalmente atribuir
um ponteiro para inteiro a uma varivel declarada como ponteiro para inteiro, mas
no para uma declarada como ponteiro para ponteiro para inteiro ou ponteiro para
booleano. Estas ltimas so abstraes ilegais ou exigem uma converso explcita
pelo programador.
Naturalmente, o mecanismo para criar novos objetos deve retornar um objeto do tipo
apropriado. Assim, new, de Java, cria um objeto tipado; outras linguagens usam uma rotina
polimrfica que toma o tipo de retorno como um parmetro. ANSI C trata disto de um
modo incomum: a rotina de alocao padro malloc retorna um ponteiro para void, o
que fora o programador a converter (cast) o valor retornado por cada chamada a malloc.
Polimorfismo
Uma funo que pode operar sobre argumentos
de diferentes tipos uma funo polimrfica.
A segurana de tipo com ponteiros baseia-se em uma suposio implcita de que os endereos correspondem a objetos tipados. A capacidade de construir novos ponteiros reduz
seriamente a capacidade do compilador e do seu sistema de runtime de raciocinar sobre
computaes baseadas em ponteiro e otimizar tal cdigo. (Veja, por exemplo, a Seo 8.4.1.)
Equivalncia de tipo
Um componente crtico de qualquer sistema de tipos o mecanismo que ele usa para
decidir se duas declaraes de tipos diferentes so equivalentes ou no. Considere as
duas declaraes em C mostradas ao lado. Tree e STree tm o mesmo tipo? So
equivalentes? Qualquer linguagem de programao com um sistema de tipos no
trivial precisa incluir uma regra no ambgua para responder a estas perguntas para
tipos quaisquer.
Historicamente, dois mtodos gerais tem sido testados. O primeiro, equivalncia de
nomes, afirma que dois tipos so equivalentes se e somente se ambos tiverem o mesmo nome. Filosoficamente, esta regra considera que o programador pode selecionar
qualquer nome para um tipo; se escolher nomes diferentes, a linguagem e sua implementao devem honrar este ato deliberado. Infelizmente, a dificuldade de manter
nomes consistentes aumenta com o tamanho do programa, com o nmero de autores
e com o nmero de arquivos de cdigo distintos.
O segundo mtodo, equivalncia estrutural, declara que dois tipos so equivalentes
se e somente se ambos tiverem a mesma estrutura. Filosoficamente, esta regra declara
que dois objetos so intercambiveis se consistirem no mesmo conjunto de campos,
na mesma ordem, e todos esses campos tiverem tipos equivalentes. A equivalncia
estrutural examina as propriedades essenciais que definem o tipo.
Cada poltica tem pontos fortes e fracos. A equivalncia de nomes considera que nomes
idnticos ocorrem como um ato deliberado; em um grande projeto de programao, isto
requer disciplina para evitar conflitos no intencionais. A equivalncia estrutural assume
que objetos intercambiveis podem ser usados com segurana um no lugar do outro; mas
pode criar problemas se alguns dos valores tiverem significados especiais. (Imagine
dois tipos hipotticos, estruturalmente idnticos. O primeiro contm um bloco de controle
do sistema de E/S, enquanto o segundo, contm a coleo de informaes sobre uma
imagem na tela como um mapa de bits. Trat-los como tipos distintos permitiria que o
compilador detectasse um uso indevido passando o bloco de controle de E/S para uma
rotina de atualizao de tela , mas isto no aconteceria ao trat-los como o mesmo tipo.)
REPRESENTAO DE TIPOS
Assim como a maioria dos objetos que um compilador precisa manipular, os tipos
precisam de uma representao interna. Algumas linguagens, como FORTRAN 77,
possuem um pequeno conjunto fixo de tipos. Para estas linguagens, uma pequena
tag de inteiros to eficiente quanto suficiente. Porm, muitas linguagens modernas
possuem sistemas de tipos abertos. Para estas, o construtor de compiladores precisa
projetar uma estrutura que possa representar tipos arbitrrios.
Se o sistema de tipos for baseado em equivalncia de nomes, qualquer quantidade
de representaes simples ser suficiente, desde que o compilador possa usar a
representao para rastrear at uma representao da estrutura real. Se o sistema de
tipos for baseado em equivalncia estrutural, a representao do tipo precisa codificar
sua estrutura. A maioria desses sistemas constri rvores para representar tipos;
constroem uma rvore para cada declarao de tipo e comparam estruturas de rvore
para testar a equivalncia.
Regras de inferncia
Em geral, regras de inferncia de tipo especificam, para cada operador, o mapeamento
entre os tipos de operando e o tipo do resultado. Para alguns casos, o mapeamento
simples. Uma atribuio, por exemplo, tem um operando e um resultado. O resultado,
ou lado esquerdo, precisa ter um tipo que seja compatvel com o do operando, ou lado
direito. (Em Pascal, a subfaixa 1..100 compatvel com os inteiros, pois qualquer
elemento da subfaixa pode ser atribudo com segurana a um inteiro.) Esta regra permite a atribuio de um valor inteiro a uma varivel inteira, e probe a atribuio de
uma estrutura a uma varivel inteira sem uma converso explcita que faa sentido
para a operao.
O relacionamento entre os tipos de operando e os tipos de resultado normalmente
especificado como uma funo recursiva sobre o tipo da rvore de expresso. A funo
calcula o tipo do resultado de uma operao como uma funo dos tipos de seus
operandos. As funes podem ser especificadas em forma tabular, semelhante tabela
na Figura4.1. s vezes, o relacionamento entre tipos de operando e tipos de resultado
especificado por uma regra simples. Em Java, por exemplo, a soma de dois tipos
inteiros de preciso diferente produz um resultado do tipo mais preciso (mais longo).
As regras de inferncia apontam erros de tipo. As expresses de tipo misto podem ser
ilegais. Em FORTRAN 77, um programa no pode somar um double e um complex. Em Java, um programa no pode atribuir um nmero a um caractere. Essas
combinaes devem produzir um erro de tipo em tempo de compilao, juntamente
com uma mensagem que indique como o programa est malformado.
Algumas linguagens exigem que o compilador realize converses implcitas. O compilador precisa reconhecer certas combinaes de expresses de tipos misturados e tratar
delas inserindo as converses apropriadas. Em FORTRAN, a soma de um inteiro e
de um nmero de ponto flutuante fora a converso do inteiro para a forma de ponto
flutuante antes da adio. De modo semelhante, Java exige converses implcitas para
a adio inteira de valores com preciso diferente. O compilador precisa forar o valor
de menor preciso para a forma do valor mais preciso antes da adio. Uma situao
semelhante surge em Java com a atribuio de inteiros. Se o lado direito for menos
preciso, convertido para o tipo mais preciso do lado esquerdo. Porm, se o lado esquerdo for menos preciso do que o lado direito, a atribuio produz um erro de tipo,
a menos que o programador insira uma operao de converso explcita para mudar
seu tipo e forar seu valor.
Declaraes e inferncia
Como j mencionamos, muitas linguagens de programao incluem uma regra de declarar antes de usar. Com declaraes obrigatrias, cada varivel possui um tipo bem
definido. O compilador precisa de um modo de atribuir tipos a constantes. Duas tcnicas
so comuns. Ou a forma de uma constante implica um tipo especfico por exemplo,
2 um inteiro e 2.0 um nmero de ponto flutuante ou o compilador deduz o tipo
de uma constante pelo seu uso por exemplo, sin(2) implica que 2 um nmero
de ponto flutuante, enquanto x 2, para um x, inteiro, que 2 um inteiro. Com tipos
declarados para variveis, tipos implcitos para constantes e um conjunto completo de
regras de inferncia de tipos, o compilador pode atribuir tipos a qualquer expresso sobre
variveis e constantes. As chamadas de funo complicam o quadro, conforme veremos.
Algumas linguagens livram o programador da escrita de quaisquer declaraes. Nestas,
o problema de inferncia de tipos torna-se muito mais complicado. A Seo4.5 descreve alguns dos problemas que isto cria e algumas das tcnicas que os compiladores
usam para resolv-los.
Para analisar e entender as chamadas de procedimento, o compilador precisa de uma assinatura de tipo para cada funo. Por exemplo, a funo strlen na biblioteca-padro
de C usa um operando do tipo char * e retorna um int que contm seu tamanho
em bytes, excluindo o caractere de trmino. Em C, o programador pode registrar este
fato com um prottipo de funo, que se parece com:
Assinatura de tipo
Especificao dos tipos dos parmetros formais e
valor(es) de retorno de uma funo.
Este prottipo garante que strlen utiliza um argumento do tipo char *, que ele
no modifica, conforme indicado pelo atributo const. A funo retorna um inteiro
no negativo. Escrevendo isso em uma notao mais abstrata, poderamos dizer que:
Prottipo de funo
A linguagem C inclui uma preparao que permite ao
programador declarar funes que no esto presentes,
permitindo-lhe inserir uma declarao esqueleto,
chamada prottipo de funo.
que lemos como strlen uma funo que usa uma string de caracteres de valor
constante e retorna um inteiro sem sinal. Como segundo exemplo, a funo clssica
filter da linguagem Scheme tem a seguinte assinatura:
Ou seja, filter uma funo que usa dois argumentos. O primeiro deve ser uma funo
que mapeia algum tipo a para um booleano, escrito como (a boolean), e o segundo,
uma lista cujos elementos so do mesmo tipo a. Dados argumentos desses tipos, filter
retorna uma lista cujos elementos possuem tipo a. A funo filter exige polimorfismo
paramtrico; seu tipo de resultado uma funo dos seus tipos de argumento.
Para realizar uma inferncia de tipos exata, o compilador precisa de uma assinatura de
tipo para cada funo, podendo obt-la de vrias maneiras. O compilador pode eliminar
compilao separada, exigindo que o programa inteiro seja apresentado para compilao
como uma unidade. Pode exigir que o programador fornea uma assinatura de tipo
para cada funo; isto normalmente tem a forma de prottipos de funo obrigatrios.
Pode adiar a verificao de tipo at o tempo de ligao ou tempo de execuo, quando
toda essa informao est disponvel. Finalmente, o construtor de compiladores pode
embutir o compilador em um sistema de desenvolvimento de programas que rene a
informao de requisito e a torna disponvel ao compilador por demanda. Todas essas
tcnicas tm sido usadas em sistemas reais.
REVISO DA SEO
Um sistema de tipos associa a cada valor no programa algum nome textual, um tipo,
que representa um conjunto de propriedades comuns mantidas por todos os valores
desse tipo. A definio de uma linguagem de programao especifica interaes entre
objetos do mesmo tipo, como as operaes legais sobre valores de um tipo, e entre
objetos de tipos diferentes, como as operaes aritmticas de tipo misto. Um sistema
de tipos bem projetado pode aumentar a expressividade da linguagem de programao, permitindo o uso seguro de recursos como sobrecarga. Pode expor erros sutis
em um programa muito antes que se tornem erros confusos em runtime ou respostas
erradas. E pode, ainda, permitir que o compilador evite verificaes em tempo de
execuo que desperdiam tempo e espao.
Um sistema de tipos consiste em um conjunto de tipos bsicos, regras para construir
novos tipos a partir dos existentes, um mtodo para determinar a equivalncia de dois
tipos e regras para inferir os tipos de cada expresso em um programa. As noes de
tipos bsicos, tipos construdos e equivalncia de tipo devem ser familiar a qualquer
um que tenha programado em uma linguagem de alto nvel. A inferncia de tipos
desempenha um papel crtico na implementao do compilador.
QUESTES DE REVISO
1. Para a sua linguagem de programao favorita, escreva os tipos bsicos de seu sistema de tipos. Que regras e construes a linguagem permite para construir tipos
agregados? Ela oferece um mecanismo para criar um procedimento que use um
nmero varivel de argumentos, como printf da biblioteca de E/S-padro da
linguagem C?
2. Que tipos de informao o compilador deve ter para garantir a segurana de
tipos nas chamadas de procedimento? Esboce um esquema com base no uso de
prottipos de funo. Esboce um esquema que possa verificar a validade desses
prottipos de funo.
Para tornar estas noes mais concretas, considere uma gramtica livre de contexto para
nmeros binrios com sinal. A Figura4.4 define a gramtica SBN=(T,NT,S,P), que gera
todos os nmeros binrios com sinal, como 101, +11, 01 e +11111001100,
e exclui os nmeros binrios sem sinal, como 10.
A partir de SBN, podemos construir uma gramtica de atributos que associa Number
ao valor do nmero binrio com sinal que ele representa. Para construir uma gramtica
de atributo a partir de uma gramtica livre de contexto, temos que decidir quais
atributos cada n precisa, e elaborar as produes com regras que definem valores
para esses atributos. Para nossa verso atribuda de SBN, os seguintes atributos so
necessrios:
FIGURA 4.4 Uma gramtica de atributo para nmeros binrios com sinal.
Smbolo
Atributos
Number
Sign
List
Bit
value
negative
position, value
position, value
FIGURA 4.6 rvore atribuda para o nmero binrio com sinal 101.
Circularidade
Uma gramtica de atributo circular se puder, para
algumas entradas, criar um grafo de dependncia
cclico.
4.3.2Circularidade
As gramticas de atributo circulares podem fazer surgir grafos cclicos de dependncia
de atributos. Nossos modelos para avaliao falham quando o grafo de dependncia
contm um ciclo. Uma falha deste tipo em um compilador causa srios problemas
por exemplo, o compilador pode no ser capaz de gerar cdigo para sua entrada.
O impacto catastrfico dos ciclos no grafo de dependncia sugere que esta questo
merece muita ateno.
Se um compilador usa gramticas de atributo, deve lidar com a circularidade de modo
apropriado. Duas tcnicas so possveis.
1. Evitamento. O construtor de compiladores pode restringir a gramtica de atributo
a uma classe que no far surgir grafos de dependncia circulares. Por exemplo, restringir a gramtica para usar apenas atributos sintetizados e constantes
elimina qualquer possibilidade de um grafo de dependncia circular. Existem
A segunda adia este trabalho at a gerao de cdigo, mas faz isso ao custo de
distribuir o conhecimento sobre tipos e converses entre duas partes distintas do
compilador. Qualquer uma das tcnicas funcionar; a diferena , em grande parte,
uma questo de gosto.
ciclos para o bloco, considerando um processador nico que executa uma operao
por vez. Esta gramtica, como aquela para inferir tipos de expresso, utiliza apenas
atributos sintetizados. A estimativa aparece no atributo cost do n Bloco mais alto da
rvore de derivao. A metodologia simples. Os custos so calculados de baixo para
cima; para ler o exemplo, comece com as produes para Fator e suba at aquelas para
Bloco. A funo Cost retorna a latncia de uma dada operao ILOC.
que carrega y apenas uma vez. Para aproximar melhor o comportamento do compilador, podemos modificar a gramtica de atributo para cobrar apenas um nico load
para cada varivel usada no bloco, o que requer regras de atribuio mais complexas.
Para considerar loads com mais preciso, as regras precisam rastrear referncias a cada
varivel pelo seu nome. Esses nomes so extragramaticais, pois a gramtica rastreia a
categoria sinttica nome ao invs de nomes individuais, como x, y e z. A regra para
nome deve seguir este esboo geral:
deriva nome, realiza o trabalho crtico; testa o conjunto Before para determinar se
um load necessrio ou no, e atualiza os atributos cost e After do pai de modo
correspondente.
Para completar a especificao, o construtor de compiladores deve acrescentar regras que copiem os conjuntos Before e After pela rvore de derivao. s vezes
chamadas regras de cpia, elas conectam os conjuntos Before e After dos diversos
ns Fator. Como as regras de atribuio podem referenciar somente atributos locais
definidos como os atributos do pai de um n, seus irmos e seus filhos , a gramtica
de atributo precisa copiar valores explicitamente pela rvore de derivao para garantir
que sejam locais. A Figura4.10 mostra as regras necessrias para as outras produes
da gramtica. Uma regra adicional foi acrescentada; ela inicializa o conjunto Before
da primeira instruo Assign como .
Este modelo muito mais complexo do que o modelo simples. Tem mais de trs vezes o
nmero de regras; cada regra precisa ser escrita, entendida e avaliada. Ele usa atributos
sintetizados e herdados, de modo que a estratgia simples de avaliao bottom-up
no funcionar mais. Finalmente, as regras que manipulam os conjuntos Before e
After exigem muita ateno o tipo de detalhe de baixo nvel que esperaramos
evitar usando um sistema baseado em especificaes de alto nvel.
Localizao de respostas
Um problema final com os esquemas de gramtica de atributo para anlise sensvel ao
contexto mais sutil. O resultado da avaliao de atributo uma rvore atribuda. Os
resultados da anlise so distribudos por essa rvore na forma de valores de atributo.
Para usar esses resultados em passos posteriores, o compilador precisa percorrer a
rvore a fim de localizar a informao desejada.
O compilador pode usar travessias cuidadosamente construdas para localizar um n
em particular, o que exige percorrer a rvore sinttica desde a raiz para baixo at o
local apropriado em cada acesso. Isto torna o cdigo to mais lento quanto mais
difcil de escrever, pois o compilador precisa executar cada uma dessas travessias,
e o construtor de compiladores, construir cada uma delas. A alternativa copiar
as respostas importantes em um ponto na rvore, normalmente a raiz, onde sejam
facilmente encontradas, processo este que introduz mais regras de cpia, exacerbando
este problema.
REVISO DA SEO
Gramticas de atributo fornecem uma especificao funcional que pode ser usada
para solucionar uma srie de problemas, incluindo muitos dos que surgem na
realizao da anlise sensvel ao contexto. Nesta tcnica, o construtor de compiladores
produz regras sucintas para descrever a computao; o avaliador de gramtica de
atributo, ento, fornece os mecanismos para executar a computao real. Um sistema de gramtica de atributo de alta qualidade simplifica a construo da seo de
elaborao semntica de um compilador.
A tcnica de gramtica de atributo nunca conseguiu uma popularidade generalizada
por uma srie de motivos triviais. Grandes problemas, como a dificuldade de realizar
computao no local e a necessidade de percorrer a rvore sinttica para descobrir
respostas para perguntas simples, desencorajaram a adoo dessas ideias. Pequenos
problemas, como o gerenciamento de espao para atributos de vida curta, eficincia
do avaliador e a falta de avaliadores de gramtica de atributo disponveis e de cdigo
aberto, tambm tornaram essas ferramentas e tcnicas menos atraentes.
QUESTES DE REVISO
1. A partir da gramtica da calculadora de quatro funes mostrada ao lado, construa um esquema de gramtica de atributo que atribua cada n Calc computao especificada, exibindo a resposta em cada reduo para Expr.
2. A regra definir antes de usar especifica que cada varivel usada em um procedimento deve ser declarada antes que aparea no texto. Esboce um esquema
de gramtica de atributo para verificar se um procedimento est de acordo com
esta regra. O problema mais fcil se a linguagem exigir que todas as declaraes
precedam qualquer comando executvel?
O gerador de parser pode reunir as aes dirigidas pela sintaxe, embuti-las em uma
instruo case, que seleciona o nmero da produo que est sendo reduzida, e colocar
essa instruo case antes de desempilhar o lado direito da produo.
O esquema de traduo ilustrado na Figura4.11 mais simples do que o usado para
explicar as gramticas de atributo. Naturalmente, podemos escrever uma gramtica de
atributo que aplique a mesma estratgia, que usaria apenas atributos sintetizados, e teria
menos regras de atribuio e menos atributos do que aquela ilustrada na Figura4.5.
Escolhemos o esquema de atribuio mais complexo para ilustrar o uso de atributos
sintetizados e herdados.
FIGURA 4.11Traduo ad hoc dirigida pela sintaxe para nmeros binrios com sinal.
em outros pontos na anlise sinttica. Esta seo descreve mecanismos para tratar
dessas questes em um parser bottom-up, shift-reduce. Ideias semelhantes funcionam
para parsers top-down. Adotamos uma notao introduzida no sistema Yacc, um antigo
e popular gerador de parser LALR(1) distribudo com o sistema operacional Unix. A
notao do Yacc tem sido adotada por muitos sistemas subsequentes.
esta limitao significa que valores mais complexos so passados usando ponteiros
para estruturas.
Para economizar espao de armazenamento, o parser pode omitir da pilha os
smbolos reais da gramtica. A informao necessria para a anlise sinttica
codificada no estado. Este processo encurta a pilha e agiliza a anlise, eliminando
as operaes que empilham e desempilham esses smbolos. Por outro lado, o
smbolo de gramtica pode ajudar no relato de erros e na depurao do parser.
Este dilema normalmente decidido em favor de no modificar o parser que as
ferramentas produzem essas modificaes precisam ser refeitas toda vez que o
parser gerado novamente.
Nomeao de valores
Para simplificar o uso de valores baseados em pilha, o construtor de compiladores
precisa de uma notao para nome-los. O Yacc introduziu uma notao concisa
para resolver este problema. O smbolo $$ refere-se ao local do resultado para a
produo atual. Assim, a atribuio $$=0; empilha o valor inteiro zero como
resultado correspondente reduo atual. Essa atribuio poderia implementar
a ao para a regra 6 na Figura4.11. Para o lado direito, os smbolos $1, $2, ...,
$n referem-se aos locais para o primeiro, segundo at o n-simo smbolo no lado
direito, respectivamente.
A reescrita do exemplo da Figura4.11 nesta notao produz a seguinte especificao:
Produo
1
2
3
4
5
6
7
Trecho de cdigo
+
Bit
List1 Bit
0
1
$$ $1$2
$$ 1
$$ 1
$$ $1
$$ 2$1+$2
$$ 0
$$ 1
Observe como os trechos de cdigo so compactos. Este esquema tem uma implementao eficiente; os smbolos so traduzidos diretamente em deslocamentos (offsets) a
partir do topo da pilha. A notao $1 indica um local 3| b | posies abaixo do topo
da pilha, enquanto uma referncia a $i designa o local 3(| b | i+1) posies a
partir do topo da pilha. Assim, a notao posicional permite que os trechos de ao
leiam e escrevam diretamente nos locais da pilha.
forar aes sobre shifts, o construtor de compiladores pode mov-la para o scanner
ou acrescentar uma produo para conter a ao. Por exemplo, para realizar uma
ao sempre que o parser desloca o smbolo Bit, o construtor de compiladores pode
acrescentar uma produo
e substituir cada ocorrncia de Bit por ShiftedBit. Isto acrescenta uma reduo extra
para cada smbolo terminal. Assim, o custo adicional diretamente proporcional ao
nmero de smbolos terminais no programa.
4.4.2Exemplos
Para entender como funciona a traduo ad hoc dirigida pela sintaxe, considere a
reescrita do estimador de tempo de execuo usando esta tcnica. A principal desvantagem da soluo de gramtica de atributo est na proliferao de regras para
copiar informaes pela rvore, porque cria muitas regras adicionais na especificao
e duplica os valores de atributo em muitos ns.
Para resolver esses problemas em um esquema de traduo ad hoc dirigida pela sintaxe,
o construtor de compiladores normalmente introduz um repositrio central para informaes sobre variveis, conforme j sugerimos. Isto elimina a necessidade de copiar
valores pelas rvores, e tambm simplifica o tratamento de valores herdados. Como o
parser determina a ordem de avaliao, no precisamos nos preocupar sobre quebras
de dependncias entre atributos.
A maioria dos compiladores monta e usa este repositrio, chamado tabela de smbolos,
que mapeia um nome para uma srie de anotaes como um tipo, o tamanho de sua
representao em tempo de execuo e a informao necessria para gerar um endereo
de runtime. A tabela tambm pode armazenar diversos campos dependentes de tipo,
como a assinatura de tipo de uma funo ou o nmero de dimenses e seus limites
para um array. A Seo 5.5 e o Apndice B.4 aprofundam-se mais no projeto da tabela
de smbolos.
FIGURA 4.12 Rastreando loads com a traduo ad hoc dirigida pela sintaxe.
um local para a inicializao. Uma produo inicial, como Start CostInit Block, juntamente com CostInit , faz isto. O framework pode realizar a atribuio cost 0
na reduo de para CostInit.
cria uma AST cuja raiz um n para plus com dois filhos, cada um deles um n
folha para num.
Para criar uma rvore sinttica abstrata, o esquema de traduo ad hoc dirigida pela
sintaxe segue dois princpios gerais:
1. Para um operador, cria um n com um filho para cada operando. Assim, 2+3
cria um n binrio para+com os ns para 2 e 3 como filhos.
2. Para uma produo intil, como Termo Fator, reutiliza o resultado da ao
Fator como seu prprio resultado.
FIGURA 4.14 Construindo uma rvore sinttica abstrata e inferindo tipos de expresso.
Desta maneira, este esquema evita a criao de ns de rvore que representam variveis
sintticas, como Fator, Termo e Expr. A Figura4.14 mostra um exemplo de traduo
dirigida pela sintaxe que incorpora essas ideias.
Processamento de declaraes
Naturalmente, o construtor de compiladores pode usar aes dirigidas pela sintaxe
para preencher muitas informaes que residem na tabela de smbolos. Por exemplo,
o fragmento de gramtica apresentado na Figura4.16 descreve um subconjunto
limitado da sintaxe para declarao de variveis em C. (Ele omite typedefs,
structs, unions, os qualificadores de tipo const, restrict e volatile,
bem como os detalhes da sintaxe de inicializao, assim como deixa vrios no
terminais no elaborados.) Considere as aes exigidas para criar entradas da
tabela de smbolos para cada varivel declarada. Cada Declarao comea com
um conjunto de um ou mais qualificadores que especificam o tipo e a classede
armazenamento da varivel. Esses qualificadores so seguidos por uma lista
de um ou mais nomes de varivel; cada nome de varivel pode incluir especificaes
sobre indireo (uma ou mais ocorrncias de *), dimenses de array e valores
iniciais para a varivel.
Por exemplo, a produo ClasseArmazenamento permite que o programador especifique informaes sobre o tempo de vida do valor de uma varivel; uma varivel
auto tem um tempo de vida que corresponde ao do bloco que a declara, enquanto
variveis static tm tempos de vida que duram por toda a execuo do programa.
O especificador register sugere ao compilador que o valor deve ser mantido em
Em uma reduo de auto para ClasseArmazenamento, o compilador pode verificar que o campo para a classe de armazenamento ainda no foi definido e ento
defini-lo como auto. Aes semelhantes para static, extern e register
completam o tratamento dessas propriedades de um nome.
j As produes de especificador de tipo definiro outros campos na estrutura,
e devem incluir verificaes para garantir que ocorram somente combinaes
vlidas.
j A reduo de ident para DeclaradorDireto deve disparar uma ao que cria
uma nova entrada na tabela de smbolos para o nome e copiar as configuraes
atuais da estrutura de propriedades para essa entrada.
j A reduo pela produo
Coordenando uma srie de aes entre as produes na sintaxe da declarao, o construtor de compiladores pode providenciar para que a estrutura de propriedades contenha
as configuraes apropriadas toda vez que um nome processado.
Quando o parser terminar de construir a ListaDeclarao ter criado uma entrada da
tabela de smbolos para cada varivel declarada no escopo atual. Neste ponto, pode ter
que realizar algumas tarefas de manuteno, como atribuir locais de armazenamento
a variveis declaradas, o que pode ser feito em uma ao para a produo que reduz
a ListaDeclarao. Se for necessrio, essa produo pode ser dividida para criar um
ponto conveniente para a ao.
REVISO DA SEO
A introduo dos geradores de parser criou a necessidade de um mecanismo para
unir as aes sensveis ao contexto ao comportamento em tempo de anlise do
compilador. A traduo ad hoc dirigida pela sintaxe, conforme descrita nesta seo,
evoluiu para atender a esta necessidade. Ela utiliza algumas das mesmas intuies
da tcnica de gramtica de atributo; s permite uma ordem de avaliao; e tem um
espao de nomes limitado para uso nos trechos de cdigo que formam as aes
semnticas.
Apesar dessas limitaes, o poder de permitir um cdigo qualquer nas aes semnticas, junto com o suporte para esta tcnica em geradores de parser bastante utilizados,
levou ao uso generalizado da traduo ad hoc dirigida pela sintaxe, que funciona bem
em conjunto com estruturas de dados globais, como uma tabela de smbolos, para
realizar a comunicao no local, resolvendo de modo eficiente e eficaz uma classe de
problemas que surgem na criao do front end de um compilador.
QUESTES DE REVISO
1. Considere o problema de incluir aes ad hoc a um gerador de parser LL(1). Como
voc modificaria o esqueleto de parser LL(1) para incluir aes definidas pelo
usurio para cada produo?
2. Na questo de reviso 1da Seo 4.3, voc criou um framework de gramtica de
atributo para calcular valores na gramtica da calculadora de quatro funes.
Agora, considere a implementao de um widget calculadora para a rea de trabalho do seu computador pessoal. Compare a utilidade de sua gramtica de atributo
com seu esquema de traduo ad hoc dirigida pela sintaxe para a implementao
da calculadora.
por exemplo. Seu procedimento de biblioteca map usa como argumentos uma funo
e uma lista. Ele retorna o resultado da aplicao da funo passada como argumento
a cada elemento da lista. Ou seja, se a funo do argumento leva o tipo a para b,
ento map leva uma lista de a para uma lista de b. Escreveramos sua assinatura de
tipo como
Como o tipo de retorno de map depende dos tipos de seus argumentos, uma
propriedade conhecida como polimorfismo paramtrico, as regras de inferncia
precisam incluir equaes sobre o espao de tipos. (Com tipos de retorno conhecidos e constantes, as funes retornam valores neste espao.) Com esse acrscimo, uma simples tcnica iterativa de ponto fixo para a inferncia de tipos no
suficiente.
O mtodo clssico para verificar esses sistemas mais complexos baseia-se na unificao,
embora projetos inteligente do sistema de tipos e das representaes de tipo possam
permitir o uso de tcnicas mais simples ou mais eficientes.
Produo
Aes
Produo
Aes
Aes
List elt
List
{ $$ MakeListHeader ( ) }
{ $$ AddToEnd($1, $2) }
{ $$ RemoveListHeader($1) }
est fora do escopo deste livro. Na prtica, o construtor de compiladores precisa estudar o sistema de tipos da linguagem fonte por completo e projetar a implementao
da inferncia e verificao de tipos com cuidado. As sugestes neste captulo so um
incio, mas uma implementao realista exige mais estudo.
NOTAS DO CAPTULO
Os sistemas de tipos tm sido uma parte integral das linguagens de programao
desde o compilador FORTRAN original. Embora os primeiros sistemas de tipos
refletissem os recursos da mquina subjacente, nveis de abstrao mais profundos
logo apareceram nos sistemas de tipos para linguagens como Algol 68 e Simula 67.
A teoria dos sistemas de tipos foi ativamente estudada durante dcadas, produzindo
uma srie de linguagens que incorporavam princpios importantes. Estas incluem
Russell [45] (polimorfismo paramtrico), CLU [248] (tipos de dados abstratos),
Smalltalk [162] (subtipos por herana) e ML [265] (tratamento meticuloso e completo de tipos como objetos de primeira classe). Cardelli escreveu uma excelente
viso geral dos sistemas de tipo [69]. A comunidade APL produziu uma srie de
artigos clssicos que lidavam com tcnicas para eliminar verificaes em tempo de
execuo [1,35,264,349].
Gramticas de atributo, como muitas ideias na cincia da computao, foram propostas inicialmente por Knuth [229,230]. A literatura especializada tem se concentrado
em avaliadores [203,342], teste de circularidade [342] e aplicaes de gramticas de
atributo [157,298]. Essas gramticas serviram de base para diversos sistemas bemsucedidos, incluindo o compilador Pascal da Intel para o 80286 [142,143], o Cornell
Program Synthesizer [297] e o Synthesizer Generator [198,299].
A traduo ad hoc dirigida pela sintaxe sempre foi uma parte do desenvolvimento de
parsers reais. Irons descreveu as ideias bsicas por trs da traduo dirigida pela sintaxe
para separar as aes de um parser da descrio de sua sintaxe [202]. Sem dvida, as
mesmas ideias bsicas foram usadas nos parsers de precedncia codificados mo. O
estilo da escrita de aes dirigidas pela sintaxe que descrevemos foi introduzido por
Johnson no Yacc [205]. A mesma notao foi transportada para sistemas mais recentes,
incluindo bison, do projeto Gnu.
EXERCCIOS
Seo 4.2
1. Em Scheme, o operador+ sobrecarregado. Visto que esta linguagem tipada
dinamicamente, descreva um mtodo para verificar o tipo de uma operao da
forma (+ a b), onde a e b podem ser de qualquer tipo que seja vlido para o
operador +.
2. Algumas linguagens, como APL ou PHP, no exigem declarao de varivel
nem foram a consistncia entre atribuies para a mesma varivel. (Um programa pode atribuir o inteiro 10 ae, depois, o valor de string livro ano
mesmo escopo.) Este estilo de programao s vezes chamado malabarismo
de tipos.
Suponha que voc tenha uma implementao existente de uma linguagem que
no possui declaraes, mas exige usos consistentes em relao ao tipo. Como
voc a modificaria para permitir o malabarismo de tipos?
Seo 4.3
3. Com base nas regras de avaliao a seguir, desenhe uma rvore sinttica anotada
que mostre como a rvore sinttica para a (b+c) construda.
Produo
Regras de avaliao
E0
E1+T
E0
E1T
E0
T
T
T
(E)
id
{ E0
.nptr mknode(+,
E1.nptr, T.nptr) }
.nptr mknode(-,
{ E0
E1.nptr, T.nptr) }
{ E0
.nptr T.nptr }
{ T.nptr E.nptr }
{ T.nptr mkleaf(id,id.entry) }
onde IDList deriva uma lista de nomes de varivel separados por vrgulas,
e TypeID deriva um tipo vlido em Pascal. Voc pode achar necessrio reescrever
a gramtica.
a. Escreva uma gramtica de atributo que atribua o tipo de dado correto
a cada varivel declarada.
b. Escreva um esquema de traduo ad hoc dirigida pela sintaxe que atribua
o tipo de dados correto a cada varivel declarada.
c. Algum esquema pode operar em um nico passo sobre a rvore sinttica?
8. s vezes, o construtor de compiladores pode mover uma questo pela fronteira
entre a anlise livre de contexto e a anlise sensvel ao contexto. Considere,
por exemplo, a ambiguidade clssica que surge entre a chamada de funo
e referncias de array em FORTRAN 77 (e outras linguagens). Essas construes
poderiam ser acrescentadas gramtica de expresso clssica usando
as produes:
Aqui, a nica diferena entre uma chamada de funo e uma referncia de array
est na forma como nome declarado.
Em captulos anteriores, discutimos o uso da cooperao entre o scanner e o
parser para remover a ambiguidade dessas construes. O problema pode ser
resolvido durante a anlise sensvel ao contexto? Qual soluo prefervel?
9. s vezes, uma especificao de linguagem usa mecanismos sensveis ao contexto
para verificar propriedades que podem ser testadas de uma forma livre de
contexto. Considere o fragmento de gramtica na Figura4.16. Ele permite
um nmero arbitrrio de especificadores de ClasseArmazenamento quando,
na verdade, o padro restringe uma declarao a um nico especificador
de ClasseArmazenamento.
a. Reescreva a gramtica para impor a restrio gramaticalmente.
b. De modo semelhante, a linguagem permite apenas um conjunto limitado de
combinaes de EspecificadorTipo. long permitido com int ou float;
short, apenas com int. Tanto signed quanto unsigned podem
aparecer com qualquer forma de int. signed tambm pode aparecer
em char. Essas restries podem ser escritas na gramtica?
c. Proponha uma explicao para o motivo pelo qual os autores estruturaram
a gramtica da forma como foi feito.
d. Suas revises na gramtica mudam a velocidade geral do parser? Na criao
de um parser para C, voc usaria a gramtica como a da Figura4.16, ou
preferiria sua gramtica revisada? Justifique sua resposta.
Seo 4.5
10. As linguagens orientadas a objeto permitem sobrecarga de operador e funo.
Nelas, o nome da funo nem sempre um identificador exclusivo, pois voc
pode ter vrias definies relacionadas, como em
Para fins de pesquisa, o compilador precisa construir um identificador distinto para cada funo. s vezes, essas funes sobrecarregadas tambm tero
diferentes tipos de retorno. Como voc criaria identificadores distintos para tais
funes?
11. A herana pode criar problemas para a implementao de linguagens orientadas
a objeto. Quando o tipo de objeto A um pai do tipo B, um programa pode
atribuir um ponteiro para B para um ponteiro para A, com uma sintaxe como
a b. Isto no deve causar problemas, pois tudo o que A pode fazer B tambm
pode. Porm, no se pode atribuir um ponteiro para A para um ponteiro
para B, pois a classe de objeto B pode implementar mtodos que a classe
de A no implemente.
Crie um mecanismo que possa usar a traduo ad hoc dirigida pela sintaxe
para determinar se uma atribuio de ponteiro deste tipo permitida ou no.
Captulo
Representaes intermedirias
VISO GERAL DO CAPTULO
A estrutura de dados central em um compilador a forma intermediria do programa
sendo compilado. A maioria dos passos no compilador l e manipula o formato IR do
cdigo. Assim, as decises sobre o que e como representar desempenham um papel
crucial no custo da compilao e na sua eficcia. Este captulo apresenta um estudo
das formas de IR que os compiladores usam, incluindo a IR grfica, IRs lineares e
tabelas de smbolos.
Palavras-chave: Representao intermediria, IR grfica, IR linear, Formato SSA,
Tabela de smbolos
5.1INTRODUO
Os compiladores, em geral, so organizados como uma srie de passos. medida que
o compilador deriva conhecimento sobre o cdigo que compila, precisa transmitir esta
informao de um passo para outro. E, portanto, necessita de uma representaopara
todos os fatos que deriva sobre o programa. Chamamos isto de representao intermediria, ou IR (Intermediate Representation). Um compilador pode ter uma nica
IR, ou ento uma srie de IRs que utiliza enquanto transforma o cdigo da linguagem
fonte para sua linguagem-alvo. Durante a traduo, o formato IR do programa de
entrada a forma definitiva do programa. O compilador no se refere de volta ao
texto-fonte; ao invs disso, examina o formato IR do cdigo. As propriedades da IR ou
das IRs possuem um efeito direto sobre o que o compilador pode fazer com o cdigo.
Quase toda fase do compilador manipula o programa em seu formato de IR. Assim, as
propriedades da IR, como os mecanismos de leitura e escrita de campos especficos,
para encontrar fatos especficos ou anotaes, e para navegar por um programa no
formato IR, tm um impacto direto sobre a facilidade de escrever os passos individuais
e sobre seu custo da execuo.
Roteiro conceitual
Este captulo se concentra nas questes que cercam o projeto e o uso de uma IR
na compilao. A Seo5.1.1 fornece uma viso geral taxonmica das IRs e suas
propriedades. Muitos construtores de compilador consideram rvores e grafos como
a representao natural para programas; por exemplo, rvores sintticas facilmente
capturam as derivaes criadas por um parser. A Seo5.2 descreve vrias IRs com
base em rvores e grafos. Naturalmente, a maioria dos processadores que os compiladores visam possui linguagens assembly lineares como sua linguagem nativa. Por
conseguinte, alguns compiladores utilizam IRs lineares com o raciocnio de que estas
IRs expem propriedades do cdigo da mquina-alvo que os compiladores deveriam
ver explicitamente. A Seo5.3 examina as IRs lineares.
As ltimas sees deste captulo tratam de questes que se relacionam s IRs, mas no
so, estritamente falando, questes de projeto de IR. A Seo5.4 explora as questesque
se relacionam nomeao: a escolha de nomes especficos para valoresespecficos,
193
que pode ter forte impacto sobre o tipo de cdigo gerado por um compilador. Esta discusso inclui uma viso detalhada de uma IR especfica, bastante utilizada, chamada
forma de atribuio nica esttica, ou SSA (Static Single-Assignment). A Seo5.5
fornece uma viso geral de alto nvel de como o compilador constri, usa e mantm
tabelas de smbolos. A maioria dos compiladores constri uma ou mais dessas tabelas
para manter informaes sobre nomes e valores e fornecer acesso eficiente a elas.
O Apndice B.4 oferece mais material sobre
a implementao da tabela de smbolos.
Viso geral
Para transmitir informaes entre seus passos, o compilador precisa de uma representao para todo o conhecimento que deriva sobre o programa que est sendo compilado.
Assim, quase todos os compiladores utilizam alguma forma de representao intermediria para modelar o cdigo sendo analisado, traduzido e otimizado. A maioria
dos passos no compilador consome IR o scanner uma exceo. A maioria dos
passos produz IR; os passos no gerador de cdigo podem ser excees. Muitos compiladores modernos utilizam vrias IRs durante o curso de uma nica compilao. J
em um compilador estruturado em passos, a IR serve como a representao principal
e definitiva do cdigo.
A IR precisa ser expressiva o suficiente para registrar todos os fatos teis que o
compilador pode ter que transmitir entre os passos. O cdigo-fonte insuficiente
para esta finalidade; o compilador deriva muitos fatos que no tm representao no
cdigo-fonte, como endereos de variveis e constantes ou do registrador em que
determinado parmetro passado. Para registrar todos os detalhes que o compilador
precisa codificar, a maioria dos construtores de compilador aumenta a IR com tabelas
e conjuntos que registram informaes adicionais. Consideramos essas tabelas como
parte da IR.
A seleo de uma IR apropriada para um projeto de compilador exige conhecimento da
linguagem-fonte, da mquina-alvo e das propriedades das aplicaes que o compilador
traduzir. Por exemplo, um tradutor fonte a fonte pode usar uma IR que seja muito
semelhante ao cdigo-fonte, enquanto um compilador que produz cdigo assembly
para um microcontrolador pode obter melhores resultados com uma IR tipo cdigo
assembly. De modo semelhante, um compilador para C pode precisar de anotaes
sobre valores de ponteiro que so irrelevantes em outro para Perl, e um compilador
Java mantm registros sobre a hierarquia de classes que no tem correspondente em
um compilador C.
A implementao de uma IR fora o construtor de compiladores a se concentrar nas
questes prticas. O compilador precisa de meios pouco dispendiosos para realizar
suas operaes frequentes, e de formas concisas para expressar toda a faixa de construes que podem surgir durante a compilao. O construtor de compiladores tambm
precisa de mecanismos que permitam que as pessoas examinem o programa IR fcil
e diretamente. O interesse prprio deve garantir que os construtores de compilador
prestem ateno a este ltimo ponto. Finalmente, compiladores que usam uma IR
quase sempre fazem vrios passos pela IR para um programa. A capacidade de reunir
informaes em um passo e us-las em outro melhora a qualidade do cdigo que o
compilador pode gerar.
mas rvores com outros nveis de abstrao tm sido usadas em muitos compiladores.
Por exemplo, muitos compiladores C tm usado rvores de expresso de baixo nvel.
De modo semelhante, IRs lineares podem ter construes relativamente de alto nvel,
como um operador max ou min, ou uma operao de cpia de strings.
O terceiro eixo de nossa taxonomia de IR lida com o espao de nomes usado para
representar valores no cdigo. Na traduo do cdigo-fonte para uma forma de nvel
mais baixo, o compilador precisa escolher nomes para uma srie de valores distintos.
Por exemplo, para avaliar a 2b em uma IR de baixo nvel, ele poderia gerar uma
sequncia de operaes como as mostradas ao lado. Aqui, o compilador usou quatro
nomes, de t1 a t4. Um esquema tambm vlido substituiria as ocorrncias de t2 e t4
por t1, o que reduz a quantidade de nomes pela metade.
t1 b
t2 2t1
t3 a
t4 t3 t2
A escolha de um esquema de nomeao tem forte efeito sobre como a otimizaopodemelhorar o cdigo. Se a subexpresso 2 b tem um nome exclusivo, o compilador
pode encontrar outras avaliaes de 2 b que possa substituir por uma referncia ao valor
ento produzido. Se o nome for reutilizado, o valor atual pode no estar disponvel na
avaliao subsequente, redundante. A escolha de um esquema de nomeao tambm
tem impacto sobre o tempo de compilao, pois determina os tamanhos de muitas estruturas de dados em tempo de compilao.
Por uma questo prtica, os custos de gerao e manipulao de uma IR devem interessar ao construtor de compiladores, pois afetam diretamente a velocidade de
umcompilador. Os requisitos de espao de dados de diferentes IRs variam sobre um
grande intervalo. Como o compilador normalmente toca em todo o espao que aloca,
o espao de dados normalmente tem relacionamento direto com o tempo de execuo.
Para tornar esta discusso mais concreta, considere as IRs usadas em dois sistemas de
pesquisa diferentes que criamos na Rice University.
j O
5.2IRs GRFICAS
Muitos compiladores utilizam IRs que representam o cdigo subjacente como um grafo.
Embora todas as IRs grficas consistam em ns e arestas, diferem em seu nvel de abstrao, no relacionamento entre o grafo e o cdigo subjacente e na estrutura do grafo.
rvores sintticas
Como vimos na Seo 3.2.2, rvore sinttica uma representao grfica para a
derivao, ou anlise sinttica, que corresponde ao programa de entrada. A F
igura5.1
mostra a gramtica de expresso clssica junto com uma rvore deste tipo para
a2+a2b. A rvore sinttica grande em relao ao texto fonte, pois
representa a derivao completa, com um n para cada smbolo da gramtica na
derivao. Como o compilador precisa alocar memria para cada n e cada aresta,
e necessita percorrer todos esses ns e arestas durante a compilao, vale a pena
considerar maneiras de encolher esta rvore sinttica.
FIGURA 5.1 rvore sinttica para a2+a2b usando a gramtica de expresso clssica.
AST uma representao quase de nvel fonte. Devido sua correspondncia aproximada
com uma rvore de derivao, o parser pode criar uma AST diretamente (ver Seo 4.4.2).
Elas tm sido usadas em muitos sistemas de compilador prticos. Sistemas de fonte a
fonte, incluindo editores dirigidos pela sintaxe e ferramentas automticas de paralelizao,
normalmente utilizam uma AST da qual o cdigo-fonte pode ser facilmente regenerado.
As S-expresses encontradas nas implementaes Lisp e Scheme so, basicamente, ASTs.
Mesmo quando esta rvore usada como uma representao quase de nvel fonte, as escolhas de representao afetam a usabilidade. Por exemplo, a AST no Rn Programming
Environment usava a subrvore mostrada ao lado para representar uma constante
complex em FORTRAN, escrita como (c1, c2). Esta opo funcionava bem para
o editor dirigido pela sintaxe, em que o programador era capaz de mudar c1 e c2 independentemente; o n pair correspondia aos parnteses e vrgula.
Esse formato de par, porm, provou ser problemtico para o compilador. Cada parte
do compilador que tratava com constantes precisava de um cdigo especial para as
constantes complexas. Todas as outras constantes eram representadas com um nico
n que continha um ponteiro para o texto real da constante. Usar um formato semelhante para constantes complexas teria complicado algumas operaes, como a edio
de constantes complexas e sua carga em registradores. Isto teria simplificado outras,
como a comparao de duas constantes. Tomadas pelo sistema inteiro, as simplificaes
provavelmente teriam superado as complicaes.
EFICINCIA DE ARMAZENAMENTO E REPRESENTAES GRFICAS
Muitos sistemas prticos tm usado rvores sintticas abstratas para representar
otexto-fonte sendo traduzido. Um problema comum encontrado nesses sistemas
otamanho da AST em relao ao texto de entrada. Grandes estruturas de dados
podem limitar o tamanho dos programas que as ferramentas podem tratar.
Os ns da AST no Rn Programming Environment eram grandes o suficiente para que
impusessem um problema aos sistemas de memria limitada das estaes de trabalho
da dcada de 1980. O custo de E/S de disco para as rvores reduzia a velocidade
detodas as ferramentas Rn.
Nenhum problema isolado leva a esta exploso no tamanho da AST. Rn tinha apenas
um tipo de n, de modo que a estrutura inclua todos os campos necessrios
para qualquer n. Isto simplificou a alocao, mas aumentou o tamanho do n.
(Aproximadamente metade dos ns eram folhas, que no precisam de ponteiros
parafilhos.) Em outros sistemas, os ns crescem por meio do acrscimo de inmeros
campos secundrios usados por um passo ou outro no compilador. s vezes, o tamanho
do n aumenta com o tempo, medida que novos recursos e passos so acrescentados.
A ateno cuidadosa com a forma e o contedo da AST pode encurtar seu tamanho.
No Rn, montamos programas para analisar o contedo da AST e como a AST era
usada. Combinamos alguns campos e eliminamos outros. (Em alguns casos, foi menos
dispendioso recalcular informaes do que escrev-las e l-las.) Em alguns poucos
casos, usamos hash linking para registrar fatos incomuns usando um bit no campo
que armazena o tipo de cada n para indicar a presena de informaes adicionais
armazenadas em uma tabela hash. (Este esquema reduziu o espao dedicado
aoscampos que raramente eram usados.) Para registrar a AST em disco, a convertemos
em uma representao linear com uma travessia de rvore em pr-ordem, eliminando
assim a necessidade de registrar quaisquer ponteiros internos.
No Rn, essas mudanas reduziram o tamanho das ASTs em memria em cerca
de 75%. No disco, aps os ponteiros serem removidos, os arquivos tinham cerca
dametade do tamanho de sua representao na memria. Essas mudanas permitiram
que o Rn tratasse de programas maiores e tornou as ferramentas mais responsivas.
As rvores sintticas abstratas foram muito difundidas. Muitos compiladores e interpretadores as utilizam; e o nvel de abstrao que esses sistemas precisam varia bastante.
Se o compilador gera cdigo-fonte como sua sada, a AST normalmente possui abstraes de nvel-fonte. Se o compilador gera cdigo assembly, em geral a verso final
da AST est no nvel de abstrao do conjunto de instrues da mquina ou abaixo dele.
Nvel de abstrao
Todas as nossas rvores de exemplo at aqui tm mostrado IRs de nvel quase fonte.
Os compiladores tambm usam rvores de baixo nvel. Tcnicas baseadas em rvore
para otimizao e gerao de cdigo, na verdade, podem exigir este tipo de detalhe.
Como exemplo, considere a instruo w a 2b. A AST de nvel-fonte cria uma
forma concisa, como mostra a Figura5.2a, porm, ela no possui tanto detalhe quanto
necessrio para traduzir a instruo para cdigo assembly. Uma rvore de baixo nvel,
mostrada na Figura5.2b, pode tornar este detalhe explcito. Essa rvore introduz quatro
novos tipos de n. Um n val representa um valor j em um registrador; um n num,
uma constante conhecida; um n lab, um label em nvel de assembly, em geral um
smbolo relocvel; finalmente, um operador que desreferencia um valor; ele trata o
valor como um endereo de memria e retorna o contedo da memria nesse endereo.
A rvore de baixo nvel revela os clculos de endereo para as trs variveis. w armazenado no deslocamento 4 a partir do ponteiro em rarp, que mantm o ponteiro para
a rea de dados para o procedimento atual. A desreferncia dupla de a mostra que ele
um parmetro formal de chamada por referncia, acessado por meio de um ponteiro
armazenado 16 bytes antes de r arp. Finalmente, b armazenado no deslocamento
12 aps o label @G.
O nvel de abstrao importa porque o compilador, em geral, s pode otimizar detalhes
que esto expostos na IR. As propriedades que esto implcitas na IR so difceis de
rea de dados
O compilador agrupa o espao de armazenamento
para valores que possuem o mesmo tempo de vida e
visibilidade. Chamamos esses blocos de armazenamento de reas de dados.
5.2.2Grafos
Embora as rvores forneam uma representao natural para a estrutura gramatical do
cdigo-fonte, descoberta pela anlise sinttica, sua estrutura rgida as torna menos teis
para representar outras propriedades dos programas. Para modelar esses aspectos do
comportamento do programa, os compiladores normalmente usam grafos mais gerais
como IRs. O DAG introduzido na seo anterior um exemplo de um grafo.
Um grafo de fluxo de controle (CFG Control-Flow Graph) modela o fluxo de controle entre os blocos bsicos em um programa; um grafo dirigido, G=(N, E). Cada
n n N corresponde a um bloco bsico. Cada aresta e=(ni, nj) E corresponde a
uma possvel transferncia de controle do bloco ni para o bloco nj.
Para simplificar a discusso da anlise de programa nos Captulos8 e9, consideramos
que cada CFG tem um nico n de entrada, n0, e um nico n de sada, nf. No CFG para
um procedimento, n0 corresponde ao ponto de entrada do procedimento. Se um procedimento tem vrias entradas, o compilador pode inserir um n0 exclusivo e acrescentar
arestas de n0 para cada ponto de entrada real. De modo semelhante, nj corresponde
sada do procedimento. Mltiplas sadas so mais comuns do que mltiplas entradas,
A aresta de stmt1 de volta para o cabealho do lao cria um ciclo; a AST para este
fragmento seria acclica. Para uma construo if-then-else, o CFG acclico:
Ele mostra que o controle sempre flui de stmt1 e stmt2 para stmt3. Em uma AST, esta
conexo implcita, ao invs de explcita.
Em geral, compiladores usam um CFG em conjunto com outra IR. O primeiro representa os relacionamentos entre blocos, enquanto as operaes dentro de um bloco
so representadas por outra IR, como uma AST em nvel de expresso, um DAG ou
uma das IRs lineares. A combinao resultante uma IR hbrida.
Alguns autores recomendam a criao de CFGs em que cada n represente um segmento
de cdigo mais curto do que um bloco bsico. O bloco alternativo mais comum o
bloco de nica instruo. O uso destes blocos pode simplificar algoritmos para anlise
e otimizao.
O compromisso entre um CFG criado com blocos de nica instruo e outro com
blocos bsicos gira em torno de tempo e espao. O primeiro tem mais ns e arestas do
que o segundo. A verso de nica instruo usa mais memria e leva mais tempo para
atravessar do que a verso de bloco bsico. Mais importante, medida que o compilador
inclui anotaes nos ns e arestas do CFG, o CFG de nica instruo tem muito mais
conjuntos do que o de bloco bsico. O tempo e o espao gastos na construo e no uso
dessas anotaes sem dvida ofusca o custo da construo do CFG.
Muitas partes do compilador contam com um CFG, explicita ou implicitamente. A
anlise para dar suporte otimizao geralmente comea com a anlise do fluxo de
controle e a construo do CFG (Captulo9). O escalonamento de instrues precisa
de um CFG para entender como o cdigo escalonado para blocos individuais flui junto
(Captulo12). A alocao de registradores globais conta com um CFG para entender
com que frequncia cada operao pode ser executada e onde inserir loads e stores
para valores derramados (Captulo13).
Grafo de dependncia
Compiladores tambm usam grafos para codificar o fluxo de valores a partir do ponto
onde um valor criado, uma definio, at qualquer ponto onde usado, um uso.
O grafo de dependncia de dados incorpora este relacionamento. Os ns em um grafo
de dependncia de dados representam operaes. A maioria destas operaes contm
definies e usos. Uma aresta em um grafo de dependncia de dados conecta dois ns,
um que define um valor e outro que o utiliza. Desenhamos grafos de dependncia com
arestas que vo da definio at o uso.
Para tornar isto concreto, a Figura5.3 reproduz o exemplo da Figura1.3 e mostra
seu grafo de dependncia de dados. O grafo tem um n para cada instruo no bloco.
Cada aresta mostra o fluxo de um nico valor. Por exemplo, a aresta de 3 para 7 reflete
a definio de rb na instruo 3 e seu uso subsequente na instruo 7. rarp contm
o endereo inicial da rea de dados local. Os usos de rarp referem-se sua definio
implcita no incio do procedimento; eles aparecem com linhas tracejadas.
As arestas no grafo representam restries reais sobre a sequncia de operaes um
valor no pode ser usado at que tenha sido definido. Porm, o grafo de dependncia
no captura totalmente o fluxo de controle do programa. Por exemplo, o grafo exige
que 1 e 2 venham antes de 6. Nada, porm, exige que 1 ou 2 venham antes de 3.
Muitas sequncias de execuo preservam as dependncias mostradas no cdigo,
incluindo 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 e 2, 1, 6, 3, 7, 4, 8, 5, 9, 10. A liberdade nessa
ordem parcial exatamente o que um processador desarranjado explora.
Em um nvel mais alto, considere o fragmento de cdigo mostrado na Figura5.4.
As referncias a a[i] aparecem obtendo seus valores de um n que representa as
definies anteriores de a. Isto conecta todos os usos de a por meio de um nico n.
Grafo de chamada
Para enfrentar ineficincias que surgem entre fronteiras de procedimento, alguns
compiladores realizam anlise e otimizao intraprocedimental. Para representar as
transferncias de controle em tempo de execuo entre procedimentos, os compiladores
usam um grafo de chamada, que tem um n para cada procedimento e uma aresta para
cada local distinto de chamada de procedimento. Assim, se o cdigo chama q a partir
de trs locais textualmente distintos em p; o grafo de chamada tem trs arestas (p, q),
uma para cada local de chamada.
Tanto a prtica de engenharia de software quanto os recursos da linguagem complicam
a construo de um grafo de chamada.
j
Interprocedimental
Qualquer tcnica que examina interaes entre mltiplos procedimentos chamada interprocedimental.
Intraprocedimental
Qualquer tcnica que limita sua ateno a um nico
procedimento chamada intraprocedimental.
Grafo de chamada
Grafo que representa os relacionamentos de chamada
entre os procedimentos em um programa.
O grafo de chamada tem um n para cada procedimento e uma aresta para cada local de chamada.
Prettyprinter
Programa que percorre uma rvore sinttica e escreve
seu contedo original de uma forma fcil de ser
entendido.
QUESTES DE REVISO
1. Compare a dificuldade de escrever um programa formatador (prettyprinter)
parauma rvore sinttica, uma AST e um DAG. Que informaes adicionais seriam
necessrias para reproduzir o formato do cdigo original com preciso?
2. Como o nmero de arestas em um grafo de dependncia cresce em funo
dotamanho do programa de entrada?
5.3IRs LINEARES
A alternativa a uma IR grfica a IR linear. Um programa em linguagem assembly
uma forma de cdigo linear, que consiste em uma sequncia de instrues executada
em sua ordem de aparecimento (ou em outra ordem coerente com esta). As instrues
podem conter mais de uma operao; neste caso, as operaes so executadas em
paralelo. As IRs lineares usadas nos compiladores so semelhantes ao cdigo assembly
para uma mquina abstrata.
A lgica por trs do uso do formato linear simples. O cdigo-fonte que serve como entrada para o compilador est em um formato linear, assim como o cdigo da
mquina-alvo que ele emite. Vrios compiladores mais antigos usavam IRs lineares; esta
era uma notao natural para seus autores, pois eles haviam programado anteriormente
em cdigo assembly.
IRs lineares impem uma ordenao clara e til sobre a sequncia de operaes. Por
exemplo, na Figura5.3, compare o cdigo ILOC com o grafo de dependncia de
dados. O cdigo ILOG tem uma ordem implcita; o grafo de dependncia impe uma
ordenao parcial que permite muitas ordens de execuo diferentes.
Se uma IR linear for usada como a representao definitiva em um compilador, ela
precisa incluir um mecanismo para codificar transferncias de controle entre pontos no
Desvio tomado
Na maioria das ISAs (Arquitetura de Conjunto
deInstrues), os desvios condicionais usam umrtulo.
Ocontrole flui, ou para o rtulo, chamado desvio
tomado, ou para a operao que vem aps o rtulo,
chamada desvio no tomado, ou fall-through.
Cdigos de um endereo modelam o comportamento das mquinas acumuladoras e de pilha. Esses cdigos expem o uso de nomes implcitos, de modo que
o compilador pode ajustar o cdigo a este uso. O cdigo resultante bastante
compacto.
j Cdigos de dois endereos modelam uma mquina que tem operaes destrutivas. Esses cdigos caram em desuso quando as restries de memria
se tornaram menos importantes; um cdigo de trs endereos pode modelar
operaes destrutivas explicitamente.
j Cdigos de trs endereos modelam uma mquina na qual a maioria
dasoperaes utiliza dois operandos e produzem um resultado. O surgimento
dearquiteturas RISC nas dcadas de 1980 e 1990 tornaram esses cdigos
populares, pois so semelhantes a uma mquina RISC simples.
Operao destrutiva
Operao em que um dos operandos sempre
redefinido com o resultado.
O restante desta seo descreve duas IRs lineares que continuam sendo populares: cdigo
de mquina de pilha e cdigo de trs endereos. O primeiro oferece uma representao
compacta, eficiente em termos de armazenamento. Em aplicaes nas quais o tamanho da
IR importa, como um applet Java transmitido por uma rede antes da execuo, o cdigo
de mquina de pilha faz sentido. O segundo modela o formato de instruo de uma
mquina RISC moderna; ele tem nomes distintos para dois operandos e um resultado.
Voc j est acostumado com um cdigo de trs endereos: a ILOC usada neste livro.
push 2
push b
multiply
push a
subtract
Este cdigo compacto. A pilha cria um espao de nomes implcito e elimina muitos
nomes da IR, encurtando o tamanho de um programa no formato IR. Porm, o uso da
pilha significa que todos os resultados e argumentos so transitrios, a menos que o
cdigo os mova explicitamente para a memria.
Cdigo de mquina de pilha simples de gerar e de executar. Smalltalk 80 e Java
utilizam bytecodes, uma IR semelhante em conceito ao cdigo de mquina de pilha.
Os bytecodes so executados em um interpretador ou traduzidos para o cdigo da
mquina-alvo imediatamente antes da execuo. Isto cria um sistema com uma
Bytecode
IR projetada especificamente por sua forma compacta;
normalmente, o cdigo para uma mquina de pilha abstrata.
O nome deriva do seu tamanho limitado; cdigos
deoperao so limitados a um byte ou menos.
forma compacta do programa para distribuio e um esquema razoavelmente simples para transportar a linguagem para uma nova mquina-alvo (implementando o
interpretador).
concentre nos cdigos de trs endereos, a maior parte dos pontos aplica-se igualmente
ao cdigo de mquina de pilha (ou qualquer outro formato linear).
Os cdigos de trs endereos frequentemente so implementados como um conjunto de
qudruplas, cada uma representada com quatro campos: um operador, dois operandos
(ou origens) e um destino. Para formar blocos, o compilador precisa de um mecanismo
para conectar qudruplas individuais. Os compiladores implementam qudruplas de
diversas maneiras.
A Figura5.5 mostra trs esquemas diferentes para implementar o cdigo de trs endereos para a - 2b, repetido ao lado. O esquema mais simples, na Figura5.5a,
usa um array curto para representar cada bloco bsico. Normalmente, o construtor de
compiladores coloca o array dentro de um n no CFG. (Esta pode ser a forma mais
comum de IR hbrida.) O esquema na Figura5.5b utiliza um array de ponteiros para
agrupar qudruplas em um bloco; este array pode estar contido em um n do CFG. O
esquema final, na Figura5.5c, liga as qudruplas para formar uma lista. Ele exige menos
armazenamento no n do CFG, ao custo de restringir os acessos s travessias sequenciais.
Considere os custos contrados na rearrumao do cdigo neste bloco. A primeira
operao carrega uma constante em um registrador; na maioria das mquinas, isto
traduzido diretamente para uma operao de load imediato. A segunda e quarta operaes carregam valores da memria, que na maioria das mquinas poderia incorrer em
atraso multiciclos, a menos que os valores j estejam na cache principal. Para ocultar
t1 2
t2 b
t3 t1t2
t4 a
t5 t4 t3
Salto ambguo
Desvio ou salto cujo destino no pode ser determinado
em tempo de compilao; normalmente, um salto
paraum endereo em um registrador.
REVISO DA SEO
IRs lineares representam o cdigo que est sendo compilado como uma sequncia
ordenada de operaes. Elas podem variar em seu nvel de abstrao o cdigo -fonte
para um programa em um arquivo de texto simples uma forma linear, assim
comoocdigo assembly para este mesmo programa. IRs lineares prestam-se a representaes compactas, legveis por pessoas.
Duas IRs lineares muito usadas so bytecodes, geralmente implementados
como cdigo de um endereo com nomes implcitos em muitas operaes,
ecdigo detrsendereos, em geral implementado como um conjunto de operaes binriasque possuem nomes de campo distintos para dois operandos e um
resultado.
QUESTES DE REVISO
1. Considere a expresso a2+a2b. Traduza-a para os cdigos
de mquinade pilha e de trs endereos. Compare o nmero de operaes
e o deoperandos em cada formato. Como eles se comparam com as rvores
naFigura5.1?
2. Esboce um algoritmo para construir grafos de fluxo de controle a partir da ILOC
para programas que incluem rtulos falsos e saltos ambguos.
O bloco lida com apenas quatro nomes, {a, b, c, d}, mas refere-se a mais de
quatro valores. Cada um de b, c e d possui um valor antes que a primeira instruo seja
executada. Esta primeira instruo calcula um novo valor, b+c, e a segunda, a d.
A expresso b+c na terceira instruo calcula um valor diferente do b+c anterior, a
menos que c=d inicialmente. Por fim, a ltima instruo calcula a d; seu resultado
sempre idntico quele produzido pela segunda instruo.
Os nomes do cdigo fonte dizem ao compilador pouca coisa sobre os valores que
mantm. Por exemplo, o uso de b na primeira e terceira instrues refere-se a valores
distintos (a menos que c=d). O reuso do nome b no transmite informao; na
verdade, isto poderia levar um leitor ocasional a pensar que o cdigo define a e c com
o mesmo valor.
Quando o compilador nomeia cada uma dessas expresses, pode escolher nomes
que codificam especificamente informaes teis sobre seus valores. Considere, por
exemplo, as tradues mostradas nas Figuras5.7b e5.7c. Essas duas variantes foram
geradas com diferentes disciplinas de nomeao.
O cdigo na Figura5.7b usa menos nomes do que o na 5.7c. Ele segue os nomes
do cdigo-fonte, de modo que um leitor pode facilmente relacionar o cdigo ao da
Figura5.7a. O cdigo na Figura5.7c usa mais nomes do que aquele em 5.7b. Sua disciplina de nomeao reflete os valores calculados e garante que expresses textualmente
idnticas produzam o mesmo resultado. Este esquema deixa bvio que a e c podem
receber valores diferentes, enquanto b e d devem receber o mesmo valor.
Como outro exemplo do impacto dos nomes, considere novamente a representao de
uma referncia de array, A[i,j]. A Figura5.8 mostra dois fragmentos que representam a mesma computao em nveis de abstrao muito diferentes. A IR de alto nvel,
na Figura5.8a, contm toda a informao essencial e fcil de identificar como uma
referncia de subscrito. A IR de baixo nvel, na Figura5.8b, expe muitos detalhes ao
compilador que so implcitos no fragmento AST de alto nvel; todos os detalhes na
IR de baixo nvel podem ser inferidos a partir da AST de nvel fonte.
FIGURA 5.8 Diferentes nveis de abstrao para uma referncia de subscrito de array.
Na IR de baixo nvel, cada resultado intermedirio tem seu prprio nome. O uso de nomes distintos expe esses resultados anlise e transformao. Na prtica, a maior parte
da melhoria que os compiladores conseguem na otimizao surge do aproveitamento
do contexto. Para possibilitar esta melhoria, a IR deve expor o contexto. A nomeao
pode ocultar o contexto, como quando ela reutiliza um nome para muitos valores distintos. Ela tambm pode expor o contexto, como quando cria uma correspondncia
entre nomes e valores. Esta questo no especificamente uma propriedade dos cdigos
lineares; o compilador poderia usar uma AST de nvel inferior que expusesse o clculo
completo de endereo.
Forma SSA
IR que tem um sistema de nomes baseado em valor,
criado pela renomeao e uso de pseudo-operaes,
chamadas funes .
SSA codifica o fluxo de controle e o fluxo de valor.
muito usada na otimizao (ver Seo 9.3.)
Funo
Uma funo toma vrios nomes e os junta, definindo
um novo nome.
entrou no bloco. Assim, quando o controle flui para o lao a partir do bloco acima do
lao, as funes no topo do corpo do lao copiam os valores de x0 e y0 para x1 e y1,
respectivamente. Quando o controle flui para o lao a partir do teste na parte inferior
do lao, as funes selecionam seus outros argumentos, x2 e y2.
Na entrada de um bloco bsico, todas as suas funes so executadas, simultaneamente, antes de qualquer outra instruo. Primeiro, todas lem os valores dos
argumentos apropriados, depois, todas definem seus nomes SSA de destino. A definio
de seu comportamento desta forma permite que os algoritmos que manipulam a forma
SSA ignorem a ordenao das funes no topo de um bloco uma simplificao
importante , o que pode complicar o processo de traduo da forma SSA de volta
ao cdigo executvel, conforme veremos na Seo 9.3.5.
A forma SSA teve por finalidade a otimizao do cdigo. A colocao de funes na
forma SSA codifica informaes sobre a criao de valores e seus usos. Uma propriedade de atribuio nica do espao de nomes permite que o compilador evite
muitas questes relacionadas aos tempos de vida dos valores; por exemplo, como os
nomes nunca so redefinidos ou mortos, o valor de um nome est disponvel ao longo
de qualquer caminho que prossegue a partir dessa operao. Essas duas propriedades
simplificam e melhoram muitas tcnicas de otimizao.
O exemplo expe algumas esquisitices da forma SSA que merecem explicao. Considere a funo que define x1. Seu primeiro argumento, x0, definido no bloco que
precede o lao. O segundo, x2, definido mais adiante no bloco rotulado como loop.
Assim, quando a funo executada inicialmente, um de seus argumentos indefinido. Em muitos contextos de linguagem de programao, isto causaria problemas.
Como a funo l apenas um argumento, e este corresponde aresta seguida mais
recentemente no CFG, ela nunca poder ler o valor indefinido.
Funes no esto em conformidade com o modelo de trs endereos. Uma funo
toma um nmero qualquer de operandos. Para ajustar a forma SSA a uma IR de trs
endereos, o construtor de compiladores precisa incluir um mecanismo para representar
operaes com listas de operandos mais longas. Considere o bloco ao final de uma
instruo case, como mostramos ao lado.
O IMPACTO DA NOMEAO
No final da dcada de 1980, experimentamos os esquemas de nomeao em um
compilador FORTRAN. A primeira verso gerava um novo registrador temporrio
para cada computao, incrementando um contador simples. Ele produzia grandes
espaos de nomes, por exemplo, 985 nomes para uma implementao de 210 linhas
da decomposio de valor singular (SVD Singular Value Decomposition).
O espao de nomes parecia grande para o tamanho do programa. Isto causou
problemas develocidade e espao no alocador de registradores, onde o tamanho
do espao denomes controla o tamanho de muitas estruturas de dados. (Hoje, temos
estruturas dedados melhores e mquinas mais velozes, com mais memria.)
A segunda verso usava um protocolo alocar/liberar para gerenciar os nomes.
Ofront end alocava temporrios por demanda e os liberava quando os usos
imediatosterminavam. Este esquema usava menos nomes; por exemplo, SVD usava
cerca de60 nomes. Isto agilizava a alocao, reduzindo, por exemplo, o tempo
paraencontrar variveis vivas na SVD em 60%.
Infelizmente, a associao de mltiplas expresses a um nico nome temporrio
obscureceu o fluxo de dados e degradou a qualidade da otimizao. O declnio
naqualidade do cdigo encobriu quaisquer benefcios em tempo de compilao.
Mais experimentao levou a um curto conjunto de regras que gerava uma forte
otimizao, enquanto mitigava o crescimento no espao de nomes.
1. Cada expresso textual recebeu um nome exclusivo, determinado pela entrada
dooperador e dos operandos em uma tabela hash. Assim, cada ocorrncia de uma
expresso, por exemplo, r17+r21, visava o mesmo registrador.
2. Em op ri, rj rk, k foi escolhido, de modo que i,j<k.
3. Operaes de cpia de registrador (i2i ri rj na ILOC) tinham permisso
para ter i>j somente se rj correspondesse a uma varivel escalar do programa.
Os registradores para estas variveis s eram definidos por operaes de cpia.
Asexpresses eram avaliadas em seu registrador natural e depois movidas
para o registrador para a varivel.
4. Cada operao store (store ri rj na ILOC) seguida por uma cpia de ri
para o registrador nomeado da varivel. (A regra 1 garante que os loads desse local
sempre visam o mesmo registrador. A regra 4 garante que o registrador virtual
eolocal da memria contenham o mesmo valor.)
Este esquema de espao de nomes usava cerca de 90 nomes para o SVD, masexpunha
todas as otimizaes encontradas com o primeiro esquema de espao denomes.
Ocompilador usou essas regras at que adotamos a forma SSA, com sua disciplina
para nomes.
CRIAO DA SSA
A forma de atribuio nica esttica (SSA) a nica IR que descrevemos que no tem
um algoritmo de construo bvio. A Seo 9.3 apresenta o algoritmo em detalhes.
Porm, um esboo do processo de construo esclarecer algumas das questes.
Suponha que o programa de entrada j esteja em formato ILOC. Para convert-lo
paraum formato linear equivalente SSA, o compilador primeiro deve inserir funes
e depois renomear os registradores virtuais da ILOC.
O modo mais simples de inserir funes somar um para cada registrador virtual
ILOC no incio de cada bloco bsico que tem mais de um predecessor no grafo
de fluxo de controle. Isto insere muitas funes desnecessrias; a maior parte da
complexidade no algoritmo completo visa reduzir o nmero de funes irrelevantes.
A funo para x17 precisa ter um argumento para cada caso. Uma operao tem um
argumento para cada caminho entrante de fluxo de controle; assim, no se enquadra no
esquema de nmero fixo de argumentos, como o esquema de trs endereos.
Em uma representao de array simples para o cdigo de trs endereos, o construtor de compiladores precisa usar vrios slots para cada operao ou uma estrutura de dados paralela para manter os argumentos dessas operaes. Nos outros
dois esquemas para implementao do cdigo de trs endereos apresentado na
Figura5.5, o compilador pode inserir tuplas de tamanho varivel. Por exemplo,
as tuplas para load e load imediato poderiam ter espao para apenas dois nomes,
enquanto a tupla para uma operao ser grande o suficiente para acomodar todos
os seus operandos.
Significado
Load imediato
Load no varivel
Usando esta hierarquia, o front end pode codificar o conhecimento sobre o valor
dedestino diretamente no cdigo ILOC 9X. medida que outros passos descobrem
informaes adicionais, podem reescrever operaes para mudar um valor que usam
um load de propsito geral para uma forma mais restrita. Se o compilador descobrir
que algum valor uma constante conhecida, pode substituir um load geral ou um load
escalar desse valor por um load imediato. Se uma anlise de definies e usos descobrir
que algum local no pode ser definido por qualquer operao de store executvel, os
loads desse valor podem ser reescritos para que usem um load no varivel.
As otimizaes podem aproveitar o conhecimento codificado neste padro. Por
exemplo, uma comparao entre o resultado de um load no varivel e uma constante
dever ser invariante um fato que poderia ser difcil ou impossvel de provar com
um load escalar ou um load geral.
REVISO DA SEO
Os esquemas usados para nomear valores na IR de um compilador tm efeito
diretosobre a capacidade do compilador de otimizar a IR e gerar cdigo assembly
de qualidade a partir dela. O compilador precisa gerar nomes internos para todos
osvalores, desde variveis do programa em linguagem-fonte at valores intermedirios
calculados como parte de uma expresso de endereo para uma referncia de array
com subscrito. O uso cuidadoso de nomes pode codificar e expor fatos para uso
posterior na otimizao; ao mesmo tempo, a proliferao de nomes pode atrasar
ocompilador, forando-o a usar estruturas de dados maiores.
O espao de nomes gerado na forma SSA tem obtido popularidade, pois codifica
propriedades teis; por exemplo, cada nome corresponde a uma definio exclusiva
no cdigo. Essa preciso pode auxiliar na otimizao, conforme veremos
noCaptulo8.
O espao de nomes tambm pode codificar um modelo de memria. Uma divergncia entre o modelo de memria e o conjunto de instrues da mquina-alvo
pode complicar a otimizao e a gerao de cdigo subsequente, enquanto uma
correspondncia melhor permite que o compilador se ajuste cuidadosamente
mquina-alvo.
QUESTES DE REVISO
1. Considere a funo fib apresentada. Escreva a ILOC que o front end deum compilador poderia gerar para esse cdigo sob o modelo registrador-para-registrador
e sob o modelo memria-para-memria. Qual a semelhana entre os dois? Sob
quais circunstncias cada modelo de memria poderia ser desejvel?
2. Converta o cdigo registrador-para-registrador que voc gerou na questo anterior
para a forma SSA. Existem funes cujo valor de sada nunca pode ser usado?
Em muitos locais neste texto, referimo-nos sobre a tabela de smbolos. Conforme veremos
na Seo 5.5.4, o compilador pode incluir diversas, distintas e especializadas tabelas de
smbolos. Uma implementao cuidadosa poderia usar os mesmos mtodos de acesso para
todas essas tabelas.
A implementao da tabela de smbolos exige ateno aos detalhes. Como quase todo
aspecto da traduo refere-se tabela de smbolos, a eficincia de acesso crtica.
Como o compilador no pode prever, antes da traduo, a quantidade de nomes que
encontrar, a expanso da tabela precisa ser harmoniosa e eficiente. Esta seo fornece
um tratamento de alto nvel das questes que surgem no projeto de uma tabela de
smbolos. Apresenta os aspectos especficos do compilador sobre o projeto e uso da
tabela de smbolos. Para obter detalhes mais profundos de implementao e alternativas
de projeto, consulte a Seo B.4 no Apndice B.
O compilador pode usar funes distintas para LookUp e Insert, ou elas podem
sercombinadas passando para LookUp um parmetro que especifica se o nome deve ser
inserido ou no. Isto garante, por exemplo, que um LookUp de uma varivel no declarada
falhar uma propriedade til para detectar uma violao da regra declarar antes de usar
nos esquemas de traduo dirigida pela sintaxe ou para admitir escopos lxicos aninhados.
Essa interface simples encaixa-se diretamente nos esquemas de traduo ad hoc dirigida
pela sintaxe, descritos no Captulo4. No processamento da sintaxe de declarao, o
compilador constri um conjunto de atributos para cada varivel. Quando o parser reconhece uma produo que declara alguma varivel, pode incluir o nome e os atributos
na tabela de smbolos usando Insert. Se um nome de varivel puder aparecer em
apenas uma declarao, o parser pode, primeiro, chamar LookUp para detectar um
uso repetido do nome. Quando o parser encontra um nome de varivel fora da sintaxe
de declarao, usa LookUp para obter a informao apropriada a partir da tabela de
smbolos. LookUp falha ao tentar encontrar qualquer nome no declarado. O construtor de compiladores, naturalmente, pode ter de acrescentar funes para inicializar a
tabela, armazen-la, recuper-la a partir de uma mdia externa, e para finaliz-la. Para
uma linguagem com um nico espao de nomes, esta interface suficiente.
ALTERNATIVA AO HASHING
Hashing o mtodo mais utilizado para organizar a tabela de smbolos de um
compilador. A discriminao de multiconjuntos uma alternativa interessante, que
elimina qualquer possibilidade de comportamento de pior caso. A ideia crtica por trs
desta discriminao que o ndice pode ser construdo off-line no scanner.
Para usar a discriminao de multiconjuntos, o construtor de compiladores precisa
usar uma tcnica diferente para a anlise lxica. Ao invs de processar a entrada
demodo incremental, o compilador varre o programa inteiro para encontrar o conjunto
completo de identificadores. Ao descobrir cada identificador, cria uma tupla nome,
posio, onde nome o texto do identificador e posio a sua posio ordinal na lista
de palavras classificadas, ou tokens. Ele inclui todas as tuplas em um grande conjunto.
O prximo passo ordena o conjunto lexicograficamente. Com efeito, isto cria um
conjunto de subconjuntos, um por identificador. Cada um desses subconjuntos
contm as tuplas para todas as ocorrncias de seu identificador. Como dada tupla
refere-se a um token especfico, por meio do seu valor de posio o compilador
pode usar o conjunto ordenado para modificar o fluxo de tokens. O compilador
faz uma varredura linear pelo conjunto, processando cada subconjunto; aloca
umndicedatabela de smbolos para o subconjunto inteiro, depois reescreve
ostokens para incluireste ndice. Isto aumenta os tokens de identificadores com seus
ndices na tabela de smbolos. Se o compilador precisar de uma funo de pesquisa
textual, atabela resultante est ordenada alfabeticamente para uma pesquisa binria.
O preo de usar esta tcnica um passo extra pelo fluxo de tokens, alm do custo
daclassificao lexicogrfica. As vantagens, do ponto de vista da complexidade, so:
isso evita qualquer possibilidade de comportamento de pior caso do hashing, etorna
bvio o tamanho inicial da tabela de smbolos, mesmo antes da anlise sinttica. Esta
tcnica pode ser usada para substituir uma tabela hash em quase toda aplicao em
que uma soluo off-line funcione.
Se a linguagem fonte permitir que os escopos sejam aninhados uns nos outros, ento o
front end precisa de um mecanismo para traduzir uma referncia, como x, para o escopo
e o tempo de vida apropriados. O principal mecanismo que os compiladores utilizam
para realizar esta traduo uma tabela de smbolos com escopo.
Para os propsitos desta discusso, suponha que um programa possa criar um nmero
qualquer de escopos aninhados um no outro. Adiaremos a discusso mais detalhada do
escopo lxico para a Seo 6.3.1, porm, a maioria dos programadores tem experincia
suficiente com o conceito para esta discusso. A Figura5.10 mostra um programa
em C que cria cinco escopos distintos. Vamos rotul-los com nmeros que indicam
os relacionamentos de aninhamento entre eles. O escopo de nvel 0 o mais externo,
enquanto o de nvel 3 o mais interno.
A tabela abaixo da Figura 5.10 mostra os nomes declarados em cada escopo. A declarao de b no nvel 2a oculta a do nvel 1 de qualquer cdigo dentro do bloco que
cria o nvel 2a. Dentro do nvel 2b, uma referncia a b novamente se refere ao parmetro
de nvel 1. De modo semelhante, as declaraes de a eno nvel 2b ocultam suas
declaraes mais antigas (no nvel 1 e no nvel 0, respectivamente).
Este contexto cria o ambiente de nomeao em que o comando de atribuio
e xecutado. Incluindo subscritos aos nomes para mostrar seu nvel, descobrimos que a
atribuio se refere a
Observe que a atribuio no pode usar os nomes declarados no nvel 2a porque este
bloco fechado, junto com seu escopo, antes que o nvel 2b seja aberto.
Para compilar um programa que contm escopos aninhados, o compilador precisa mapear
cada referncia de varivel sua declarao especfica. Este processo, chamado resoluo
de nome, mapeia cada referncia ao nvel lxico em que declarada. O mecanismo que os
compiladores usam para realizar essa resoluo de nomes uma tabela de smbolos com
escopo lxico. O restante desta seo descreve o projeto e a implementao de tabelas de
smbolos com escopo lxico. Os mecanismos de runtime correspondentes, que traduzem
o nvel lxico de uma referncia para um endereo, so descritos na Seo 6.4.3. As
tabelas de smbolos com escopo tambm tm aplicao direta na otimizao de cdigo.
Por exemplo, o algoritmo de numerao de valor superlocal, apresentado na Seo 8.5.1,
conta com uma tabela hash com escopo por questo de eficincia.
O conceito
Para gerenciar escopos aninhados, o parser precisa mudar, ligeiramente, sua tcnicapara o gerenciamento da tabela de smbolos. Toda vez que o parser entra em um
novo escopo lxico, pode criar uma nova tabela de smbolos para este escopo. Este
esquema cria um feixe de tabelas, interligadas em uma ordem que corresponde aos
nveis de aninhamento lxicos. Ao encontrar declaraes no escopo, ele inclui a informao na tabela atual. Insert opera sobre a tabela de smbolos atual. Ao encontrar
uma referncia de varivel, LookUp precisa verificar a tabela para o escopo atual.
Se a tabela atual no contm uma declarao para o nome, verifica a tabela para o escopo que engloba o escopo atual. Trabalhando desta maneira pelas tabelas de smbolos,
buscando nveis lxicos com numerao sucessivamente inferior, o parser, ou encontra
a declarao mais recente para o nome, ou falha no escopo mais externo, indicando
que a varivel no possui declarao visvel no escopo atual.
A Figura5.11 mostra a tabela de smbolos construda neste padro para o nosso programa de exemplo, no ponto onde o parser alcanou o comando de atribuio. Quando
o compilador chama a funo LookUp modificada para o nome b, ela ir falhar no
nvel 3, falhar no nvel 2 e encontrar o nome no nvel 1. Isto corresponde exatamente
ao nosso entendimento do programa a declarao mais recente para b como um
parmetro para example, no nvel 1. Como o primeiro bloco no nvel 2, bloco 2a, j
foi fechado, sua tabela de smbolos no est na cadeia de pesquisa. O nvel no qual o
smbolo encontrado, 1 neste caso, forma a primeira parte de um endereo para b. Se
o registro da tabela de smbolos incluir um deslocamento (offset) de armazenamento
para cada varivel, ento o par n, d especifica onde encontrar b na memria no deslocamento d a partir do incio do armazenamento para o escopo de nvel n. Chamamos
este par de coordenada esttica de b.
Coordenada esttica
Um par, <n, d>, que registra informaes
deendereo sobre alguma varivel x;
n especifica o nvel lxico onde x declarada; despecifica o deslocamento dentro da rea de dados
paraeste nvel.
Os detalhes
Para lidar com este esquema, duas chamadas adicionais so necessrias. O compilador
precisa de uma chamada que inicializa uma nova tabela de smbolos para um escopo
e outra que finaliza a tabela para um escopo.
1. InitializeScope() incrementa o nvel atual e cria uma nova tabela
desmbolos para este nvel. Vincula a nova tabela tabela do nvel que o engloba
e atualiza o ponteiro do nvel atual usado por LookUp e Insert.
2. FinalizeScope() muda o ponteiro do nvel atual de modo que aponte
paraatabela em direo ao escopo que engloba o nvel atual e depois decrementa o nvel atual. Se o compilador precisar preservar as tabelas nvel por nvel
parauso posterior, FinalizeScope pode deixar a tabela intacta na memria
ou escrev-la para uma mdia externa e reivindicar seu espao.
Para considerar o escopo lxico, o parser chama InitializeScope toda vez que
entra em um novo escopo lxico, e FinalizeScope toda vez que sai de um escopo
lxico. Este esquema produz a seguinte sequncia de chamadas para o programa da
Figura5.10:
1.
2.
3.
4.
5.
6.
7.
8.
9.
InitializeScope
Insert(w)
Insert(x)
Insert(example)
InitializeScope
Insert(a)
Insert(b)
Insert(c)
InitializeScope
10.
11.
12.
13.
14.
15.
16.
17.
18.
Insert(b)
Insert(z)
FinalizeScope
InitializeScope
Insert(a)
Insert()
InitializeScope
Insert(c)
Insert()
19.
20.
21.
22.
23.
24.
25.
26.
27.
LookUp(b)
LookUp(a)
LookUp(b)
LookUp(c)
LookUp(w)
FinalizeScope
FinalizeScope
FinalizeScope
FinalizeScope
Tabela de estrutura
As strings textuais usadas para nomear campos em uma estrutura ou registro existem em
um espao de nomes diferente das variveis e procedimentos. O nome size poderia
ocorrer em vrias estruturas diferentes em um nico programa. Em muitas linguagens
de programao, como C ou Ada, o uso de size como um campo em uma estrutura
no impede seu uso como um nome de varivel ou funo.
Para cada campo em uma estrutura, o compilador precisa registrar seu tipo, seu tamanho e seu deslocamento dentro do registro. Ele colhe esta informao das declaraes,
usando os mesmos mecanismos que utiliza para processar declaraes de varivel. E
tambm precisa determinar o tamanho geral para a estrutura, normalmente calculado
como a soma dos tamanhos de campo mais qualquer espao de overhead exigido pelo
sistema de runtime.
Existem vrias tcnicas para gerenciar o espao de nomes dos nomes de campo:
1. Tabelas separadas. O compilador pode manter uma tabela de smbolos
distintapara cada definio de registro. Esta a ideia mais limpa,conceitualmente.Se o overhead para usar tabelas mltiplas for pequeno,
como namaioria das implementaes orientadas a objeto, ento faz sentidousar uma tabela separada e associ-la entrada da tabela de smbolos
para o nome da estrutura.
2. Tabela seletora. O compilador pode manter uma tabela separada para nomes
decampo. Para evitar choques entre campos com nomes idnticos em estruturas
diferentes, ele precisa usar nomes qualificados concatenar, ou o nome daestrutura, ou algo que a mapeie exclusivamente, como o ndice da tabela desmbolos do nome da estrutura, ao nome do campo. Para esta tcnica, ocompilador
precisa, de alguma forma, ligar os campos individuais associados a cada
estrutura.
3. Tabela unificada. O compilador pode armazenar nomes de campo em sua
tabelade smbolos principal usando nomes qualificados. Isto diminui o nmero
detabelas, mas significa que a tabela de smbolos principal deve admitir todos
oscampos exigidos para variveis e funes, alm de todos os campos necessrios para cada seletor de campo em uma estrutura. Das trs opes, esta
provavelmente a menos atraente.
A tcnica de tabela separada tem a vantagem de que quaisquer questes de escopo
como reivindicar a tabela de smbolos associada a uma estrutura se encaixam
naturalmente ao framework de gerenciamento de escopo para a tabela de smbolos
principal. Quando a estrutura pode ser vista, sua tabela de smbolos interna acessvel
por meio do registro de estrutura correspondente.
Nos dois ltimos esquemas, o construtor de compiladores precisa prestar muita ateno
a questes de escopo. Por exemplo, se o escopo atual declarar uma estrutura fee e
um escopo que o englobe j tiver definido fee, ento o mecanismo de escopo precisa
mapear corretamente fee estrutura (e suas entradas de campo correspondentes). Isso
pode tambm introduzir complicaes na criao de nomes qualificados. Se o cdigo
tiver duas definies de fee, cada uma com um campo chamado size, ento fee.
size no uma chave nica para qualquer uma das entradas de campo. Este problema
pode ser resolvido associando-se um inteiro nico, gerado por um contador global, a
cada nome de estrutura.
REVISO DA SEO
Vrias tarefas dentro de um compilador exigem mapeamentos eficientes de dados
no inteiros para um conjunto compacto de inteiros. A tecnologia da tabela de smbolos oferece um modo eficiente e eficaz de implementar muitos desses mapeamentos.
Os exemplos clssicos mapeiam uma string textual, como o nome de uma varivel
ou um nome temporrio, para um inteiro. As principais consideraes que surgem
naimplementao da tabela de smbolos incluem escalabilidade, eficincia de espao
e custo de criao, insero, excluso e destruio, tanto para entradas individuais
quanto para novos escopos.
Esta seo apresentou uma tcnica simples e intuitiva para implementar uma tabela
de smbolos: feixes vinculados de tabelas hash. (A Seo B4, no Apndice B, apresenta
vrios esquemas de implementao alternativos.) Na prtica, este esquema simples
funciona bem em muitas aplicaes dentro de um compilador, variando desde
atabela de smbolos do parser at o rastreamento de informaes para numerao
devalor superlocal (ver Seo 8.5.1).
Funo memo
Funo que armazena resultados em uma tabela hash
sob uma chave construda a partir de seus argumentos
e que usa a tabela hash para evitar recalcular resultados
anteriores.
QUESTES DE REVISO
1. Usando o esquema de feixe de tabelas, qual a complexidade para inserir
umnovo nome na tabela no escopo atual? E para pesquisar um nome declarado
em um escopo qualquer? Qual , pela sua experincia, o nvel mximo de aninhamento de escopo lxico para os programas que voc escreve?
2. Quando o compilador inicializa um escopo, pode ter que fornecer um tamanho
inicial da tabela de smbolos. Como voc poderia estimar este tamanho inicial
databela de smbolos no parser? Como voc poderia estim-lo em passos
subsequentes do compilador?
NOTAS DO CAPTULO
A literatura sobre representaes intermedirias e a experincia com elas muito
esparsa. Isto de certa forma uma surpresa, em razo do grande impacto que as
decises sobre IRs tm sobre a estrutura e o comportamento de um compilador. As
formas clssicas de IR tm sido descritas em diversos livros-texto [7,33,147,171].
Formas mais recentes, como SSA [50,110,270], so descritas na literatura sobre anlise
e otimizao. Muchnick fornece um tratamento moderno do assunto e destaca o uso
de mltiplos nveis de IR em um nico compilador [270].
A ideia de usar uma funo hash para reconhecer operaes textualmente idnticas vem
desde Ershov [139]. Sua aplicao especfica em sistemas LISP parece ter surgido no
incio da dcada de 1970 [124,164]; em 1980, ela era comum o suficiente para que
McCarthy a mencione sem citao [259].
Cai e Paige introduziram a discriminao de multiconjuntos como uma alternativa
ao hashing [65]. A inteno era fornecer um mecanismo de pesquisa eficiente, com
comportamento em tempo constante garantido. Observe que as expresses regulares
livres de fechamento, descritas na Seo 2.6.3, podem ser aplicadas para se conseguir
um efeito semelhante. O trabalho sobre encolhimento do tamanho da AST de Rn foi
feito por David Schwartz e Scott Warren.
Na prtica, o projeto e a implementao de uma IR tm impacto excessivamente grande
sobre as caractersticas eventuais do compilador completo. IRs grandes e complexas
parecem modelar sistemas em sua prpria imagem. Por exemplo, as grandes ASTs
usadas nos ambientes de programao do incio da dcada de 1980, como Rn, limitaram
o tamanho dos programas que poderiam ser analisados. O formato RTL usado no GCC
tem um baixo nvel de abstrao. Em consequncia, o compilador realiza um timo
EXERCCIOS
Seo 5.2
1. Uma rvore sinttica contm muito mais informaes do que uma rvore
sinttica abstrata (AST).
a. Em que circunstncias voc poderia precisar da informao encontrada
narvore sinttica, mas no na AST?
b. Qual o relacionamento entre o tamanho do programa de entrada e sua
rvore sinttica? E sua AST?
c. Proponha um algoritmo para recuperar a rvore sinttica de um programa
apartir de sua AST.
2. Escreva um algoritmo para converter uma rvore de expresso em um DAG.
Seo 5.3.
3. Mostre como o seguinte fragmento de cdigo:
12. Voc est escrevendo um compilador para uma linguagem com escopo lxico
simples. Considere o programa de exemplo mostrado na Figura5.16.
a. Desenhe a tabela de smbolos e seu contedo na linha 11.
b. Que aes so necessrias para o gerenciamento da tabela de smbolos
quando o parser entra em um novo procedimento e quando sai de um
procedimento?
13. A tcnica de implementao mais comum para uma tabela de smbolos usa
umatabela hash, na qual as operaes de insero e excluso tm custo esperadoO(1).
a. Qual o custo de pior caso para insero e excluso em uma tabela hash?
b. Sugira um esquema de implementao alternativo que garanta a insero e
excluso O(1).
Captulo
A abstrao de procedimento
VISO GERAL DO CAPTULO
Os procedimentos desempenham um papel crtico no desenvolvimento de sistemas
de software. Eles fornecem abstraes para fluxo de controle e nomeao, e ocultao de
informaes bsica. So o bloco de construo sobre o qual os sistemas fornecem interfaces. E uma das principais formas de abstrao em linguagens tipo Algol; linguagens
orientadas a objeto contam com procedimentos para implementar seus mtodos ou
membros de cdigo.
Este captulo fornece uma viso detalhada da implementao de procedimentos e
chamadas de procedimento do ponto de vista de um construtor de compiladores, destacando as semelhanas e diferenas de implementao entre linguagens tipo Algol e
linguagens orientadas a objeto.
Palavras-chave: Chamadas de procedimento, Vinculao de parmetros, Convenes
de ligao
6.1INTRODUO
Procedimento (ou procedure) uma das principais abstraes na maioria das linguagens de programao modernas. Eles criam um ambiente de execuo controlado;
cada procedimento tem seu prprio espao de armazenamento nomeado privado.
Procedimentos ajudam a definir interfaces entre componentes do sistema; interaes
entre componentes normalmente so estruturadas por meio de chamadas de procedimento. Finalmente, procedimentos so a unidade de trabalho bsica para a maioria dos
compiladores. Um compilador tpico processa uma coleo de procedimentos e para
eles produz cdigo, que ser ligado e executado corretamente com outras colees de
procedimentos compilados.
Este ltimo recurso, normalmente chamado compilao separada, nos permite criar
grandes sistemas de software. Se o compilador precisasse do texto inteiro de um
programa para cada compilao, grandes sistemas de software seriam insustentveis.
Imagine recompilar uma aplicao de muitos milhes de linhas para cada mudana de
edio feita durante o desenvolvimento! Assim, os procedimentos desempenham um
papel to crtico no projeto e engenharia de sistemas quanto no projeto da linguagem
e implementao do compilador. Este captulo se concentra em como os compiladores
implementam a abstrao de procedimento.
Roteiro conceitual
Para traduzir um programa na linguagem-fonte para cdigo executvel, o compilador
precisa mapear todas as construes da linguagem-fonte que o programa utiliza para
operaes e estruturas de dados do processador-alvo, e, para tanto, precisa de uma
estratgia para cada uma das abstraes admitidas pela linguagem-fonte. Essas estratgias incluem tanto algoritmos quanto estruturas de dados que esto embutidas no
cdigo executvel. Esses algoritmos e estruturas de dados de runtime combinam-se
para implementar o comportamento ditado pela abstrao. Essas estratgias de runtime
233
Viso geral
O procedimento uma das abstraes centrais que esto por trs da maioria das
linguagens de programao modernas. Procedimentos criam um ambiente de execuo
controlado. Cada um tem seu prprio espao de armazenamento nomeado privado. As
instrues executadas dentro do procedimento podem acessar as variveis privadas,
ou locais, nesse espao de armazenamento privado. Um procedimento executado
quando invocado, ou chamado, por outro procedimento (ou pelo sistema operacional).
O procedimento chamado pode retornar um valor ao seu chamador, caso em que o
procedimento considerado uma funo. Esta interface entre procedimentos permite
que os programadores desenvolvam e testem partes de um programa isoladamente; a
separao entre procedimentos oferece algum isolamento contra problemas em outros
procedimentos.
Procedimentos desempenham um papel importante no modo como os programadores
desenvolvem software e como os compiladores traduzem programas. Trs abstraes
crticas que os procedimentos oferecem permitem a construo de programas no
triviais.
Conveno de ligao
Acordo entre o compilador e o sistema operacional
que define as aes tomadas para chamar
um procedimento ou uma funo.
Parmetro formal
Nome declarado como parmetro de algum
procedimento p um parmetro formal de p.
Parmetro real
Valor ou varivel passada como parmetro
em uma chamada um parmetro real da chamada.
imediatamente aps sua invocao. Se o procedimento chamado invocar outros procedimentos, eles retornam o controle da mesma maneira. A Figura6.1a mostra um programa em Pascal com vrios procedimentos aninhados, enquanto as Figuras6.1b e6.1c
exibem o grafo de chamadas do programa e seu histrico de execuo, respectivamente.
Ativao
Uma chamada a um procedimento o ativa; denominamos uma ocorrncia de sua execuo como ativao.
O grafo de chamadas mostra o conjunto de chamadas em potencial entre os procedimentos. A execuo de Main pode resultar em duas chamadas a Fee: uma a partir de
Foe e outra de Fum. O histrico de execuo mostra que as duas chamadas ocorrem
em tempo de execuo. Cada uma delas cria uma ocorrncia distinta, ou ativao, de
Fee. Quando Fum chamado, a primeira ocorrncia de Fee no est mais ativa. Ela
foi criada pela chamada de Foe (evento 3 no histrico de execuo) e destruda depois
que retornou o controle para Foe (evento 4). Quando o controle retorna para Fee, pela
chamada em Fum (evento 6), ele cria uma nova ativao de Fee. O retorno de Fee
para Fum destri essa ativao.
Quando o programa executa a atribuio x:=1 na primeira chamada de Fee, os
procedimentos ativos so Fee, Foe, Fie e Main. Todos eles se encontram em um
caminho do grafo de chamadas de Main para Fee. De modo semelhante, quando
executa a segunda chamada de Fee, os procedimentos ativos (Fee, Fum, Foe, Fie
eMain) se encontram em um caminho de Main at Fee. O mecanismo de chamada e
retorno do Pascal garante que, em qualquer ponto durante a execuo, as ativaes de
procedimento correspondem a algum caminho a partir da raiz no grafo de chamadas.
Quando o compilador gera cdigo para chamadas e retornos, esse cdigo deve preservar
informaes suficientes para que chamadas e retornos operem corretamente. Assim,
quando Foe chama Fum, o cdigo deve registrar o endereo de Foe para o qual Fum
dever retornar o controle. Fum pode divergir, ou no retornar, devido a um erro de
execuo, um lao infinito ou uma chamada a outro procedimento que no retorne.
Ainda assim, o mecanismo de chamada deve preservar informaes suficientes para
permitir que a execuo seja retomada em Foe se Fum retornar.
Divergir
Diz-se que uma computao que no termina
normalmente diverge.
O comportamento de chamada e retorno das ALLs pode ser modelado com uma pilha.
Quando Fie chama Foe, coloca o endereo de retorno de Fie na pilha. Quando
Foe retorna, retira esse endereo da pilha e salta para ele. Se todos os procedimentos
utilizarem a mesma pilha, a remoo de um endereo de retorno expe o prximo.
Endereo de retorno
Quando p chama q, o endereo em p onde a execuo
deve continuar aps seu retorno chamado endereo
de retorno.
Fecho (Closure)
Um procedimento e o contexto de execuo que define
suas variveis livres.
REVISO DA SEO
Em linguagens como Algol, os procedimentos so invocados com uma chamada e
terminam em um retorno, a menos que o procedimento divirja. Para traduzir chamadas e retornos, o compilador precisa fazer com que o cdigo registre, em cada chamada, o endereo de retorno apropriado e use, em cada retorno, o endereo de retorno
que corresponde chamada correta. O uso de uma pilha para manter corretamente
os endereos de retorno modela o comportamento ltimo a entrar, primeiro a sair
dos endereos de retorno.
Uma estrutura de dados-chave usada para analisar os relacionamentos chamador-chamado o grafo de chamadas, que representa o conjunto de chamadas entre
procedimentos, com uma aresta de Foe para Fum para cada local de chamada em
Foe que invoca Fum. Assim, ele captura o relacionamento esttico entre chamadores
e chamados, definido pelo cdigo-fonte, mas no o relacionamento dinmico, ou de
execuo, entre os procedimentos; por exemplo, ele no pode dizer quantas vezes o
programa recursivo de fatorial da Figura6.2 chama a si mesmo.
QUESTES DE REVISO
1. Muitas linguagens de programao incluem uma transferncia de controle direta,
normalmente chamada goto. Compare e contraste uma chamada de procedimento e um goto.
2. Considere o programa de fatorial mostrado na Figura6.2. Escreva o histrico de
execuo de uma chamada para (fact 5). Faa explicitamente as correspondncias entre chamadas e retornos. Mostre o valor de k e do valor de retorno.
nos escopos que o englobam, com efeito, tornando-os inacessveis dentro do escopo. Assim, as regras de escopo do ao programador controle sobre o acesso
informao.
Escopo lxico
Escopos que se aninham na ordem em que so
encontrados no programa normalmente so chamados
escopos lxicos.
No escopo lxico, um nome refere-se definio que
est lexicamente mais prxima de seu uso ou seja, a
definio no escopo mais prximo que o englobe.
Coordenada esttica
Para um nome x declarado no escopo s, sua
coordenada esttica um par n, d, onde n o nvel
de aninhamento lxico de s, e d, o deslocamento onde
x est armazenado na rea de dados do escopo.
A segunda parte da traduo de nomes ocorre durante a gerao de cdigo. O compilador precisa usar a coordenada esttica para localizar o valor em tempo de execuo.
Dada uma coordenada n, d, o gerador de cdigo precisa emitir cdigo que traduza n
para o endereo de runtime da rea de dados apropriada. Depois, pode usar o deslocamento d para calcular o endereo para a varivel correspondente a n, d. A Seo6.4.3
descreve duas maneiras diferentes de realizar esta tarefa.
ESCOPO DINMICO
A alternativa ao escopo lxico o escopo dinmico. A distino entre estes escopos
s importa quando um procedimento se refere a uma varivel que declarada fora do
prprio escopo do procedimento, normalmente chamada varivel livre.
Com o escopo lxico, a regra simples e consistente: uma varivel livre est vinculada
declarao para seu nome que estiver lexicamente mais prxima ao uso. Se o
compilador comear no escopo contendo o uso e verificar os sucessivos escopos que
o englobam, a varivel ligada primeira declarao que encontrar. A declarao
sempre vem de um escopo que engloba a referncia.
Com o escopo dinmico, a regra igualmente simples: uma varivel livre est ligada
quela com esse nome que foi criada mais recentemente em tempo de execuo.
Assim, quando a execuo encontra uma varivel livre, vincula esta varivel livre
ocorrncia mais recente desse nome. As primeiras implementaes criavam uma pilha
de nomes em tempo de execuo, na qual cada um era empilhado medida que sua
declarao era encontrada. Para vincular uma varivel livre, o cdigo em execuo
pesquisava a pilha de nomes do topo para baixo, at que uma varivel com o nome
correto fosse encontrada. Implementaes posteriores so mais eficientes.
Embora muitos dos primeiros sistemas LISP usassem escopo dinmico, o
lxico tornou-se a escolha dominante. O escopo dinmico fcil de ser
implementadoemum interpretador, e um pouco mais difcil de ser implementado
com eficincia em um compilador, porque pode criar defeitos (bugs) que so difceis
de detectar e de entender. O escopo dinmico ainda aparece em algumas linguagens;
por exemplo, Common List ainda permite ao programa especificar escopo dinmico.
A compilao separada torna difcil para os compiladores FORTRAN detectar diferentes declaraes para
um bloco common em arquivos distintos. Assim,
o compilador precisa traduzir referncias de bloco
common em pares bloco, deslocamento para
produzir comportamento correto.
por um pr-processador de macros ou para criar uma varivel local cujo escopo
o corpo de um lao.
Nome esttico
Varivel declarada como static retm seu valor entre
as chamadas de seu procedimento de definio.
As variveis que no so estticas so chamadas
automticas.
C introduz outro escopo: o escopo em nvel de arquivo, que inclui nomes declarados,
como static, que no esto delimitados em um procedimento. Assim, procedimentos e funes static esto no escopo em nvel de arquivo, assim como quaisquer variveis static declaradas em nvel mais externo no arquivo. Sem o atributo
static, esses nomes seriam variveis globais. Os nomes no escopo em nvel de
arquivo so visveis a qualquer procedimento no arquivo, mas no so visveis fora do
arquivo. Tanto variveis quanto procedimentos podem ser declarados como estticos.
j
j
j
j
j
j
Armazenamento local
O AR para uma chamada de procedimento q mantm os dados locais e a informao de
estado para esta chamada. Cada chamada distinta para q gera um AR distinto. Todos os
dados no AR so acessados por meio do ARP. Como os procedimentos normalmente
acessam seu AR com frequncia, a maioria dos compiladores dedica um registrador de
hardware para manter o ARP do procedimento atual. Em ILOC, referimo-nos a esse
registrador dedicado como rarp.
O ARP sempre aponta para um local designado no AR. A parte central do AR tem um
layout esttico; todos os campos possuem tamanhos fixos conhecidos. Isto garante
que o cdigo compilado pode acessar esses itens em deslocamentos fixos a partir do
Inicializao de variveis
Se a linguagem-fonte permitir que o programa especifique um valor inicial para uma
varivel, o compilador precisa fazer com que esta inicializao ocorra. Se a varivel for
alocada estaticamente ou seja, se ela tiver um tempo de vida que seja independente
de qualquer procedimento e o valor inicial for conhecido em tempo de compilao,
os dados podem ser inseridos diretamente nos locais apropriados pelo carregador
(loader). (Variveis estticas normalmente so armazenadas fora de todos os ARs.
Uma ocorrncia dessa varivel fornece a semntica necessria um nico valor
preservado por todas as chamadas. Usar uma rea de dados esttica separada uma
por procedimento ou uma para o programa inteiro permite que o compilador use os
recursos de inicializao normalmente encontrados nos loaders.)
As variveis locais, por outro lado, precisam ser inicializadas em tempo de execuo.
Como um procedimento pode ser chamado vrias vezes, a nica maneira vivel de
definir valores iniciais gerar instrues que armazenam os valores necessrios nos
locais apropriados. Com efeito, essas inicializaes so atribuies executadas antes
da primeira instruo do procedimento, toda vez que este for chamado.
Conforme veremos, nem todas as OOLs podem ser compiladas, pelo menos no no
sentido tradicional de uma traduo que finaliza todos os detalhes sobre o programa
executvel. Caractersticas de algumas OOLs criam espaos de nomes que no podem
ser entendidos antes do tempo de execuo. As implementaes dessas linguagens
contam com mecanismos de runtime que passam da interpretao compilao em
runtime (os chamados compiladores just-in-time, ou JITs). Como os interpretadores e
JITs utilizam muitas das mesmas estruturas que um compilador, descrevemos o problema conforme poderia ser implementado em um compilador tradicional.
Do ponto de vista do compilador, as OOLs reorganizam o espao de nomes do programa. A maioria das OOLs retm as convenes de escopo lxico orientadas a
Visibilidade
Quando um mtodo executado, pode referenciar nomes definidos em vrias hierarquias
de escopo. O mtodo um procedimento, com seu prprio espao de nomesdefinido
pelo conjunto de escopos lxicos em que declarado, e pode acessar nomes nesses
escopos usando as convenes familiares definidas para ALLs. Quando chamado em
relao a algum receptor, o mtodo pode acessar os prprios membros desse objeto. O
mtodo definido na classe do receptor. Pode acessar os membros dessa classe e, por
herana, de suas superclasses. Finalmente, o programa cria algum espao de nomes
global e executado nele. O mtodo em execuo pode acessar quaisquer nomes que
estejam contidos nesse espao de nomes global.
Superclasse direta
Se a classe a estende b, ento b a superclasse
direta de a. Se b tem uma superclasse g, ento g
, por transitividade, uma superclasse de a, mas no
superclasse direta de a.
Para dar suporte ao ambiente de nomeao mais complexo de uma OOL, o construtor
de compiladores usa as mesmas ferramentas bsicas usadas com ALL: um conjunto
interligado de tabelas de smbolos (ver Seo 5.5.3). Em uma OOL, o compilador simplesmente tem mais tabelas do que em uma ALL, e precisa us-las de um modo que
reflita o ambiente de nomeao. Ele pode interligar as tabelas na ordem apropriada,
ou ento manter os trs tipos de tabelas separados e pesquis-los na ordem apropriada.
A principal complicao que surge com algumas OOLs deriva no da presena de uma
hierarquia de classes, mas de quando essa hierarquia definida. Se a OOL exigir que
as definies de classe estejam presentes em tempo de compilao e que no mudem
aps o tempo de compilao, ento a resoluo de nomes dentro dos mtodos pode ser
realizada em tempo de compilao. Dizemos que essa linguagem tem uma estrutura de
classes fechada. Por outro lado, se a linguagem permitir que o programa em execuo
mude sua estrutura de classes, seja importando classes, como em Java, ou editando-as,
como em Smalltalk, ento a linguagem tem uma estrutura de classes aberta.
TRADUO DE JAVA
A linguagem de programao Java foi projetada para ser portvel, segura e ter
uma representao compacta para transmisso por redes. Esses objetivos de projeto
levaram diretamente a um esquema de traduo em dois estgios, que seguido
em quase todas as implementaes Java.
O cdigo primeiro compilado, no sentido tradicional, da fonte Java para uma IR,
chamada bytecode Java, que compacto e forma o conjunto de instrues para
a Mquina Virtual Java (JVM - Java Virtual Machine). A JVM tem sido implementada com
um interpretador que pode ser compilado em quase toda plataforma-alvo, provendo
portabilidade. Como o cdigo Java executado dentro da JVM, esta pode controlar
interaes entre o cdigo Java e o sistema, limitando a capacidade de um programa
Java em obter acesso ilcito aos recursos do sistema um forte recurso de segurana.
Este projeto implica um esquema de traduo especfico. O cdigo Java primeiro
compilado para bytecode Java, que ento interpretado pela JVM. Como
a interpretao acrescenta overhead em tempo de execuo, muitas implementaes
JVM incluem um compilador just-in-time que traduz sequncias de bytecode bastante
utilizadas em cdigo nativo para o hardware subjacente. Como resultado, a traduo
Java uma combinao de compilao e interpretao.
C++ tem uma estrutura de classes fechada. Quaisquer funes, exceto as funes virtuais, podem ser
resolvidas em tempo de compilao. Funes virtuais
exigem resoluo em tempo de execuo.
Chamada de mtodo
Como o compilador gera cdigo para invocar um mtodo como draw? Mtodos sempre
so chamados em relao a um objeto, digamos RightCorner, como receptor. Para
que a chamada seja vlida, RightCorner precisa ser visvel no ponto da chamada,
de modo que o compilador possa descobrir como encontrar RightCorner com uma
pesquisa em tabela de smbolos. O compilador, primeiro, examina na hierarquia lxica
do mtodo, depois na hierarquia de classes e, finalmente, no escopo global. Essa pesquisa fornece informaes suficientes para permitir que o compilador emita cdigo
para obter um ponteiro para o OR de RightCorner.
Uma vez que o compilador tenha emitido cdigo para obter o ponteiro do OR, localiza o
ponteiro do vetor de mtodos no deslocamento 4 do ponteiro do OR. E usa o deslocamento de
draw, que 0 em relao ao ponteiro do vetor de mtodos, para obter um ponteiro para a implementao desejada de draw. Utiliza esse ponteiro de cdigo em uma chamada de procedimento padro com um detalhe: passa o ponteiro do OR de RightCorner como primeiro
parmetro implcito para draw. Como localizou draw a partir do OR de RightCorner,
que contm um ponteiro para o vetor class methods de ColorPoint, a sequncia
de cdigo localiza a implementao apropriada de draw. Se a chamada tivesse sido
SimplePoint.draw, ento o mesmo processo teria encontrado o ponteiro do vetor do
mtodo Point e chamado de Point.draw.
O exemplo considera que cada classe tem um vetor de mtodo completo. Assim, o
slot para move no vetor de mtodos de ColorPoint aponta para Point.move,
enquanto o slot para draw aponta para ColorPoint.draw. Esse esquema produz o
CACHES DE MTODO
Para dar suporte a uma hierarquia de classe aberta, o compilador pode ter que produzir
uma chave de busca para cada nome de mtodo e reter um mapeamento de chaves
para implementaes que possa pesquisar em tempo de execuo. O mapeamento
do nome de mtodo para a chave de busca pode ser simples usando o nome do
mtodo ou um ndice hash para aquele nome , ou pode ser complexo atribuindoa
cada nome de mtodo um inteiro a partir de um conjunto compacto usando algum
mecanismo em tempo de ligao. Em qualquer caso, o compilador precisa incluir
tabelas que possam ser pesquisadas em tempo de execuo para localizar
a implementao de um mtodo no ancestral mais recente da classe do receptor.
Para melhorar a pesquisa de mtodo neste ambiente, o sistema de runtime pode
implementar uma cache de mtodo um software semelhante cache de dados
do hardware encontrado na maioria dos processadores. A cache de mtodo tem
um pequeno nmero de entradas, digamos, 1000. Cada entrada consiste em uma
chave, uma classe e um ponteiro para uma implementao de mtodo.
Um despacho dinmico comea com uma pesquisa na cache de mtodo; se encontrar
uma entrada com a classe do receptor e chave de mtodo, ele retorna o ponteiro
do mtodo em cache. Se a pesquisa falhar, o despacho realiza uma pesquisa completa
para cima na cadeia de superclasse, comeando com a classe do receptor; coloca
em cache o resultado que encontrar e retorna o ponteiro do mtodo.
Despacho
O processo de chamar um mtodo normalmente
chamado despacho, termo derivado do modelo de passagem de mensagens das OOLs como Smalltalk.
Herana mltipla
Algumas OOLs permitem a herana mltipla, significando que uma nova classe pode
herdar de vrias superclasses que tm layouts de objeto inconsistentes. Esta situao
impe um novo problema: o cdigo compilado para um mtodo da superclasse usa deslocamentos com base no layout do OR para essa superclasse. Naturalmente, diferentes
superclasses imediatas podem atribuir deslocamentos conflitantes aos seus campos.
Para reconciliar esses deslocamentos em conflito, o compilador precisa adotar um
esquema ligeiramente mais complexo: usar ponteiros de OR diferentes com mtodos
de superclasses diferentes.
Considere uma classe a que herda de vrias superclasses, b, g e d. Para estabelecer o
OR para um objeto de classe a, a implementao primeiro precisa impor uma ordem
sobre as superclasses de a digamos, b, g, d. Isto estabelece o OR para a classe a como
o OR inteiro, incluindo o ponteiro de classe e o vetor de mtodos para b, seguido pelo
OR inteiro para g, seguido pelo OR inteiro para d. Para este layout, ele anexa quaisquer campos declarados localmente na declarao de a, e constri o vetor de mtodos
para a anexando os mtodos de a ao vetor de mtodos para a primeira superclasse.
O desenho ao lado mostra o layout do OR resultante para a classe a. Consideramos
que a define dois campos locais, a1 e a2, e que os campos para b, g e d so nomeados
REVISO DA SEO
Linguagens como Algol normalmente utilizam escopo lxico, em que os espaos de
nomes so devidamente aninhados e novas ocorrncias de um nome obscurecem
as mais antigas. Para manter os dados associados ao seu escopo local, um procedimento tem um registro de ativao para cada chamada. Ao contrrio, embora
as linguagens orientadas a objeto possam usar escopos lxicos para nomes locais ao
procedimento, tambm contam com uma hierarquia de escopos definida pelos dados
pela hierarquia de definies de classes. Esse espao de nomes com hierarquia dual
leva a interaes mais complexas entre nomes e implementaes mais complexas.
Os dois estilos de nomeao exigem estruturas de runtime que reflitam e implementem a hierarquia de nomeao. Em uma ALL, os registros de ativao podem capturar
a estrutura do espao de nomes, fornecer o espao de armazenamento necessrio
para a maioria dos valores e preservar o estado necessrio para a correta execuo.
Em uma OOL, os registros de ativao do cdigo em execuo ainda capturam a parte
de escopo lxico do espao de nomes e o estado da execuo; porm, a implementao tambm precisa de uma hierarquia de registros de objeto e registros de classe
para modelar a parte baseada em objeto do espao de nomes.
QUESTES DE REVISO
1. Em C, setjmp e longjmp fornecem um mecanismo para transferncia
de controle entre procedimentos. setjmp cria uma estrutura de dados; chamar
longjmp sobre esta estrutura faz com que a execuo continue imediatamente
aps setjmp, com o contexto presente quando setjmp foi executado.
Que informaes setjmp deve preservar? Como a implementao
de setjmp muda entre os ARs alocados em pilha e alocados
em heap?
2. Considere o exemplo da Figura6.7. Se o compilador encontrar uma referncia
a LeftCorner com converso para a classe Point, que implementao
do mtodo draw essa referncia de converso chamaria? Como o programador
poderia se referir outra implementao de draw?
A chamada a zero atribui zero a cada elemento do array Work. Para ver isto,
reescreva zero com o texto dos parmetros reais.
Embora a vinculao em chamada por nome fosse fcil de definir, foi difcil de
implementar e entender. Em geral, o compilador precisa produzir, para cada parmetro
formal, uma funo que avalie o parmetro real e retorne um ponteiro. Essas funes
so chamadas thunks. A gerao de thunks competentes era complexa, e a avaliao
de um thunk para cada acesso de parmetro, dispendiosa. Por fim, essas desvantagens
superaram quaisquer vantagens que a vinculao de parmetros em chamadas por
nome oferecia.
A linguagem de programao R, uma ferramenta especfica para anlise estatstica,
implementa uma forma preguiosa de vinculao com chamada por valor. Esta
implementao cria e passa thunks que so invocados na primeira vez que o valor do
parmetro realmente referenciado. O thunk, ou compromisso (promise), armazena
seu resultado para referncias subsequentes.
Chamada
por valor
in
out
in
out
Valor de
retorno
fee(2,3)
fee(a, b)
fee(a,a)
Alias
Quando dois nomes podem se referir ao mesmo local,
so considerados aliases.
No exemplo, a terceira chamada cria um alias entre x e
y dentro de fee.
Chamada
por valor
Valor de
retorno
fee(2,3)
in
-
out
-
in
-
out
-
fee(a, b)
fee(a,a)
Todas as nossas figuras do AR incluram um slot para um valor retornado. Para usar este
slot, o chamador aloca espao para o valor retornado em seu prprio AR e armazena
um ponteiro para este espao no slot de retorno do seu prprio AR. O procedimento
chamado pode carregar o ponteiro a partir do slot de valor de retorno do chamador
(usando a cpia do ARP do chamador que tem no AR do procedimento chamado), e
usar o ponteiro para acessar o espao de armazenamento reservado no AR do chamador
para o valor retornado. Desde que o chamador e o chamado concordem a respeito do
tamanho do valor retornado, isso funciona.
Se o chamador no puder saber o tamanho do valor retornado, o procedimento chamado
pode ter que alocar espao para ele, possivelmente no heap. Neste caso, o procedimento
chamado aloca o espao, armazena nele o valor retornado, e armazena o ponteiro no
slot de valor de retorno do AR do chamador. No retorno, o chamador pode acessar
o valor de retorno usando o ponteiro que encontra em seu slot de valor de retorno.
O chamador precisa liberar o espao alocado pelo procedimento chamado.
Se o valor de retorno for pequeno do tamanho do slot do valor de retorno ou menos
, ento o compilador pode eliminar a indireo. Para um valor de retorno pequeno, o
procedimento chamado pode armazen-lo diretamente no slot de valor de retorno do AR
do chamador, que pode, ento, usar o valor diretamente a partir do seu AR. Essa melhoria
exige, naturalmente, que o compilador trate o valor da mesma forma nos procedimentos
chamador e chamado. Felizmente, as assinaturas de tipo para os procedimentos podem
garantir que as duas compilaes tenham a informao necessria.
rea de dados
A regio na memria que mantm os dados
para um escopo especfico chamada rea de dados.
Endereo de base
O endereo do incio de uma rea de dados
normalmente chamado endereo de base.
O compilador constri o rtulo para um endereo de base deformando o nome. Normalmente, acrescenta um prefixo, um sufixo ou ambos ao nome original, usando caracteres
que so vlidos no cdigo assembly, mas no na linguagem-fonte. Por exemplo, a
deformao do nome da varivel global fee pode produzir o rtulo &fee, que ento
anexado a uma pseudo-operao de linguagem assembly que reserva espao para fee.
Para mover o endereo para um registrador, o compilador pode emitir uma operao
como loadI & fee. ri . Operaes subsequentes podem ento usar r i para
acessar o local de memria para fee. O rtulo torna-se um smbolo realocvel para o
assembler e para o loader, que o converte em um endereo virtual em tempo de execuo.
Variveis globais podem ser rotuladas individualmente ou em grupos maiores. Em
FORTRAN, por exemplo, a linguagem rene variveis globais em blocos common. Um
compilador FORTRAN tpico estabelece um rtulo para cada bloco common; atribui
um deslocamento a cada varivel em cada bloco e gera operaes load e store
relativa ao rtulo do bloco common. Se a rea de dados for maior que o deslocamento
permitido em uma operao registrador+deslocamento, pode ser vantajoso ter vrios
rtulos para partes da rea de dados.
De modo semelhante, o compilador pode combinar todas as variveis estticas de
um nico escopo em uma rea de dados, reduzindo a probabilidade de um conflito
de nomes inesperado; tais conflitos so descobertos durante a ligao ou carga, e podem
ser confusos para o programador. Para evitar esses conflitos, o compilador pode basear
o rtulo em um nome, associado ao escopo, globalmente visvel. Essa estratgia diminui o
nmero de endereos de base em uso a qualquer momento, reduzindo a demanda por
registradores. O uso de muitos registradores para manter endereos de base pode afetar
de forma adversa o desempenho geral em tempo de execuo.
Links de acesso
A intuio por trs dos links de acesso simples. O compilador garante que cada AR
contenha um ponteiro, chamado link de acesso ou link esttico, para o AR do seu
ancestral lxico imediato. Os links de acesso formam uma cadeia que inclui todos os
ancestrais lxicos do procedimento atual, como mostra a Figura6.8. Assim, qualquer
Coordenada
Cdigo
2,24
loadAI
rarp, 24
r2
1,12
loadAI
rarp, 4
r1
loadAI
r1, 12
r2
loadAI
rarp, 4
r1
loadAI
r1, 4
r1
loadAI
r1,16
r2
0,16
Como o compilador tem a coordenada esttica para cada referncia, pode calcular a
distncia esttica (mn), que lhe diz quantos loads devem ser gerados para seguir a
cadeia, de modo que possa emitir a sequncia correta para cada referncia no local.
O custo do clculo de endereo proporcional distncia esttica. Se os programas
exibirem um aninhamento lxico superficial, a diferena em custo entre acessar duas
variveis em diferentes nveis ser muito pequena.
Para manter os links de acesso, o compilador deve acrescentar cdigo para cada
chamada de procedimento que encontre o ARP apropriado e o armazene como
o link de acesso do procedimento chamado. Para um chamador no nvel m e um
procedimento chamado no nvel n, surgem trs casos. Se n=m+1, o procedimento chamado aninhado dentro do chamador, e o chamado pode usar o ARP
do chamador como seu link de acesso. Se n=m, o link de acesso do chamado o
mesmo que o do chamador. Finalmente, se n<m, o link de acesso do procedimento
chamado link de acesso de nvel n 1 para o chamador. (Se n for zero, o link
de acesso nulo.) O compilador pode gerar uma sequncia de m n+1 loads
para encontrar esse ARP e armazenar esse ponteiro como o link de acesso do
procedimento chamado.
Display global
Neste esquema, o compilador aloca um nico array global, chamado display, para
manter o ARP da ativao mais recente de um procedimento em cada nvel lxico.
Todas as referncias a variveis locais de outros procedimentos tornam-se referncias
indiretas por meio do display. Para acessar uma varivel n, d, o compilador usa o ARP
do elemento n do display, usa d como deslocamento e gera a operao load apropriada.
A Figura6.9 mostra esta situao.
Retornando s coordenadas estticas usadas na discusso dos links de acesso, a tabela
a seguir mostra o cdigo que o compilador poderia emitir para uma implementao
baseada em display. Suponha que o procedimento atual esteja no nvel lxico 2, e que
o rtulo _ disp indique o endereo do display.
Coordenada
Cdigo
2,24
loadAI
rarp, 24
r2
1,12
loadAI
_disp
r1
loadAI
r1,4
r1
loadAI
r1,12
r2
loadAI
_ disp
r1
loadAI
r1,16
r2
0,16
Com um display, o custo do acesso no local fixo. Com links de acesso, o compilador
gera uma srie de mn loads; com um display, usa nl como deslocamento no display, onde l o tamanho de um ponteiro (4, no exemplo). O acesso local ainda mais
barato do que o no local, mas, com um display, a penalidade para o acesso no local
constante, ao invs de varivel.
Naturalmente, o compilador precisa inserir cdigo onde for necessrio para manter o
display. Assim, quando o procedimento p no nvel n chama algum procedimento q no
nvel n+1, o ARP de p torna-se a entrada do display para o nvel n. (Enquanto p est
sendo executado, essa entrada no usada.) O modo mais simples de manter o display atualizado fazer que p atualize a entrada de nvel n quando o controle entra em
p e restaur-lo na sada de p. Na entrada, p pode copiar a entrada do display de nvel
n para o slot de endereabilidade reservado em seu AR e armazenar seu prprio ARP
no slot de nvel n do display.
Muitas dessas atualizaes de display podem ser evitadas. Os nicos procedimentos que
podem usar o ARP armazenado por um procedimento p so os procedimentos q que p
REVISO DA SEO
Se o propsito fundamental de um procedimento a abstrao, ento a capacidade
de comunicar valores entre procedimentos crtica sua utilidade. O fluxo de valores
entre procedimentos ocorre por dois mecanismos diferentes: o uso de parmetros
e o de valores que so visveis em mltiplos procedimentos. Em cada um desses casos,
o construtor de compiladores precisa providenciar convenes de acesso e estruturas
de runtime para dar suporte ao acesso. Para a vinculao de parmetros, dois mecanismos em particular surgiram como os casos comuns: chamadas por valor e chamadas
por referncia. Para os acessos no locais, o compilador precisa emitir cdigo
para calcular os endereos de base apropriados. Dois mecanismos surgiram
como os casos comuns: links de acesso e display.
O aspecto mais confuso deste material a distino entre aes que acontecem
em tempo de compilao, como a determinao, pelo parser, das coordenadas estticas para uma varivel, e aquelas que acontecem em tempo de execuo, como
o rastreamento, feito pelo programa em execuo, de uma cadeia de links de acesso
para encontrar o ARP de algum escopo apropriado. No primeiro caso, o compilador
realiza a ao diretamente. No segundo, o compilador emite cdigo que realizar
a ao em tempo de execuo.
QUESTES DE REVISO
1. Uma antiga implementao FORTRAN tinha um bug estranho. O programa curto
mostrado na margem imprimia, como seu resultado, o valor 16. O que o compilador fez que levou a este resultado? O que ele deveria ter feito? (FORTRAN usa
chamada por referncia como vinculao de parmetros.)
2. Compare os custos envolvidos no uso de links de acesso em relao a displays
globais para estabelecer endereos para referncias a variveis declaradas em outros
escopos. Qual voc escolheria? As caractersticas da linguagem afetam
sua escolha?
entre os ambientes de runtime nos diferentes locais de chamadas. Desde que todos os
procedimentos obedeam conveno de ligao, os detalhes sero mescladospara
criar a transferncia transparente de valores prometida pela especificao da linguagem-fonte.
A conveno de ligao , necessariamente, dependente da mquina. Por exemplo, ela
depende implicitamente de informaes como o nmero de registradores disponveis
na mquina-alvo e os mecanismos para executar uma chamada e um retorno.
A Figura6.10 mostra como as partes de uma ligao de procedimento padro se
encaixam. Cada procedimento tem uma sequncia de prlogo e uma sequncia de
eplogo. Cada local de chamada inclui uma sequncia de pr-chamada e uma sequncia
de ps-retorno.
Salvamento de registradores
Registradores salvos pelo chamador
Os registradores designados para salvamento pelo
procedimento chamador so registradores salvos
pelochamador.
Em algum ponto na sequncia de chamada, quaisquer valores de registrador que o chamador espera que sobreviva chamada precisam ser salvos na memria. O procedimento
chamador ou o chamado pode realizar o salvamento real; existe uma vantagem em cada
uma das escolhas. Se o chamador salva os registradores, pode evitar o salvamento de
valores que sabe no so teis no decorrer da chamada; este conhecimento pode permitir que ele preserve menos valores. De modo semelhante, se o chamado salva os
REVISO DA SEO
A ligao de procedimentos une os procedimentos. A conveno de ligao um
contrato social entre o compilador, o sistema operacional e o hardware subjacente,
que governa a transferncia de controle entre procedimentos, a preservao do estado do chamador e a criao do estado do procedimento chamado, alm das regras
para passagem de valores entre eles.
As ligaes padro de procedimentos nos permitem montar programas executveis a
partir de procedimentos que possuem diferentes autores, que so traduzidos em diferentes momentos e que so compilados com diferentes compiladores. As ligaes de
procedimentos permitem que cada um opere de forma segura e correta. As mesmas
convenes permitem que o cdigo da aplicao invoque chamadas de sistema e de
biblioteca. Embora os detalhes da conveno de ligao variem de um sistemapara
outro, os conceitos bsicos so semelhantes entre a maioria das combinaes
de mquina-alvo, sistema operacional e compilador.
QUESTES DE REVISO
1. Que papel a conveno de ligao desempenha na construo de grandes programas? E de programas escritos em diferentes linguagens? Que fatos o compilador
precisa saber a fim de gerar cdigo para uma chamada em um programa escrito
em diferentes linguagens?
2. Se o compilador sabe, em uma chamada de procedimento, que o procedimento
chamado, por si s, no contm quaisquer chamadas de procedimento, que
etapas ele poderia omitir da sequncia de chamada? Existem campos no AR
queoprocedimento chamado nunca precisaria?
Para contornar esta falha, o alocador pode usar o ponteiro ao final de um bloco liberado
para agrupar blocos livres adjacentes. A rotina free carrega a palavra que precede o
campo de tamanho de bj, que o ponteiro de fim de bloco para o que precede imediatamente bj na memria. Se essa palavra contiver um ponteiro vlido, que aponte para
um cabealho de bloco correspondente (um cujo endereo mais o campo de tamanho
apontem para o incio de bj), ento tanto bj quanto seu predecessor esto livres. A
rotina free pode combin-los aumentando o campo de tamanho do predecessor e
armazenando o ponteiro apropriado ao final de bj. A combinao desses blocos permite que free evite a atualizao da lista livre.
Para que este esquema funcione, allocate e free precisam manter os ponteiros de
fim de bloco. Toda vez que free processa um bloco, deve atualizar esse ponteirocom
o endereo do incio do bloco. A rotina allocate precisa invalidar o ponteiro next
ou o ponteiro de fim de bloco para impedir que free agrupe um bloco liberado com
um alocado em que esses campos no foram sobrescritos.
A rotina free tambm pode tentar combinar bj com seu sucessor na memria, bk,
usando o campo de tamanho de bj para localizar o incio de bk. Ela pode usar o campo
de tamanho de bk e o ponteiro de fim de bloco para determinar se bk est livre. Se
estiver, ento free pode combinar os dois blocos, removendo bk da lista livre, acrescentando bj lista livre e atualizando o campo de tamanho de bj e o ponteiro de fim
de bloco de modo apropriado. Para tornar a atualizao da lista livre eficiente, esta
lista deve ser duplamente encadeada. Naturalmente, os ponteiros so armazenados em
blocos no alocados, de modo que o overhead de espao irrelevante. O tempo extra
exigido para atualizar a lista livre duplamente encadeada mnimo.
Conforme descrevemos, o esquema de agrupamento depende do fato de o relacionamento entre o ponteiro final e o campo de tamanho em um bloco livre estar ausente
em um bloco alocado. Embora seja extremamente improvvel que o alocador identifiqueumbloco alocado como livre, isto pode acontecer. Para evitar este evento
improvvel, o implementador pode tornar o ponteiro de fim de bloco um campo que
exista nos blocos alocados e nos blocos livres. Na alocao, o ponteiro definido para
conter um endereo fora do heap, como zero. Na liberao, o ponteiro definido como
o endereo do prprio bloco. O custo desta certeza adicional um campo extra em cada
bloco alocado e um espao de armazenamento extra para cada alocao.
Muitas variaes da alocao de primeiro encaixe tem sido experimentadas. Elas
negociam o custo de allocate, o custo de free, a quantidade de fragmentao
produzida por uma longa srie de alocaes e a quantidade de espao desperdiada
retornando blocos maiores do que o solicitado.
Ajuda na depurao
Programas escritos com alocao e desalocao explcitas so notoriamente difceis
de depurar. Parece que os programadores tm dificuldade em decidir quando liberar os
objetos alocados em heap. Se o alocador puder distinguir rapidamente entre um objeto
alocado e um objeto livre, ento o software de gerenciamento de heap pode fornecer
ao programador alguma ajuda na depurao.
Por exemplo, para agrupar blocos livres adjacentes, o alocador precisa de um ponteiro
do final de um bloco at o seu incio. Se um bloco alocado tiver este ponteiro definido
como um valor invlido, ento a rotina de desalocao pode verificar esse campo e
relatar um erro de execuo quando o programa tentar desalocar um bloco livre ou
um endereo ilegal um ponteiro para qualquer ponto que no seja o incio de um
bloco alocado.
Contagem de referncia
Esta tcnica acrescenta um contador a cada objeto alocado no heap. O contador acompanha o nmero de ponteiros pendentes que se referem ao objeto. Quando o alocador cria
o objeto, tambm define seu contador de referncia. Cada atribuio a uma varivel
de ponteiro ajusta duas contagens de referncia; decrementa a contagem de referncia
do valor de pr-atribuio do ponteiro e incrementa a contagem de referncia do seu
valor de ps-atribuio. Quando o contador de referncia de um objeto cai para zero,
no existe um ponteiro que possa alcan-lo, de modo que o sistema pode seguramente
liber-lo. A liberao de um objeto pode, por sua vez, descartar ponteiros para outros
objetos, o que deve decrementar seus contadores de referncia. Assim, descartar o
ltimo ponteiro para uma rvore sinttica abstrata deve liberar a rvore inteira. Quando
o contador de referncia do n raiz cair para zero, o n liberado e os contadores de
referncia de seus descendentes so decrementados. Isto, por sua vez, deve liberar os
descendentes, decrementando os contadores de seus filhos. Este processo continua at
que a AST inteira tenha sido liberada.
A presena de ponteiros nos objetos alocados cria problemas para esquemas de contagem de referncia, como a seguir:
1. O cdigo em execuo precisa de um mecanismo para distinguir ponteiros
de outros dados. Ele pode armazenar informao extra no campo de cabealho
para cada objeto ou limitar a faixa de ponteiros para menos de uma palavra
inteira e usar os bits restantes para etiquetar o ponteiro. Os coletores em lote
enfrentam o mesmo problema e utilizam as mesmas solues.
2. A quantidade de trabalho realizado para um nico decremento pode se tornar
muito grande. Se as restries externas exigirem tempos de desalocao limitados, o sistema de runtime pode adotar um protocolo mais complexo, que limita
o nmero de objetos desalocados para cada atribuio de ponteiro. Mantendo
uma fila de objetos que devem ser liberados e limitando o nmero tratado
em cada ajuste de contagem de referncia, o sistema pode distribuir o custo
de liberar objetos por um conjunto maior de operaes, o que amortiza o custode liberao pelo conjunto de todas as atribuies a objetos alocados em heap
e limita o trabalho feito por atribuio.
3. O programa pode formar grafos cclicos com ponteiros. As contagens de referncia para uma estrutura de dados cclica no podem ser decrementadas at zero.
Quando o ltimo ponteiro externo descartado, o ciclo torna-se inalcanvel e
no reciclvel. Para garantir que todos esses objetos sejam liberados, o programador precisa quebrar o ciclo antes de descartar o ltimo ponteiro para o ciclo.
(A alternativa, para realizar anlise de alcanabilidade sobre os ponteiros em
runtime, tornaria a contagem de referncia proibidamente dispendiosa.) Muitas
categorias de objetos alocados em heap, como strings de tamanho varivel e
registros de ativao, no podem ser envolvidos em tais ciclos.
Contagem de referncia incorre em custo adicional a cada atribuio de ponteiro. A
quantidade de trabalho feito para uma atribuio de ponteiro especfica pode ser limitada;
em qualquer esquema bem projetado, o custo total pode ser limitado a algum fator constante multiplicado pelo nmero de atribuies de ponteiro executadas mais o nmero
de objetos alocados. Os proponentes da contagem de referncia argumentam que esses
overheads so muito pequenos, e que o padro de reso nos sistemas de contagem de
referncia produz boa localidade de programa. Os oponentes, por sua vez, argumentam
que os programas reais realizam mais atribuies de ponteiro do que alocaes, de
modoque a coleta de lixo alcana funcionalidade equivalente com menos trabalho total.
Coletores em lote
Coletores em lote consideram a desalocao somente quando o pool de espao livre
tiver sido esgotado. Quando o alocador falha ao encontrar o espao necessrio, chama
o coletor em lote, que interrompe a execuo do programa, examina o pool de memria
alocada para descobrir objetos no usados e reivindica seu espao. Ao final do processo,
o pool de espao livre normalmente est no vazio. O alocador pode terminar sua tarefa
original e retornar um novo objeto alocado ao chamador. (Assim como a contagem de
referncia, existem esquemas que realizam coleta de forma incremental para amortizar
o custo sobre maiores perodos de execuo.)
Logicamente, os coletores em lote prosseguem em duas fases. A primeira descobre
o conjunto de objetos que podem ser alcanados a partir de ponteiros armazenados
em variveis de programa e temporrios gerados pelo compilador. O coletor assume,
de forma conservadora, que qualquer objeto assim alcanvel est vivo e que o restante est morto. A segunda fase desaloca e recicla objetos mortos. Duas tcnicas
comumente utilizadas so coletores marcar-varrer e coletores de cpia, que diferem
na implementao da segunda fase da coleta reciclagem.
Coletores marcar-varrer
Coletores marcar-varrer reivindicam e reciclam objetos fazendo uma passada linear
sobre o heap. O coletor acrescenta cada objeto desmarcado lista livre (ou uma delas),
onde o alocador o encontrar e o reutilizar. Com uma nica lista livre, a mesma
coleo de tcnicas usadas para agrupar blocos no alocador de primeiro encaixe se
aplica. Se a compactao for desejvel, pode ser implementada misturando objetos
vivos de forma incremental durante a varredura, ou com um passo de compactao
ps-varredura.
Coletores de cpia
Coletores de cpia dividem a memria em dois pools, um antigo e um novo. O alocador
sempre opera a partir do pool antigo. O tipo mais simples de coletor de cpia chamado
parar e copiar. Quando uma alocao falha, um coletor do tipo parar-e-copiar copia todos os
dadosvivos do pool antigo para o novo e troca as identidades desses pools. O ato de copiardados vivos os compacta; aps a coleta, todo o espao livre est em um nico blococontguo. A
coleta pode ser feita em dois passos, como marcar-varrer, ou ser feita de modo incremental,
medida que dados vivos so descobertos. Um esquema incremental pode marcar objetos
no pool antigo, enquanto os copia para evitar copiar o mesmo objeto vrias vezes.
Uma famlia importante de coletores de cpia so os coletores de gerao. Estes
aproveitam a observao de que um objeto que sobrevive a uma coleta mais provvel
de sobreviver a coletas subsequentes. Para aproveitar esta observao, coletores de
gerao reparticionam periodicamente seu pool novo em um novo e um antigo.
Deste modo, coletas sucessivas examinam apenas objetos recm-alocados. Os esquemas
de gerao variam na frequncia com que declaram uma nova gerao, congelando
os objetos sobreviventes e isentando-os da prxima coleta, e se eles periodicamente
reexaminam as geraes mais antigas ou no.
Comparao de tcnicas
A coleta de lixo libera o programador de ter que se preocupar em liberar memria e
rastrear os vazamentos de armazenamento inevitveis, que resultam da tentativa de
gerenciar alocao e desalocao explicitamente. Os esquemas individuais tm seus
pontos fortes e fracos. Na prtica, os benefcios da desalocao implcita superam as
desvantagens de qualquer esquema para a maioria das aplicaes.
A contagem de referncia distribui o custo da desalocao mais uniformemente pela
execuo do programa do que a coleta em lote. Porm, aumenta o custo de cada
atribuio que envolve um valor alocado em heap mesmo que o programa nunca esgote o espao livre. Ao contrrio, os coletores em lote no incorrem em custo at que
o alocador falhe ao encontrar o espao necessrio. Neste ponto, porm, o programa
incorre no custo total da coleta. Assim, qualquer alocao pode provocar uma coleta.
Os coletores marcar-varrer examinam o heap inteiro, enquanto os de cpia s examinam
os dados vivos. Coletores de cpia, na realidade, movem cada objeto vivo, enquanto os
coletores marcar-varrer os deixam no lugar. O compromisso entre esses custos variar com
o comportamento da aplicao e com os custos reais de diversas referncias de memria.
As implementaes de contagem de referncia e os coletores em lote conservadores
tm problemas para reconhecer estruturas cclicas, pois no conseguem distinguir entre
referncias de dentro do ciclo e as de fora. Os coletores marcar-varrer comeam de um
conjunto externo de ponteiros, de modo que descobrem que uma estrutura cclica morta
inalcanvel. Os coletores de cpia, comeando pelo mesmo conjunto de ponteiros,
simplesmente no conseguem copiar os objetos envolvidos no ciclo.
Os coletores de cpia compactam a memria como uma parte natural do processo. O
coletor pode atualizar todos os ponteiros armazenados, ou exigir o uso de uma tabela
de indireo para cada acesso a objeto. Um coletor marcar-varrer preciso tambm pode
compactar a memria, movendo objetos de um extremo da memria para o espao
livre no outro extremo. Novamente, o coletor pode reescrever os ponteiros existentes
ou exigir o uso de uma tabela de indireo.
Em geral, um bom implementador pode fazer que tanto marcar-varrer quanto de cpia
funcionem bem o suficiente para que sejam aceitveis para a maioria das aplicaes.
NOTAS DO CAPTULO
Grande parte do material neste captulo vem da experincia acumulada pela comunidade
de construo de compiladores. A melhor maneira de aprender mais sobre as estruturas de
espao de nomes de diversas linguagens consultar as prprias definies da
EXERCCIOS
Seo 6.2
1. Mostre a rvore de chamadas e o histrico de execuo para o seguinte programa
em C:
Seo 6.3
3. Considere o seguinte programa em Pascal, no qual aparecem apenas chamadas
de procedimento e declaraes de variveis:
Seo 6.4
8. Considere o programa escrito em pseudocdigo tipo Pascal mostrado na
Figura6.13. Simule sua execuo sob as regras de vinculao de parmetros
de chamada por valor, chamada por referncia, chamada por nome e chamada
por valor-resultado. Mostre os resultados das instrues print em cada caso.
9. A possibilidade de que duas variveis distintas se refiram ao mesmo objeto (rea
de memria) considerada indesejvel em linguagens de programao. Considere
o seguinte procedimento em Pascal, com os parmetros passados por referncia:
Seo 6.5
10. Considere o programa em Pascal mostrado na Figura6.14a. Suponha que a
implementao use ARs conforme mostra a Figura6.14b. (Alguns campos foram
omitidos por simplicidade.) A pilha de implementao aloca os ARs, com a pilha
crescendo para o topo da pgina. O ARP o nico ponteiro para o AR, de modo
que os links de acesso so valores anteriores do ARP. Finalmente, a Figura6.14c
mostra o AR inicial para uma computao. Para o programa de exemplo da
Figura6.14a, desenhe o conjunto de seus ARs imediatamente antes do retorno
da funo F1. Inclua todas as entradas nos ARs. Use nmeros de linha para os
endereos de retorno. Desenhe arcos direcionados para os links de acesso. Rotule
os valores das variveis locais e dos parmetros. Rotule cada AR com seu nome
de procedimento.
11. Suponha que o compilador seja capaz de analisar o cdigo para determinar fatos
como deste ponto em diante, a varivel v no usada novamente neste procedimento ou a varivel v tem seu prximo uso na linha 11 deste procedimento,
e que compilador mantm todas as variveis locais em registradores para os trs
procedimentos a seguir:
a. A varivel x no procedimento f1 est viva durante as duas chamadas de procedimento. Para a execuo mais rpida do cdigo compilado, o compilador
deve mant-la em um registrador salvo pelo chamador ou em um registrador
salvo pelo chamado? Justifique sua resposta.
b. Considere as variveis a e c no procedimento main. O compilador dever
mant-las em registradores salvos pelo chamador ou pelo chamado, novamente supondo que o compilador est tentando maximizar a velocidade do
cdigo compilado? Justifique sua resposta.
12. Considere o seguinte programa Pascal. Suponha que os ARs sigam o mesmo
layout que no Exerccio 10, com a mesma condio inicial, exceto que a implementao usa um display global ao invs de links de acesso.
Captulo
Forma de cdigo
VISO GERAL DO CAPTULO
Para traduzir um programa de aplicao, o compilador deve mapear cada instruo
da linguagem fonte para uma sequncia de uma ou mais operaes no conjuntode
instrues da mquina-alvo. E precisa escolher entre muitas formas alternativas
de implementar cada construo. Essas escolhas tm um impacto forte e direto sobre
a qualidade do cdigo que o compilador eventualmente produz.
Este captulo explora algumas das estratgias de implementao que o compilador
pode empregar para uma srie de construes comuns da linguagens de programao.
Palavras-chave: Gerao de cdigo, Estruturas de controle, Avaliao de expresso
7.1INTRODUO
Quando o compilador traduz o cdigo da aplicao para a forma executvel, enfrenta
inmeras escolhas sobre detalhes especficos, como a organizao da computao e a
localizao dos dados. Essas decises normalmente afetam o desempenho do cdigo
resultante, e so orientadas por informaes que o compilador obtm no decorrer
da traduo. Quando a informao descoberta em um passo e usada em outro, o
compilador precisa registr-la para seu prprio uso posterior.
Com frequncia, os compiladores codificam fatos na forma IR (Intermediate Representation) do programa fatos que so difceis de obter novamente, a menos que
estejam codificados. Por exemplo, o compilador poderia gerar a IR de modo que cada
varivel escalar que pode residir seguramente em um registrador seja armazenada em
um registrador virtual. Neste esquema, a tarefa do alocador de registradores decidir
quais registradores virtuais deve rebaixar para a memria. A alternativa, gerar a IR com
variveis escalares armazenadas na memria e fazer com que o alocador as promova
para registradores, exige uma anlise muito mais complexa. Codificar o conhecimento
no espao de nomes da IR desta maneira simplifica os ltimos passos e melhora a
eficcia e a eficincia do compilador.
Roteiro conceitual
A traduo das construes do cdigo fonte para operaes na mquina alvo um dos
atos fundamentais da compilao. O compilador precisa produzir cdigo-alvo para
cada construo da linguagem-fonte. Muitas das mesmas questes surgem ao gerar a
IR no front end do compilador e ao gerar cdigo assembly para um processador real
em seu back end. O processador-alvo pode, devido a recursos finitos e caractersticas
peculiares, apresentar um problema mais difcil, mas os princpios so os mesmos.
Este captulo focaliza as formas de implementar diversas construes da linguagem-
fonte. Em muitos casos, detalhes especficos da implementao afetam a capacidade
do compilador de analisar e melhorar o cdigo nos ltimos passos. O conceito de
forma de cdigo encapsula todas as decises, grandes e pequenas, que o construtor
de compiladores faz sobre como representar a computao tanto na IR como no
283
cdigo assembly. Uma ateno cuidadosa forma do cdigo pode simplificar a tarefa
de analisar e melhorar o cdigo, alm de melhorar a qualidade do cdigo final que o
compilador produz.
Viso geral
Em geral, o construtor de compiladores deve se concentrar em modelar o cdigo de
modo que os diversos passos no compilador possam ser combinados para produzir
cdigo de qualidade. Na prtica, um compilador pode implementar a maioria das
construes da linguagem-fonte de muitas maneiras em um determinado processador.
Essas variaes usam diferentes operaes e diferentes abordagens. Algumas dessas
implementaes so mais rpidas que outras; outras usam menos memria; algumas,
menos registradores; outras, ainda, podem consumir menos energia durante a execuo.
Consideramos essas diferenas como questes de forma de cdigo.
Forma de cdigo tem forte impacto sobre o comportamento do cdigo compilado e
sobre a capacidade do otimizador e do back end de melhor-lo. Considere, por exemplo,
o modo como um compilador C poderia implementar um comando switch que desviasse com base em um valor de caractere de nico byte. Ele poderia usar uma srie
em cascata de comandos ifthenelse para implementar o comando switch.
Dependendo do layout dos testes, isto poderia produzir resultados diferentes. Se o
primeiro teste for para zero, o segundo para um, e assim por diante, ento esta tcnica
regride para a busca linear sobre um campo de 256 chaves. Se os caracteres forem distribudos uniformemente, as buscas de caractere exigiro uma mdia de 128 testes e
desvios por caractere um modo dispendioso de implementar um comando case. Se,
ao invs, os testes realizarem uma pesquisa binria, o caso mdio envolveria oito testes
e desvios, um nmero mais aceitvel. Para comprometer espao de dados em funo
da velocidade, o compilador pode construir uma tabela de 256 rtulos e interpretar o
caractere carregando a entrada de tabela correspondente e saltando para ela com
um overhead constante por caractere.
Todas essas so implementaes legais do comando switch. Decidir qual faz sentido
para um determinado comando switch depende de muitos fatores. Em particular,
o nmero de casos e suas frequncias relativas de execuo so importantes, assim
como o conhecimento detalhado da estrutura de custo para os desvios no processador.
Mesmo quando o compilador no pode determinar a informao de que precisa para
fazer a melhor escolha, ainda assim precisa fazer uma escolha. As diferenas entre as
implementaes possveis e a escolha do compilador so questes de forma de cdigo.
Como outro exemplo, considere a expresso simples x+y+z, onde x, y e z so inteiros.
A Figura7.1 mostra vrias maneiras de implement-la. Na forma de cdigo-fonte,
podemos pensar na operao como uma adio ternria, mostrada esquerda. Porm,
o mapeamento dessa operao idealizada em uma sequncia de adies binrias expe
Registrador virtual
Nome simblico usado na IR no lugar do nome de um
registrador fsico.
A Figura7.2 mostra um layout tpico para o espao de endereos utilizado por um nico
programa compilado. O layout posiciona regies de tamanho fixo de cdigo e dados no
Pgina
Unidade de alocao em um espao de endereo
virtual.
O sistema operacional mapeia pginas virtuais em
frames de pgina fsica.
Atribuio de deslocamentos
No caso de reas de dados locais, estticas e globais, o compilador precisa atribuir
a cada nome um deslocamento (offset) dentro da rea de dados. ISAs de mquinas-
alvo restringem o posicionamento de itens de dados na memria. Um conjunto
tpico de restries poderia especificar que inteiros de 32 bits e nmeros de ponto
flutuante de 32 bits comecem em fronteiras de palavra (32 bits), que inteiros de
64 bits e dados de ponto flutuante comecem em fronteiras de palavra dupla (64 bits),
e que dados de string comecem em fronteiras de meia palavra (16 bits). Chamamos
isto de regras de alinhamento.
Alguns processadores fornecem operaes para implementar chamadas de procedimento alm de uma simples operao de salto. Este suporte normalmente acrescenta
mais restries de alinhamento. Por exemplo, a ISA poderia ditar o formato do AR
e o alinhamento do incio de cada AR. Os computadores VAX da DEC tinham uma
instruo de chamada particularmente elaborada; ela armazenava o estado de registradores e outras partes do processador com base em uma mscara de bits especfica
de chamada, que o compilador produzia.
Para cada rea de dados, o compilador precisa calcular um layout que atribui o deslocamento de cada varivel na rea de dados. Esse layout precisa ser compatvel com
as regras de alinhamento da ISA. O compilador pode ter que inserir preenchimentos
entre algumas variveis para obter os alinhamentos apropriados. Para reduzir o espao desperdiado, o compilador deve ordenar as variveis em grupos, daqueles
com as regras de alinhamento mais restritivas para aqueles com as menos restritivas.
(Por exemplo, o alinhamento de palavra dupla mais restritivo do que o de palavra.)
O compilador, ento, atribui deslocamentos s variveis na categoria mais restrita,
seguido pela prxima classe mais restrita, e assim por diante, at que todas as variveis
tenham deslocamentos. Como as regras de alinhamento quase sempre especificam uma
potncia de dois, o final de cada categoria se ajustar naturalmente restrio para a
prxima categoria.
na cache ao mesmo tempo. Isto pode ser realizado de duas maneiras. Na melhor das
hipteses, os dois valores compartilhariam um nico bloco de cache, que garante que
eles sero buscados da memria para a cache juntos. Se no puderem compartilhar um
bloco de cache, o compilador gostaria de garantir que as duas variveis sejam mapeadas
para linhas de cache diferentes. E pode conseguir isso controlando a distncia entre
seus endereos.
Se considerarmos apenas duas variveis, o controle da distncia entre elas parece ser
algo administrvel. Porm, quando todas as variveis ativas so consideradas, o problema do arranjo timo para uma cache NP-completo. A maioria das variveis tem
interaes com muitas outras; isto cria uma teia de relacionamentos que o compilador
pode no ser capaz de satisfazer simultaneamente. Se considerarmos um lao que usa
vrios arrays grandes, o problema de arrumar a no interferncia mtua torna-se ainda
pior. Se o compilador puder descobrir o relacionamento entre as diversas referncias de
array no lao, pode incluir preenchimento entre os arrays para aumentar a probabilidade
de que as referncias atinjam diferentes linhas de cache e, assim, no interfiram umas
com as outras.
Conforme vimos, o mapeamento do espao de endereos lgicos do programa para o
de endereos fsicos do hardware no precisa preservar a distncia entre variveis especficas. Contudo, levando isto para sua concluso lgica, o leitor se questionar como
o compilador pode garantir algo sobre deslocamentos relativos que sejam maiores do
que o tamanho de uma pgina de memria virtual. A cache do processador pode usar
endereos virtuais ou fsicos em seus campos de tag. Uma cache endereada virtualmente preserva o espaamento entre valores que o compilador cria; com tal cache, o
compilador pode ser capaz de planejar a no interferncia entre objetos grandes. Com
uma cache endereada fisicamente, a distncia entre dois locais em pginas diferentes
determinada pelo mapeamento de pgina (a menos que tamanho de cachetamanho
de pgina). Assim, as decises do compilador sobre layout de memria tm pouco ou
nenhum efeito, exceto dentro de uma nica pgina. Nesta situao, o compilador deve
se concentrar em obter objetos que so referenciados juntos na mesma pgina e, se
possvel, na mesma linha de cache.
Um valor que o compilador pode manter em um registrador chamado valor no ambguo; um valor que pode ter mais de um nome chamado valor ambguo. A ambiguidade
surge de vrias maneiras. Os valores armazenados em variveis baseadas em ponteiro
normalmente so ambguas. As interaes entre parmetros formais de chamada por
referncia e regras de escopo de nome podem tornar os parmetros formais ambguos.
Muitos compiladores tratam valores de elemento de array como ambguos, pois o
compilador no consegue saber se duas referncias, como A[i, j] e A[m,n], podem se
referir ao mesmo local. Em geral, o compilador no pode manter um valor ambguo em
um registrador por meio de uma definio ou do uso de outro valor ambguo.
Com uma anlise cuidadosa, o compilador pode remover a ambiguidade de alguns
desses casos. Considere a sequncia de atribuies na margem, supondo que tanto a
quanto b sejam ambguos. Se a e b se referem ao mesmo local, ento c recebe o valor
26; caso contrrio, recebe m+n+13. O compilador no pode manter a em um registrador por meio de uma atribuio a outra varivel ambgua, a menos que possa provar
que os conjuntos de locais aos quais os dois nomes podem se referir so disjuntos.
Este tipo de anlise de emparelhamento comparativa dispendiosa, de modo que os
compiladores normalmente relegam valores ambguos memria, com um load antes
de cada uso e um store aps cada definio.
A anlise da ambiguidade, portanto, concentra-se em provar que determinado valor
no ambguo. A anlise pode ser superficial e local. Por exemplo, em C, qualquer
varivel local cujo endereo nunca tomado no ambgua no procedimento onde
declarada. Anlises mais complexas montam conjuntos de nomes possveis para cada
varivel de ponteiro; qualquer varivel cujo conjunto tenha apenas um elemento
no ambgua. Infelizmente, a anlise no pode resolver todas as ambiguidades. Assim, o compilador precisa estar preparado para lidar com valores ambguos de modo
cuidadoso e correto.
Caractersticas da linguagem podem afetar a capacidade do compilador de analisar
a ambiguidade. Por exemplo, ANSI C inclui duas palavras-chave que comunicam
diretamente informaes sobre ambiguidade: restrict informa ao compilador que
um ponteiro no ambguo normalmente usada quando um procedimento passa um
endereo diretamente em um local de chamada ; enquanto volatile permite que
o programador declare que o contedo de uma varivel pode mudar arbitrariamente e
sem aviso usada para registradores de dispositivo de hardware e para variveis que
poderiam ser modificadas por rotinas de atendimento de interrupo ou outras tarefas
(threads) de controle em uma aplicao.
REVISO DA SEO
O compilador precisa determinar, para cada valor calculado no programa, onde ele deve
ser armazenado: na memria ou em um registrador e, nos dois casos, o local especfico.
Precisa tambm atribuir a cada valor um local que seja coerente com seu tempo de vida
(ver Seo 6.3) e sua endereabilidade (ver Seo 6.4.3). Assim, o compilador agrupar
valores em reas de dados nas quais cada valor tem a mesma classe de armazenamento.
A atribuio de armazenamento fornece ao compilador uma oportunidade estratgica
para codificar informaes na IR para uso por passos posteriores. Especificamente, a
distino entre um valor ambguo e um no ambguo pode ser difcil de obter pela
anlise da IR. Porm, se o compilador atribuir a cada valor no ambguo seu prprio
registrador virtual por todo seu tempo de vida, as fases subsequentes do compilador
podem usar o local de armazenamento de um valor para determinar se uma referncia ou no ambgua. Esse conhecimento simplifica a otimizao subsequente.
Valor no ambguo
Valor que pode ser acessado por apenas um nome
no ambguo.
Valor ambguo
Qualquer valor que pode ser acessado por vrios nomes
ambguo.
QUESTES DE REVISO
1. Esboce um algoritmo que atribua deslocamentos a uma lista de variveis estticas em um nico arquivo a partir de um programa em C. Como ele ordena as
variveis? Que restries de alinhamento seu algoritmo poderia encontrar?
2. Considere o pequeno fragmento de programa em C na margem. Ele menciona
trs valores: a, b e *b. Quais valores so ambguos? Quais so no ambguos?
base e um registrador para manter esse valor por um breve tempo. Como alternativa, se
a estivesse em um registrador, as duas instrues usadas para carreg-lo em r2 poderiam
ser omitidas, e o compilador usaria o nome do registrador que mantm a diretamente na
instruo sub. Manter o valor em um registrador evita tanto o acesso memria quanto
qualquer clculo de endereo. Se a, b e c j estivessem em registradores, a sequncia
de sete instrues poderia ser encurtada para uma sequncia de duas instrues.
Decises de forma de cdigo codificadas no gerador de cdigo por percurso em rvore
tm efeito sobre a demanda por registradores. O cdigo simples da figura utiliza oito
registradores mais rarp. tentador assumir que o alocador de registradores, quando
executado mais adiante na compilao, pode reduzir o nmero de registradores a um
mnimo. Por exemplo, o alocador de registradores poderia reescrever o cdigo como
mostra a Figura7.6a, que reduz o uso de registradores de oito para trs, mais o rarp.
A demanda mxima por registradores ocorre na sequncia que carrega c e realiza a
multiplicao.
Uma forma de cdigo diferente pode reduzir a demanda por registradores. O gerador
de cdigo por percurso em rvore carrega a antes de calcular bc, um artefato da
deciso de usar o percurso em rvore da esquerda para a direita. O uso do percurso em
rvore da direita para a esquerda produziria o cdigo mostrado na Figura7.6b. Embora
o cdigo inicial use o mesmo nmero de registradores do cdigo gerado da esquerda
para a direita, a alocao de registradores revela que o cdigo na realidade precisa de
menos registradores, como mostra a Figura7.6c.
Para escolher uma ordem de avaliao que reduza a demanda por registradores, o gerador
de cdigo deve alternar entre filhos direita e esquerda; ele precisa de informaes sobre
as necessidades de registrador detalhadas de cada subrvore. Como uma regra, o compilador pode minimizar o uso de registrador avaliando primeiro, em cada n, a subrvore
que necessita de mais registradores. O cdigo gerado precisa preservar o valor da primeira
subrvore que avalia por meio da avaliao da segunda subrvore; logo, o tratamento da
subrvore menos exigente primeiro aumenta em 1 registrador a demanda por registradores
na subrvore mais exigente. Esta tcnica exige um passo inicial pelo cdigo para calcular
a demanda por registradores, seguido por um passo que emite o cdigo real.
No decorrer deste livro, os exemplos consideram que prefervel gerar essa sequncia
de duas operaes, ao invs de uma nica operao. Trs fatores sugerem essa conduta.
1. A sequncia de cdigo maior d um nome explcito a @a. Se @a for reutilizado em
outros contextos, este nome pode ser reutilizado.
2. O deslocamento @apode no caber no campo imediato de um loadAI. Esta
determinao melhor feita no seletor de instruo.
3. A sequncia de duas operaes leva a uma decomposio funcional clara no
gerador de cdigo, mostrada na Figura7.5.
O compilador pode converter a sequncia de duas operaes em uma nica operao
durante a otimizao se for apropriado (por exemplo, @a no reutilizado ou mais
barato recarreg-lo). O melhor caminho, porm, pode ser adiar o problema para a
seleo de instrues, isolando assim o comprimento constante dependente da
mquina em uma parte do compilador que j altamente dependente da mquina.
Se o construtor de compiladores quiser gerar o loadAI mais cedo, dois mtodos
simples funcionam. Ele pode refazer o gerador de cdigo de percurso em rvore da
Figura7.5 e levar a lgica oculta em base e offset para o caso IDENT. Como
alternativa, pode fazer com que emit mantenha um pequeno buffer de instruo,
reconhea este caso especial e emita o loadAI. O uso de um pequeno buffer torna
este mtodo prtico (ver Seo 11.5).
tambm ocorrem como operandos em expresses. Para avaliar uma chamada de funo, o
compilador simplesmente gera a sequncia de chamada necessria para invocar a funo e
emite o cdigo necessrio para mover o valor retornado para um registrador (ver Seo7.9).
A conveno de ligao limita o impacto do procedimento chamado sobre o chamador.
A presena de uma chamada de funo pode restringir a capacidade do compilador de
mudar a ordem de avaliao de uma expresso. A funo pode ter efeitos colaterais
que modificam os valores das variveis usadas na expresso. O compilador deve respeitar a ordem de avaliao implcita da expresso de origem, pelo menos com relao
chamada. Sem conhecimento sobre os efeitos colaterais possveis de uma chamada,
ele no pode mover referncias por meio da chamada. Tambm, deve assumir o pior
caso que a funo modifica e usa cada varivel que ela pode acessar. O desejo de
melhorar, em hipteses de pior caso, como neste caso, tem motivado grande parte do
trabalho sobre anlise interprocedimental (ver Seo 9.4).
REVISO DA SEO
O percurso de rvore em ps-ordem fornece um modo natural para estruturar um
gerador de cdigo para rvores de expresso. O framework bsico facilmente adaptado para lidar com uma srie de complicaes, incluindo mltiplos tipos e locais de
valores, chamadas de funo, converses de tipo e operadores novos. Para melhorar
ainda mais o cdigo, pode ser preciso realizar vrias passagens por ele.
Algumas otimizaes so difceis de se ajustar em um framework de percurso em
rvore. Em particular, fazer bom uso dos modos de endereo do processador (ver
Captulo11), ordenar operaes para ocultar atrasos especficos do processador (ver
Captulo12) e alocao de registradores (ver Captulo13) no se ajustam bem ao
framework de percurso em rvore. Se o compilador utiliza um percurso em rvore
para gerar a IR, pode ser melhor mant-la simples e permitir que o back end resolva
essas questes com algoritmos especializados.
QUESTES DE REVISO
1. Esboce o cdigo para as duas rotinas de suporte, base e offset, usadas pelo
gerador de cdigo de percurso em rvore na Figura7.5.
2. Como voc adaptaria o gerador de cdigo de percurso em rvore para lidar com
uma operao de salto incondicional, como a instruo goto da linguagem C?
7.4.1Representaes
Tradicionalmente, duas representaes tm sido propostas para os valores booleanos:
uma codificao numrica e uma codificao posicional. A primeira atribui valores
especficos a true e false e os manipula usando operaes aritmticas e lgicas da
mquina-alvo. A segunda codifica o valor da expresso como uma posio no cdigo
executvel, e usa comparaes e desvios condicionais para avaliar a expresso; os
diferentes caminhos do fluxo de controle representam o resultado da avaliao. Cada
tcnica funciona bem para alguns exemplos, mas no para outros.
Codificao numrica
Quando o programa armazena o resultado de uma operao booleana ou relacional
em uma varivel, o compilador precisa garantir que o valor tenha uma representao
concreta. O construtor de compiladores precisa atribuir valores numricos a true e false
que funcionem com operaes de hardware, como and, or e not. Os valores tpicos
so zero para false e um ou uma palavra de uns, false, para true.
Para uma comparao, como a < b , o compilador precisa gerar cdigo que compare a
e b e atribua o valor apropriado ao resultado. Se a mquina-alvo admite uma operao
de comparao que retorna um booleano, o cdigo trivial:
Se, por outro lado, a comparao define um cdigo de condio que deve ser lido com
um desvio, o cdigo resultante maior e mais complicado. Este estilo de comparao
leva a uma implementao mais confusa para a < b .
Codificao posicional
No exemplo anterior, o cdigo em L1 cria o valor true e o em L2 cria o valor false. Em
cada um desses pontos, o valor conhecido. Em alguns casos, o cdigo no precisa
produzir um valor concreto para o resultado da expresso. Ao invs disso, o compilador
pode codificar esse valor como um local no cdigo, como L1 ou L2.
A Figura7.8a mostra o cdigo que um gerador de cdigo por percurso em rvore
emite para a expresso a<b c<d e<f . O cdigo avalia as trs subexpresses,
a < b , c < d e e < f , usando uma srie de comparaes e saltos. Depois, combina o
resultado das trs avaliaes de subexpresso usando as operaes booleanas em L9.
Infelizmente, isto produz uma sequncia de operaes em que cada caminho usa 11
operaes, incluindo trs desvios e trs saltos. Parte da complexidade deste cdigo
pode ser eliminada representando os valores de subexpresso implicitamente e gerando
cdigo que realiza o curto-circuito da avaliao, como na Figura7.8b. Esta verso do
cdigo avalia a < b c < d e < f com menos operaes porque no cria valores para
representar as subexpresses.
A codificao posicional faz sentido se o resultado de uma expresso nunca for armazenado. Quando o cdigo usa o resultado de uma expresso para determinar o fluxo
de controle, a codificao posicional normalmente evita operaes irrelevantes. Por
exemplo, no fragmento de cdigo
o nico uso para a < b determinar se instruo1 ou instruo2 ser executada. neste
caso, a produo de um valor explcito para a < b no tem um propsito direto.
Em uma mquina na qual o compilador precisa usar uma comparao e um desvio para
produzir um valor, ele pode simplesmente colocar o cdigo para instruo1 e instruo2
nos locais onde o cdigo simples atribuiria true e false. Este uso da codificao posicional leva a um cdigo mais simples e mais rpido do que o da codificao numrica.
Aqui, o cdigo para avaliar a < b foi combinado com aquele para selecionar entre
instruo1 e instruo2. O cdigo representa o resultado de a < b como uma posio,
ou L1 ou L2.
AVALIAO EM CURTO-CIRCUITO
Em muitos casos, o valor de uma subexpresso determina o da expresso inteira. Por
exemplo, o cdigo mostrado na Figura7.8a avalia c < d e < f, mesmo que j tenha
sido determinado que a<b, caso em que a expresso inteira avaliada como true. De
modo semelhante, se tanto a b quanto c d, ento o valor de e < f no importa.
O cdigo na Figura7.8b usa esses relacionamentos para produzir um resultado assim
que o valor da expresso puder ser conhecido. Esta tcnica de avaliao de expresso,
em que o cdigo avalia a quantidade mnima da expresso necessria para determinar
seu valor final, chamada avaliao em curto-circuito, que conta com duas identidades
booleanas:
Movimentao condicional
Este esquema acrescenta uma instruo de movimentao condicional ao modelo de
cdigo de condio direto. Na ILOC, uma movimentao condicional se parece com:
Se o cdigo de condio cci combinar com LT, ento o valor de rj copiado para
rm. Caso contrrio, o valor de rk copiado para rm. A operao de movimentao
condicional normalmente executada em um nico ciclo. Isto leva a um cdigo mais
rpido, permitindo que o compilador evite desvios.
A movimentao condicional retm a principal vantagem do uso de cdigos de condio
evitar uma comparao quando uma operao anterior j tiver definido o cdigo
de condio. Como vemos na Figura7.9a, isto permite que o compilador codifique
operaes condicionais simples com desvios. Aqui, o compilador avalia especulativamente as duas adies, e usa a movimentao condicional para a atribuio final. Isto
seguro, desde que nenhuma adio possa gerar uma exceo.
Se o compilador tiver valores para true e false em registradores, digamos rT e rF respectivamente, ento pode usar a movimentao condicional para converter o cdigo
de condio em um booleano. A Figura7.9b usa esta estratgia. Ela compara a e b e
coloca o resultado booleano em r1; calcula o booleano para c < d em r2; e o resultado
final como a conjuno lgica (and) de r1 e r2.
Comparaes booleanas
Este esquema evita totalmente os cdigos de condio. O operador de comparao
retorna um valor booleano em um registrador. O desvio condicional toma esse resultado
como um argumento que determina seu comportamento.
As comparaes booleanas no ajudam com o cdigo na Figura7.9a. O cdigo
equivalente ao esquema de cdigo de condio direto. Ele exige comparaes, desvios
e saltos para avaliar a construo if-then-else.
A Figura7.9b mostra a fora deste esquema. A comparao booleana permite que o
cdigo avalie o operador relacional sem um desvio e sem converter os resultados da
comparao para valores booleanos. A representao uniforme dos valores booleanos
e relacionais leva a um cdigo conciso e eficiente para este exemplo.
Um ponto fraco deste modelo que exige comparaes explcitas. Embora os modelos
de cdigo de condio s vezes possam evitar a comparao, tratando de definir o
cdigo de condio apropriado com uma operao aritmtica anterior, o modelo de
comparao booleana sempre precisa de uma comparao explcita.
Execuo predicada
As arquiteturas que do suporte execuo predicada permitem que o compilador evite
alguns desvios condicionais. Na ILOC, escrevemos uma instruo predicada incluindo
uma expresso predicada antes da instruo. Para lembrar o leitor da finalidade do
predicado, vamos delimit-lo por parnteses e usar um ponto de interrogao depois.
Por exemplo,
indica uma operao de adio (ra+rb) que executada se, e somente se, r17 contiver
true.
O exemplo na Figura7.9a mostra a fora da execuo predicada. O cdigo simples
e conciso, e gera dois predicados, r1 e r2, que so utilizados para controlar o cdigo
Execuo predicada
Um recurso arquitetural em que algumas operaes
tomam um operando booleano que determina se a
operao tem efeito ou no.
QUESTES DE REVISO
1. Se o compilador atribui o valor zero para false, quais so os mritos relativos de
cada um dos seguintes valores para true? Um? Qualquer nmero diferente de
zero? Uma palavra composta totalmente por uns?
2. Como o esquema de gerao de cdigo por percurso em rvore poderia ser
adaptado para gerar cdigo posicional para expresses booleanas e relacionais?
Voc pode usar a avaliao em curto-circuito no seu mtodo?
Quando o compilador encontra uma referncia, como V[6], precisa usar o ndice no
vetor, junto com fatos disponveis a partir da declarao de V, a fim de gerar um deslocamento para V[6]. O endereo real ento calculado como a soma do deslocamento
e de um ponteiro para o incio de V, que escrevemos como @V.
Como exemplo, suponha que V tenha sido declarado como V [low...high] , onde low
e high so os limites inferior e superior do vetor. Para traduzir a referncia V[i], o
compilador precisa de um ponteiro para o incio do armazenamento para V e do deslocamento do elemento i dentro de V. O deslocamento simplesmente (i low ) w ,
onde w o tamanho de um nico elemento de V. Assim, se low 3, i 6, e w 4; o
deslocamento (6 3)4=12. Supondo que ri armazene o valor de i, o fragmento
de cdigo a seguir calcula o endereo de V[i] em r3 e carrega seu valor em rV:
Observe que a simples referncia V[i] introduz trs operaes aritmticas. O compilador pode melhorar essa sequncia. Se w for uma potncia de dois, a multiplicao
pode ser substituda por um shift aritmtico; muitos tipos bsicos em linguagens de
programao reais tm esta propriedade. A soma do endereo e do deslocamento
parece ser inevitvel; talvez isto explique por que a maioria dos processadores inclui um modo de endereamento que usa um endereo de base e um deslocamento
e acessa o local em endereo de base+deslocamento. Na ILOC, escrevemos isso
como loadAO.
Usando @V0 e supondo que i esteja em ri, o cdigo para acessar V[i] torna-se
Falso zero
O falso zero de um vetor V o endereo onde V[0]
estaria.
Em mltiplas dimenses, este o local de um zero em
cada dimenso.
O aninhamento de lao a seguir mostra o efeito da ordem por linhas sobre os padres
de acesso memria:
Na ordem por linhas, a instruo de atribuio percorre a memria em ordem sequencial, comeando com A[1,1], A[1, 2], A[1, 3], seguindo at A[2, 4] . Este acesso
sequencial funciona bem com a maioria das hierarquias de memria. Mover o lao i
para dentro do lao j, resulta em uma sequncia de acesso que salta entre as linhas,
acessando A[1,1], A[2,1] , A[1, 2] ,, A[2, 4] . Para um array pequeno, como A,
isto no um problema. Para arrays que so maiores que a cache, a falta de acesso
sequencial poderia produzir um desempenho fraco na hierarquia de memria. Em regra,
a ordem por linhas produz acesso sequencial quando o subscrito mais direita, j neste
exemplo, varia mais rpido.
FORTRAN usa a ordem por colunas.
A alternativa bvia ordem por linhas a ordem por colunas, que mantm as colunas
de A em locais contguos, produzindo o seguinte layout:
A ordem por colunas produz acesso sequencial quando o subscrito mais esquerda varia
mais rapidamente. Em nosso lao duplamente aninhado, ter o lao i na posio externa
produz aceso no sequencial, enquanto mov-lo para a posio interna produziria o
acesso sequencial.
Uma terceira alternativa, no to bvia, tem sido usada em diversas linguagens. Este
esquema usa vetores de indireo para reduzir todos os arrays multidimensionais a um
conjunto de vetores. Para o nosso array A, produziria:
Cada linha tem seu prprio armazenamento contguo. Dentro de uma linha, os elementos so endereados como em um vetor. Para permitir o endereamento sistemtico dos
vetores de linha, o compilador aloca um vetor de ponteiros e o inicializa de modo apropriado. Um esquema semelhante pode criar vetores de indireo em ordem por colunas.
Os vetores de indireo parecem simples, mas introduzem sua prpria complexidade.
Primeiro, exigem mais armazenamento do que qualquer um dos esquemas de armazenamento contguos, conforme apresentado graficamente na Figura7.10. Segundo, exigem
que a aplicao inicialize, em tempo de execuo, todos os ponteiros de indireo.
Uma vantagem do mtodo de vetor de indireo que permite a fcil implementao
de arrays irregulares, ou seja, arrays nos quais o tamanho da ltima dimenso varia.
Cada um desses esquemas tem sido usado em uma linguagem de programao popular.
Para linguagens que armazenam arrays usando o armazenamento contguo, a ordem
por linhas tem sido a escolha tpica; a nica exceo digna de nota FORTRAN, que
usa a ordem por colunas. Tanto BCPL quanto Java admitem vetores de indireo.
Substituindo os valores reais para i,j, low1, high2, low2 e w, descobrimos que
A[2,3] se encontra no deslocamento:
a partir de A[1,1] (supondo que @Aaponte para A[1,1] , no deslocamento 0). Examinando
A na memria, descobrimos que o endereo de A[1,1] +24 , na verdade, o de A[2, 3].
Vetores de indireo
O uso destes vetores simplifica o cdigo gerado para acessar um elemento individual.
Como a dimenso mais externa armazenada como um conjunto de vetores, a etapa
final se parece com o acesso de vetor descrito na Seo 7.5.1. Para B[i, j, k], a etapa
final calcula um deslocamento a partir de k, o limite inferior da dimenso mais externa,
e o tamanho de um elemento para B. As etapas preliminares derivam o endereo inicial
para esse vetor seguindo os ponteiros apropriados por meio da estrutura de vetor de
indireo.
Assim, para acessar o elemento B[i, j, k] no array B, apresentado na Figura7.10, o compilador usa @B0, i e o tamanho de um ponteiro para encontrar o vetor para o subarray B[i,*,*].
Em seguida, usa este resultado, junto com j e o tamanho de um ponteiro, para encontrar o
vetor para o subarray B[i, j,*]. Finalmente, usa este endereo de base no clculo do endereo de vetor com k e o tamanho do elemento w para encontrar o endereo de B[i,j,k].
Rvalue
Uma expresso avaliada como um valor um rvalue.
Lvalue
Uma expresso avaliada como um local um lvalue.
Se os valores atuais para i,j e k existem nos registradores ri,rj e rk, respectivamente, e @B0 o endereo ajustado em zero da primeira dimenso, ento B[i,j,k]
pode ser referenciado da seguinte forma:
Este cdigo assume que os ponteiros na estrutura de indireo j foram ajustados para
considerar os limites inferiores diferentes de zero. Se este no for o caso, ento os
valores em rj e rk devem ser decrementados pelos limites inferiores correspondentes.
As multiplicaes podem ser substitudas por shifts neste exemplo.
Usando vetores de indireo, a referncia exige apenas duas operaes por dimenso.
Esta propriedade tornou o esquema de vetor de indireo eficiente em sistemas nos
quais o acesso memria rpido em relao aritmtica por exemplo, na maioria
dos sistemas antes de 1985. medida que o custo dos acessos memria aumentou
em relao aritmtica, este esquema perdeu sua vantagem em velocidade.
Em mquinas baseadas em cache, a localidade crtica para o desempenho. Quando os
arrays crescem para se tornar muito maiores do que a cache, a ordem de armazenamento
afeta a localidade. Os esquemas de armazenamento em ordem por linhas e por colunas
produzem boa localidade para algumas operaes baseadas em array. As propriedades
de localidade de um array implementado com vetores de indireo so mais difceis
para o compilador prever e, talvez, otimizar.
Quando o compilador gera uma referncia a um parmetro array formal, precisa extrair
a informao do vetor dopado. Ele gera o mesmo polinmio de endereo que usaria
para uma referncia a um array local, carregando valores do vetor dopado conforme a
necessidade. O compilador precisa decidir, por uma questo de poltica, qual forma de
polinmio de endereo usar. Com o polinmio de endereo simples, o vetor dopado
contm um ponteiro para o incio do array, o limite inferior de cada dimenso e os
tamanhos de todas as dimenses, menos uma. Com o polinmio de endereo baseado no
falso zero, a informao de limite inferior desnecessria. Por poder compilar os procedimentos chamador e chamado separadamente, o compilador precisa ser consistente
em sua deciso. Na maior parte dos casos, o cdigo para construir o vetor dopado real
pode ser movido do local de chamada e colocado no cdigo de prlogo do chamador.
Para uma chamada dentro de um lao, esse movimento reduz o overhead da chamada.
Um procedimento poderia ser chamado a partir de vrios locais de chamada, cada um
passando um array diferente. O procedimento main em PL/I da Figura7.11a contm
duas chamadas ao procedimento fee. A primeira passa o array x, enquanto a segunda
passa y. Dentro de fee, o parmetro real (x ou y) est ligado ao parmetro formal
A. O cdigo em fee para uma referncia a A precisa de um vetor dopado para descrever o parmetro real. A Figura7.11b mostra os respectivos vetores dopados para os
dois locais de chamada, com base na verso de falso zero do polinmio de endereo.
Vetor dopado
Descritor para um parmetro real com valor de array.
Vetores dopados tambm podem ser usados para arrays
cujos limites so determinados em tempo de execuo.
REVISO DA SEO
As implementaes de linguagem de programao armazenam arrays em diversos
formatos. Os principais so arrays contguos em ordem por linhas ou por colunas e
arrays disjuntos usando vetores de indireo. Cada formato tem uma frmula distinta
para calcular o endereo de determinado elemento. Os polinmios de endereo para
arrays contguos podem ser otimizados com uma lgebra simples, para reduzir seus
custos de avaliao.
Os parmetros passados como arrays exigem cooperao entre o procedimento
chamador e o chamado. O chamador precisa criar um vetor dopado para manter as
informaes que o procedimento chamado requer. Os procedimentos chamador e
chamado devem concordar quanto ao formato do vetor dopado.
QUESTES DE REVISO
1. Para um array bidimensional A armazenado em ordem por colunas, escreva o
polinmio de endereo para a referncia A[i, j] . Suponha que A seja declarado
com as dimenses (l1 : h1 ) e (l2 : h2 ) e que os elementos de A ocupam w bytes.
2. Dado um array de inteiros com dimenses A[0 : 99,0 : 89,0 : 109], quantas
palavras de memria so usadas para representar A como um array compacto em
ordem por linhas? Quantas palavras so necessrias para representar A usando
vetores de indireo? Suponha que ponteiros e inteiros exijam uma palavra cada.
Este cdigo carrega a palavra que contm b[2], extrai o caractere, desloca-o para a
posio, mascara-o para a posio apropriada na palavra que contm a[1] e armazena
o resultado de volta ao local. Na prtica, as mscaras que o cdigo carrega em rc2 e
rc124 provavelmente seriam guardadas em armazenamento inicializado estaticamente
ou calculadas. A complexidade adicional dessa sequncia de cdigo pode explicar por
que as operaes load e store orientadas a caractere so comuns.
O cdigo semelhante para strings maiores. PL/I tem um operador de atribuio de
string. O programador pode escrever uma instruo como a = b ; onde a e b foram
declaradas como strings de caracteres. Suponha que o compilador use a representao
de tamanho explcito. O lao simples a seguir mover os caracteres em uma mquina
com operaes cload e cstore orientadas a byte.
Observe que este cdigo testa os tamanhos de a e b para evitar estourar a. (Com
representao de tamanho explcita, o overhead pequeno.) O rtulo Lsov representa
um tratador de erro em tempo de execuo para condies de estouro de string (string
overflow).
Em C, que usa a terminao nula para strings, a mesma atribuio seria escrita como
um lao de cpia de caracteres:
tamanho alocado de cada string. Se o compilador souber esses tamanhos, poder realizar a
verificao durante a gerao de cdigo e evitar a verificao em tempo de execuo. Em
casos em que o compilador no pode saber os tamanhos de a e b, ele deve gerar cdigo
para calcular os tamanhos em tempo de execuo e realizar o teste apropriado: o desvio.
REVISO DA SEO
Em princpio, as operaes de string so semelhantes a operaes sobre vetores. Os
detalhes da representao de string e as complicaes introduzidas por questes de
alinhamento e o desejo de eficincia podem complicar o cdigo que o compilador
deve gerar. Laos simples que copiam um caractere por vez so fceis de gerar,
entender e provar serem corretos. Laos mais complexos, que movem vrios caracteres por iterao, podem ser mais eficientes; o custo desta eficincia cdigo adicional
para lidar com os casos de fim. Muitos compiladores simplesmente lanam mo de
uma rotina de cpia de string fornecida pelo sistema, como as rotinas strcpy ou
memmove do Linux, para os casos complexos.
QUESTES DE REVISO
1. Escreva o cdigo ILOC para a atribuio de string a b usando loads e stores
com tamanho de palavra. (Use loads e stores com tamanho de caractere em um
lao subsequente para limpar os casos de fim.) Suponha que a e b sejam alinhados
por palavra e no se sobreponham.
2. Como seu cdigo muda se a e b forem alinhados por caractere ao invs de por
palavra? Que complicaes as strings sobrepostas introduziriam?
Cada node contm um nico inteiro e um ponteiro para outro n. As declaraes finais
criam um n, NILNode, e um ponteiro, NIL. Eles inicializam NILNode com o valor
zero e um ponteiro next ilegal, e definem NIL para que aponte para NILNode. (Os
programas normalmente utilizam um ponteiro designado NIL para indicar o final de
uma lista.) A introduo de estruturas e ponteiros cria dois problemas distintos para o
compilador: valores annimos e layout de estrutura.
Com esta informao, o compilador pode facilmente gerar cdigo para referncias de
estrutura. Retornando ao exemplo de lista, o compilador poderia traduzir a referncia
p1 > next, para um ponteiro para o n p1, no seguinte cdigo ILOC:
loadI
r1
//deslocamento de next
loadAO
rp1,r1
r2
// valor de p1->next
As duas primeiras linhas criam node s annimos. A linha 6 escreve em p1, enquanto
a linha 7, em p3 . Devido ao if-then-else, p3 pode se referir ao n alocado na
linha 1 ou na linha 2. Finalmente, a linha 8 referencia p1 > value.
O uso de ponteiros limita a capacidade do compilador de manter valores em registradores. Considere a sequncia de atribuies nas linhas de 6 a 8. A linha 8 reutiliza o
valor atribudo na linha 6 ou o valor atribudo na linha 7. Por uma questo de eficincia,
o compilador deve evitar armazenar esse valor na memria e recarreg-lo. Porm, ele
no pode determinar facilmente qual valor a linha 8 usa. A resposta para esta questo
depende do valor da expresso condicional na linha 3.
Embora possa ser possvel saber o valor da expresso condicional em certos casos
especficos (por exemplo, 1 > 2 ), isto indecidvel no caso geral. A menos que o
compilador saiba o valor da expresso condicional, dever emitir um cdigo conservador para as trs atribuies. Ele precisa carregar o valor usado na linha 8 a partir da
memria, ainda que recentemente tivesse o valor em um registrador.
A incerteza introduzida pelos ponteiros impede que o compilador mantenha valores
usados nas referncias baseadas em ponteiro nos registradores. Os objetos annimos
complicam ainda mais o problema, pois introduzem um conjunto ilimitado de objetos
para acompanhar. Como resultado, instrues que envolvem referncias baseadas em
REVISO DA SEO
Para implementar estruturas e arrays de estruturas, o compilador precisa estabelecer
um layout para cada estrutura e ter uma frmula para calcular o deslocamento de
qualquer elemento da estrutura. Em uma linguagem na qual as declaraes ditam a
posio relativa dos elementos de dados, o layout da estrutura simplesmente requer
que o compilador calcule deslocamentos. Se a linguagem permitir que o compilador
determine a posio relativa dos elementos de dados, ento o problema de layout
semelhante ao layout de rea de dados (ver Seo7.2.2). O clculo de endereo
para um elemento de estrutura uma aplicao simples dos esquemas usados para
variveis escalares (por exemplo, base+deslocamento) e para elementos de array.
Dois recursos relacionados a estruturas introduzem complicaes. Se a linguagem permitir unies ou estruturas variantes, o cdigo de entrada deve especificar o elemento
desejado de forma no ambgua. A soluo tpica para este problema o uso de nomes totalmente qualificados para elementos de estrutura em uma unio. A segunda
questo surge da alocao de estruturas em tempo de execuo. O uso de ponteiros
para manter endereos de objetos alocados dinamicamente introduz ambiguidades
que complicam a questo de quais valores podem ser mantidos em registradores.
QUESTES DE REVISO
1. Quando o compilador estabelece o layout de uma estrutura, precisa garantir que
cada elemento da estrutura esteja alinhado na fronteira apropriada. Ele pode ter
que inserir um preenchimento (espao em branco) entre elementos para atender
s restries de alinhamento. Escreva um conjunto de regras prticas que um
programador poderia usar para reduzir a probabilidade de preenchimento inserido
pelo compilador.
2. Se o compilador tiver liberdade para reajustar estruturas e arrays, s vezes pode
melhorar o desempenho. Quais recursos da linguagem de programao inibem a
capacidade do compilador de realizar esse rearranjo?
instruo rotulada, pois pode ser o destino de um desvio. medida que o compilador gera
cdigo, pode construir blocos bsicos simplesmente agregando operaes consecutivas,
no rotuladas, que no sejam de controle de fluxo. (Consideramos que uma instruo
rotulada no rotulada voluntariamente, ou seja, cada instruo rotulada o destino de
algum desvio.) A representao de um bloco bsico no precisa ser complexa. Por exemplo, se o compilador tiver uma representao tipo assembly mantida em um array linear
simples, ento o bloco pode ser descrito por um par, primeiro, ltimo, que mantm os
ndices da instruo que inicia o bloco e da instruo que o termina. (Se os ndices de bloco
forem armazenados em ordem numrica crescente, um array de primeiros ser suficiente.)
Para unir um conjunto de blocos de modo que formem um procedimento, o compilador
precisa inserir cdigo que implementa as operaes de fluxo de controle do programa
fonte. Para capturar os relacionamentos entre blocos, muitos compiladores constroem um grafo de fluxo de controle (CFG Control-Flow Graph, ver Sees 5.2.2
e 8.6.1) e o utilizam para anlise, otimizao e gerao de cdigo. No CFG, os ns
representam blocos bsicos e as arestas, possveis transferncias de controle entre
blocos. Normalmente, o CFG uma representao secundria que contm referncias
para uma representao mais detalhada de cada bloco.
O cdigo para implementar as construes de controle de fluxo reside nos blocos bsicos
no final de cada bloco ou perto dele. (Em ILOC, no existe o caso fall-through em um
desvio, de modo que cada bloco termina com um desvio ou um salto. Se a IR modelar
slots de atraso, ento a operao de controle de fluxo pode no ser a ltima no bloco.)
Embora muitas convenes sintticas diferentes tenham sido usadas para expressar o controle de fluxo, o nmero de conceitos bsicos pequeno. Esta seo examina muitas das
construes de controle de fluxo encontradas nas linguagens de programao modernas.
o compilador deve gerar cdigo que avalie expr e desvie para instruo1 ou instruo2,
com base no valor de expr. O cdigo ILOC que implementa as duas instrues deve
terminar com um salto para instruo3. Conforme vimos na Seo 7.4, o compilador
tem muitas opes para implementar construes if-then-else.
A discusso na Seo7.4 focalizou a avaliao da expresso de controle. E mostrou
como o conjunto de instrues bsico influencia as estratgias para tratar a expresso
de controle e, em alguns casos, as instrues controladas.
Os programadores podem colocar fragmentos de cdigo arbitrariamente grandes dentro
das partes then e else. O tamanho desses fragmentos de cdigo tem impacto sobre
a estratgia do compilador para implementar a construo if-then-else. Com as
partes then e else triviais, conforme mostra a Figura7.9, a principal considerao
para o compilador corresponder a avaliao da expresso ao hardware subjacente.
medida que essas partes then e else crescem, a importncia da execuo eficiente
dentro delas comea a superar o custo da execuo da expresso de controle.
Por exemplo, em uma mquina que aceita a execuo predicada, o uso de predicados
para grandes blocos nas partes then e else pode desperdiar ciclos de execuo.
Como o processador deve emitir cada instruo predicada para uma de suas unidades
funcionais, cada operao com um predicado falso tem um custo de oportunidade
prende um slot de emisso. Com blocos de cdigo grandes sob as partes then e
else, o custo de instrues no executadas pode superar o overhead do uso de um
desvio condicional.
A Figura7.14 ilustra este compromisso. Ela assume que as partes then e else contm 10
operaes ILOC independentes e que a mquina-alvo pode emitir duas operaes por ciclo.
A Figura7.14a mostra o cdigo que poderia ser gerado usando a predicao; ele considera que o valor da expresso de controle esteja em r1. O cdigo emite duas instrues
por ciclo. Uma delas executada em cada ciclo. Todas as operaes da parte then so
emitidas para a Unidade 1, enquanto as operaes da parte else so emitidas para a
Unidade 2. O cdigo evita toda a ramificao. Se cada operao usar um nico ciclo,
so necessrios 10 ciclos para executar as instrues controladas, independentemente
de qual desvio tomado.
A Figura7.14b mostra o cdigo que poderia ser gerado usando desvios; ele considera
que o controle flui para L1 para a parte then ou para L2 para a parte else. Como
as instrues so independentes, o cdigo emite duas instrues por ciclo. Seguir o
caminho then exige cinco ciclos para executar as operaes para o caminho tomado,
mais o custo do salto terminal. O custo para a parte else idntico.
A verso predicada evita o desvio inicial exigido no cdigo no predicado (para L1 ou
L2 na figura), bem como os saltos terminais (para L3). A verso de desvio incorre no
overhead de um desvio e um salto, mas pode ser executada mais rapidamente. Cada
caminho contm um desvio condicional, cinco ciclos de operaes e um salto terminal.
(Algumas das operaes podem ser usadas para preencher slots de atraso nos saltos.) A
diferena est na taxa de emisso efetiva a verso de desvio emite aproximadamente
metade das instrues da verso predicada. medida que os fragmentos de cdigo nas
partes then e else aumentam, essa diferena se torna maior.
A escolha entre ramificao e predicao para implementar um if-then-else
requer algum cuidado. Vrias questes devem ser consideradas, como a seguir:
1. Frequncia de execuo esperada. Se um lado da condicional for executado
com muito mais frequncia, as tcnicas que agilizam a execuo deste caminho podem produzir um cdigo mais rpido. Este vis pode tomar a forma de
predio de um desvio, de executar algumas instrues de forma especulativa ou
de reordenar a lgica.
2. Quantidades de cdigo desiguais. Se um caminho pela construo tiver muito
mais instrues do que o outro, isto pode pesar contra a predicao ou para uma
combinao de predicao e ramificao.
3. Fluxo de controle dentro da construo. Se qualquer caminho tiver fluxo de controle no trivial, como um if-then-else, lao, instruo case ou chamada,
ento a predicao pode ser uma escolha ruim. Em particular, construes
if aninhadas criam predicados complexos e reduzem a frao de operaes
emitidas que so teis.
Para tomar a melhor deciso, o compilador precisa considerar todos esses fatores,
bem como o contexto em que ocorrem. Esses fatores podem ser difceis de avaliar
cedo na compilao; por exemplo, a otimizao pode mud-los de maneiras significativas.
Laos for
Para mapear um lao for no cdigo, o compilador segue o esquema geral da Figura7.15. Para tornar isto mais concreto, considere o seguinte exemplo. As etapas 1 e 2
produzem um nico bloco bsico, como mostra o cdigo a seguir:
Lao do em FORTRAN
Em FORTRAN, o lao iterativo um lao do. semelhante ao lao for em C, mas
tem uma forma mais restrita.
Laos while
Um lao while tambm pode ser implementado com o esquema de lao da Figura7.15. Diferentemente do lao for da linguagem C ou do lao do do FORTRAN,
um while no tem inicializao. Assim, o cdigo ainda mais compacto.
Laos until
Um lao until se repete enquanto a expresso de controle for falsa. Ele verifica
esta expresso aps cada iterao. Assim, sempre entra no lao e realiza pelo menos
uma iterao, produzindo uma estrutura de lao particularmente simples, pois evita as
etapas 1 e 2 no esquema:
C no tem um lao until. Sua construo do semelhante a este lao, exceto que
o sentido da condio invertido. Ela repete enquanto a condio avaliada como
verdadeira, enquanto o untile repete enquanto a condio falsa.
A chamada de len para uma lista retorna o tamanho da lista. len baseia-se em count,
que implementa um contador simples usando chamadas de cauda.
Instrues break
Vrias linguagens implementam variaes de uma instruo break ou exit. A instruo break uma forma estruturada de sair de uma construo de controle de fluxo.
Em um lao, break transfere o controle para a primeira instruo aps o lao. Para
laos aninhados, break normalmente sai do lao mais interno. Algumas linguagens,
como Ada e Java, permitem um rtulo opcional em uma instruo break. Isto faz com
que a instruo break saia da construo delimitada especificada por este rtulo. Em
um lao aninhado, um break rotulado permite que o programa saia de vrios laos
ao mesmo tempo. C tambm usa break em sua instruo switch para transferir o
controle para a instruo que vem aps a instruo switch.
Essas aes tm implementaes simples. Cada lao e cada instruo case devem
terminar com um rtulo para a instruo que vem em seguida. Um break seria implementado como um salto imediato para este rtulo. Algumas linguagens incluem
uma instruo skip ou continue que salta para a prxima iterao de um lao.
Esta construo pode ser implementada como um salto imediato para o cdigo que
reavalia a expresso de controle e testa seu valor. Como alternativa, o compilador
pode simplesmente inserir uma cpia da avaliao, teste e desvio no ponto onde
ocorre o skip.
Busca linear
O modo mais simples de localizar o caso apropriado tratar a instruo case como
a especificao para um conjunto aninhado de instrues if-then-else. Por
exemplo, a instruo switch mostrada na Figura7.16a pode ser traduzida para
o aninhamento de instrues mostrado na Figura7.16b. Essa traduo preserva o
significado da instruo switch, mas torna o custo de alcanar casos individuais
dependente da ordem em que eles so escritos. Com uma estratgia de busca linear,
o compilador deve tentar ordenar os casos pela frequncia de execuo estimada.
Ainda assim, quando o nmero de casos pequeno digamos, trs ou quatro ,
esta estratgia pode ser eficiente.
Busca binria
medida que o nmero de casos aumenta, a eficincia da busca linear torna-se um
problema. De modo semelhante, quando o conjunto de rtulos torna-se menos denso
e menos compacto, o tamanho da tabela de saltos pode se tornar um problema para o
clculo direto de endereo. As solues clssicas que surgem na criao de uma busca
eficiente se aplicam a esta situao. Se o compilador pode impor uma ordem sobre os
rtulos de caso, pode usar a busca binria para obter uma busca logartmica, ao invs
de linear.
A ideia simples. O compilador constri uma tabela ordenada compacta de rtulos
de caso, junto com seus correspondentes rtulos de desvio. Usa a busca binria para
descobrir um rtulo de caso, ou a ausncia de uma correspondncia. E, finalmente,
desvia para o rtulo correspondente ou para o caso default.
REVISO DA SEO
As linguagens de programao incluem uma srie de recursos para implementar o
controle de fluxo. O compilador precisa de um esquema para cada construo de
controle de fluxo aceito nas linguagens-fonte. Em alguns casos, como em um lao, um
mtodo serve para uma srie de construes diferentes. Em outros, como em uma instruo case, o compilador deve escolher uma estratgia de implementao baseada
nas propriedades especficas do cdigo em mos.
QUESTES DE REVISO
1. Escreva o cdigo ILOC para o lao em FORTRAN mostrado na margem. Lembre-se
de que o corpo do lao deve executar 100 iteraes, embora o lao modifique o
valor de i.
2. Considere a escolha entre implementar uma instruo switch em C com clculo
direto de endereo e busca binria. Em que ponto o compilador deve passar do
clculo direto de endereo para a busca binria? Quais propriedades do cdigo
real devem desempenhar um papel nesta determinao?
Tabela de saltos
Vetor de rtulos usado para transferir o controle com
base em um ndice calculado para a tabela.
de entrada, cada um com sua prpria sequncia de prlogo.) Muitos dos detalhes
envolvidos nessas sequncias so descritos na Seo 6.5. Esta seo focaliza questes
que afetam a capacidade do compilador de gerar cdigo eficiente, compacto e consistente para chamadas de procedimento.
Em regra, mover operaes das sequncias de pr-chamada e ps-retorno para as de
prlogo e eplogo deve reduzir o tamanho geral do cdigo final. Se a chamada de p
para q apresentada na Figura7.19 for a nica chamada para q no programa inteiro,
ento mover uma operao da sequncia de pr-chamada em p para o prlogo em q
(ou da sequncia de ps-retorno em p para o eplogo em q) no tem impacto sobre o
tamanho do cdigo. Porm, se outros locais de chamada invocarem q e o compilador
move uma operao do chamador para o procedimento chamado (em todos os locais
de chamada), deve reduzir o tamanho geral do cdigo, substituindo vrias cpias de
uma operao por uma s. medida que o nmero de locais de chamada que invocam
um determinado procedimento aumenta, as economias aumentam. Consideramos que
a maioria dos procedimentos chamada a partir de vrios locais; se no, tanto o programador como o compilador devem considerar a incluso do procedimento em linha
no ponto de sua prpria chamada.
Do ponto de vista da forma de cdigo, chamadas de procedimento so semelhantes em linguagens tipo Algol e em linguagens orientadas a objeto. A principal
diferena entre elas est na tcnica usada para nomear o procedimento chamado
(ver Seo 6.3.4). Alm disso, uma chamada em uma linguagem orientada a objeto
normalmente acrescenta um parmetro real implcito, ou seja, o registro de objeto
do receptor.
que transfere o controle para label1 e coloca o endereo da operao que vem aps
o jsr em ri.
Procedimentos passados como parmetros reais podem exigir tratamento especial. Se p
chama q, passando o procedimento r como um argumento, p dever passar para q mais
informaes do que o endereo inicial de r. Em particular, se o cdigo compilado utiliza
links de acesso para encontrar variveis no locais, o procedimento chamado precisa do nvel
lxico de r para que uma chamada subsequente a r possa encontrar o link de acesso correto
para o nvel de r. O compilador pode construir um par endereo,nvel e pass-lo (ou seu
endereo) no lugar do parmetro com valor de procedimento. Quando o compilador constri
a sequncia de pr-chamada para um parmetro com valor de procedimento, precisa inserir
o cdigo extra para buscar o nvel lxico e ajustar o link de acesso de modo correspondente.
REVISO DA SEO
O cdigo gerado para chamadas de procedimento dividido entre o chamador e o
chamado, e entre as quatro partes da sequncia de ligao (prlogo, eplogo, pr-chamada e ps-retorno). O compilador coordena o cdigo nesses mltiplos locais
para implementar a conveno de ligao, conforme discutimos no Captulo6. As
regras da linguagem e as convenes de ligao de parmetro ditam a ordem de
avaliao e o estilo de avaliao para os parmetros reais. Convenes em nvel de sistema determinam a responsabilidade por salvar e restaurar registradores.
Os escritores de compilador devem prestar particular ateno implementao de
chamadas de procedimento, pois as oportunidades so difceis para as tcnicas de
otimizao gerais (ver Captulos8 e10). A natureza muitos-para-um do relacionamento chamador-chamado complica a anlise e a transformao, assim como a natureza
distribuda das sequncias de cdigo em cooperao. Igualmente importantes, os
pequenos desvios da conveno de ligao definida podem causar incompatibilidades no cdigo compilado com diferentes compiladores.
QUESTES DE REVISO
1. Quando um procedimento salva registradores, sejam salvamentos pelo procedimento chamado em seu prlogo ou de salvamentos pelo procedimento chamador em
uma sequncia de pr-chamada, onde ele deve salvar esses registradores? Todos os
registradores salvos para alguma chamada so armazenados no mesmo AR?
2. Em algumas situaes, o compilador deve criar um local de armazenamento
para manter o valor de um parmetro de chamada por referncia. Que tipos de
parmetros podem no ter seus prprios locais de armazenamento? Que aes
poderiam ser exigidas nas sequncias de pr-chamada e ps-chamada para lidar
corretamente com esses parmetros reais?
NOTAS DO CAPTULO
O material contido neste captulo pode ser dividido, grosseiramente, em duas categorias:
gerao de cdigo para expresses e tratamento de construes de controle de fluxo. A
avaliao de expresses bastante explorada na literatura. Discusses de como tratar
o controle de fluxo so mais raras; grande parte do material sobre fluxo de controle
neste captulo deriva do folclore, da experincia e da leitura cuidadosa da sada de
compiladores.
Floyd apresentou o primeiro algoritmo de passagem mltipla para gerao de cdigo a
partir de rvores de expresso [150]. Ele indica que tanto a eliminao da redundncia
quanto a reassociao algbrica tm potencial para melhorar os resultados de seu
algoritmo. Sethi e Ullman [311] propuseram um algoritmo de dois passos que timo
para um modelo de mquina simples; Proebsting e Fischer estenderam esse trabalho
para considerar pequenas latncias de memria [289]. Aho e Johnson [5] introduziram
a programao dinmica para encontrar implementaes de menor custo.
A predominncia de clculos de array em programas cientficos levou ao trabalho sobre
expresses de endereamento de array e a otimizaes (como a reduo de fora, Seo
10.7.2) que as melhoraram. Os clculos descritos na Seo7.5.3 seguem Scarborough
e Kolsky [307].
Harrison usou a manipulao de strings como exemplo motivador para o uso difuso
da substituio e especializao em linha [182]. O exemplo mencionado no final da
Seo7.6.4 vem deste artigo.
Mueller e Whalley descrevem o impacto das diferentes formas de lao sobre o desempenho [271]. Bernstein fornece uma discusso detalhada das opes que surgem
na gerao de cdigo para instrues case [40]. Convenes de chamada so melhor
descritas nos manuais especficos do processador e do sistema operacional.
EXERCCIOS
Seo 7.2
1. O layout de memria afeta os endereos atribudos a variveis. Suponha que as
variveis de caractere no tenham restrio de alinhamento, as variveis de inteiro
pequeno devam ser alinhadas em limites de meia palavra (2 bytes), as variveis
inteiras alinhadas em limites de palavra (4 bytes) e as variveis inteiras longas
alinhadas em limites de palavra dupla (8 bytes). Considere o seguinte conjunto de
declaraes:
Seo 7.4
7. Gere a ILOC predicada para a seguinte sequncia de cdigo. (Nenhum desvio
deve aparecer na soluo.)
8. Conforme mencionamos na Seo 7.4, o cdigo em curto-circuito para a expresso a seguir em C evita um potencial erro de diviso por zero:
Se a definio da linguagem-fonte no especificar a avaliao em curto-circuito para expresses com valor booleano, o compilador pode gerar cdigo
em curto-circuito como uma otimizao para tais expresses? Que problemas
poderiam surgir?
Seo 7.5
9. Para um array de caracteres A[10...12,1...3] armazenado em ordem por linhas,
calcule o endereo da referncia A[i, j], usando, no mximo, quatro operaes
aritmticas no cdigo gerado.
10. O que um vetor dopado? D o contedo do vetor dopado para o array de caracteres da questo anterior. Por que o compilador precisa de um vetor dopado?
11. Ao implementar um compilador C, pode ser aconselhvel fazer com que o
compilador realize verificao de limites para referncias de array. Considerando que as verificaes de limites sejam usadas e que todas as referncias de
array em um programa C tenham passado com sucesso por elas, possvel que
o programa acesse um armazenamento fora dos limites de um array, por exemplo, acessando A[1] para um array declarado com limite inferior zero e limite
superior N?
Seo 7.6
12. Considere o seguinte lao de cpia de caracteres da Seo 7.6.2:
Modifique o cdigo de modo que ele desvie para um tratador de erro em Lsov em
qualquer tentativa de estourar o tamanho alocado de a. Suponha que o tamanho
alocado de a esteja armazenado como um inteiro sem sinal de quatro bytes em
um deslocamento de 8 a partir do incio de a.
13. Atribuies arbitrrias de string podem gerar casos desalinhados.
a. Escreva o cdigo ILOC que gostaria que seu compilador emitisse para uma
atribuio de caracteres no estilo PL/I, como
onde j-i=l-k. Essa instruo copia os caracteres em fie, comeando
no local k e chegando at o local l na string fee, comeando no local i e
chegando at o local j.
Inclua verses usando operaes de memria orientadas a caractere e verses
usando operaes de memria orientadas a palavra. Voc pode considerar que
fee e fie no se sobreponham na memria.
b. O programador pode criar strings de caracteres que se sobreponham. Em
PL/I, o programador poderia escrever
ou, ainda mais diabolicamente,
Como isso complica o cdigo que o compilador precisa gerar para a atribuio de caracteres?
c. Existem otimizaes que o compilador poderia aplicar aos diversos laos de
cpia de caracteres que melhorariam o comportamento em runtime? Como
elas ajudariam?
Seo 7.7
14. Considere as seguintes declaraes de tipo em C:
Crie uma tabela de elementos de estrutura para S1. Inclua toda a informao que
um compilador precisaria para gerar referncias a elementos de uma varivel do
tipo S1, inserindo nome, tamanho, deslocamento e tipo de cada elemento.
15. Considere as seguintes declaraes em C:
Seo 7.8
16. Como programador, voc est interessado na eficincia do cdigo que produz.
Voc recentemente implementou, manualmente, um scanner, que gasta a maior
parte do seu tempo em um nico lao while que contm uma grande instruo
case.
a. Como as diferentes tcnicas de implementao de instruo case afetariam a
eficincia do seu scanner?
b. Como voc mudaria seu cdigo-fonte para melhorar o desempenho em
runtime sob cada uma das estratgias de implementao de instruo case?
17. Converta a funo com recurso de cauda em C para um lao:
Seo 7.9
18. Suponha que x seja uma varivel inteira no ambgua, local, e que x seja passada
como um parmetro real de chamada por referncia no procedimento onde
declarada. Por ser local e no ambgua, o compilador poderia tentar mant-la em
um registrador durante seu tempo de vida. Por ser passada como um parmetro
de chamada por referncia, ela precisa ter um endereo de memria no ponto da
chamada.
a. Onde o compilador deve armazenar x?
b. Como o compilador deve tratar x no local de chamada?
Captulo
Introduo otimizao
VISO GERAL DO CAPTULO
Para melhorar a qualidade do cdigo que gera, um compilador otimizador analisa o
cdigo e o reescreve para uma forma mais eficiente. Este captulo apresenta os problemas e as tcnicas da otimizao de cdigo, e mostra os principais conceitos por meio
de uma srie de exemplos. O Captulo9 expande este material com uma explorao
mais profunda da anlise do programa. O Captulo10 oferece uma cobertura mais
ampla das transformaes de otimizao.
Palavras-chave: Otimizao, Segurana, Lucratividade, Escopo de otimizao,
Anlise, Transformao
8.1INTRODUO
O front end do compilador traduz o programa em cdigo-fonte para alguma representao intermediria (IR Intermediate Representation). O back end traduz o
programa em IR para uma forma que possa ser executada diretamente na mquina
alvo, seja uma plataforma de hardware, como um microprocessador comercial, ou
uma mquina virtual, como em Java. Entre esses processos situa-se a seo do meio do
compilador, seu otimizador, cuja tarefa transformar o programa IR, produzido pelo
front end, de forma que melhore a qualidade do cdigo produzido pelo back end. A
melhoria pode assumir muitos significados. Frequentemente, isto implica execuo
mais rpida para o cdigo compilado. E tambm pode significar um executvel que
utiliza menos energia ou que ocupa menos espao na memria. Todos esses objetivos
esto no mbito da otimizao.
Este captulo introduz o assunto de otimizao de cdigo e fornece exemplos de vrias
tcnicas diferentes que atacam diferentes tipos de ineficincias e operam sobre diferentes regies do cdigo. O Captulo9 fornece um tratamento mais profundo de algumas
das tcnicas de anlise de programa que so usadas para dar suporte otimizao. O
Captulo10 descreve transformaes adicionais para melhoria do cdigo.
Roteiro conceitual
O objetivo da otimizao de cdigo descobrir, em tempo de compilao, informaes
sobre o comportamento de runtime do programa e usar esta informao para melhorar
o cdigo gerado pelo compilador. A melhoria pode assumir muitas formas. O objetivo
mais comum da otimizao fazer com que o cdigo compilado seja executado mais
rapidamente. Para algumas aplicaes, porm, o tamanho do cdigo compilado supera
sua velocidade de execuo; considere, por exemplo, uma aplicao que ser confinada
a uma memria somente de leitura, onde o tamanho do cdigo afeta o custo do sistema.
Outros objetivos incluem a reduo do custo de energia de execuo, melhoria da resposta do cdigo a eventos em tempo real ou a reduo do trfego total de memria.
Otimizadores usam muitas tcnicas diferentes para melhorar o cdigo. Uma discusso
apropriada da otimizao deve considerar as ineficincias que podem ser melhoradas
e as tcnicas propostas para faz-lo. Para cada fonte de ineficincia, o construtor de
343
compiladores deve fazer uma escolha dentre vrias tcnicas que afirmam melhorar a
eficincia. O restante desta seo ilustra alguns dos problemas que surgem na otimizao,
examinando dois exemplos que envolvem ineficincias em clculos de endereo de array.
Segurana
Uma transformao segura quando no muda os
resultados da execuo do programa.
Lucro
Uma transformao lucrativa para se aplicar em
algum ponto quando o resultado uma melhoria real.
Viso geral
As oportunidades para otimizao surgem de muitas fontes. Uma fonte importante
de ineficincia aparece da implementao de abstraes da linguagem-fonte. Como a
traduo do cdigo-fonte para IR um processo local ocorre sem muita anlise do
contexto ao redor , normalmente gera IR para lidar com o caso mais geral de cada
construo. Com conhecimento contextual, o otimizador frequentemente pode determinar que o cdigo no precisa dessa generalidade completa; quando isso acontece,
ele pode reescrever o cdigo de uma forma mais restrita e mais eficiente.
Uma segunda fonte significativa de oportunidade para o otimizador encontra-se na
mquina-alvo. Ele precisa entender, em detalhes, as propriedades do alvo que afetam
seu desempenho. Questes como o nmero de unidades funcionais e suas capacidades,
latncia e largura de banda para vrios nveis de hierarquia da memria, os diversos
modos de endereamento admitidos no conjunto de instrues e a disponibilidade de
operaes incomuns ou complexas afetam o tipo de cdigo que o compilador deve
gerar para determinada aplicao.
Historicamente, a maioria dos compiladores otimizadores tem focalizado a melhoria da
velocidade de execuo do cdigo compilado. A melhoria pode, porm, tomar outras
formas. Em algumas aplicaes, o tamanho do cdigo compilado to importante
quanto sua velocidade. Alguns exemplos incluem o cdigo que ser confinado em
memria somente de leitura, onde o tamanho uma restrio econmica, ou cdigo
que ser transmitido por um canal de comunicaes com largura de banda limitada
antes de ser executado, onde o tamanho tem um impacto direto sobre o tempo at o
trmino. A otimizao para essas aplicaes deve produzir cdigo que ocupe menos
espao. Em outros casos, o usurio pode querer otimizar para critrios como uso de
registrador, uso de memria, consumo de energia ou resposta a eventos em tempo real.
Otimizao um assunto vasto e detalhado, cujo estudo poderia preencher um ou mais
cursos (e livros) completos. Este captulo introduz o assunto e algumas das suas ideiascrticas, que desempenham um significativo papel nos Captulos11,12 e13. Os dois
captulos seguintes tratam mais profundamente da anlise e transformao de programas.
O Captulo9 apresenta uma viso geral da anlise esttica; descreve alguns dos problemas
de anlise que um compilador otimizador precisa resolver e apresenta tcnicas prticas que
tem sido usadas para solucion-los. J o Captulo10, examina as chamadas otimizaes
escalares intencionadas para um uniprocessador de uma forma mais sistemtica.
8.2FUNDAMENTOS
At o incio da dcada de 1980, muitos construtores de compilador consideravam a
otimizao como um recurso que deveria ser acrescentado ao compilador somente
depois que suas outras partes estivessem funcionando bem, o que levou a uma
8.2.1Exemplos
A fim de fornecer um foco para esta discusso, vamos comear examinando dois
exemplos em profundidade. O primeiro, um clculo simples de endereo de array
bidimensional, mostra o papel que o conhecimento e o contexto desempenham no tipo
de cdigo que o compilador pode produzir. O segundo, um aninhamento de lao a partir
da rotina dmxpy na biblioteca numrica LINPACK, de grande utilizao, fornece ideias
para o prprio processo de transformao e para os desafios que o cdigo transformado
pode apresentar ao compilador.
Reduo de fora
Uma transformao que reescreve uma srie de
operaes, por exemplo
i . c , ( i + 1). c , ..., ( i + k ). c
para uma srie equivalente
i1 , i2 , , ik ,
onde @n o endereo de runtime do primeiro elemento de m, lowi(m) e highi(m) so os limites inferior e superior, respectivamente, da i-sima dimenso de
m, e w o tamanho de um elemento de m. A capacidade do compilador de reduzir
o custo desse clculo depende diretamente de sua anlise do cdigo e do contexto
ao redor.
Se m for um array local com limites inferiores de um em cada dimenso e limites
superiores conhecidos, ento o compilador pode simplificar o clculo para:
Desenrolamento de lao
Replica o corpo do lao para iteraes distintas e ajusta
correspondentemente os clculos de ndice.
Segurana
Como o programador soube que essa transformao era segura? Ou, por que ele
acreditou que o cdigo transformado produziria os mesmos resultados do cdigo
original? Um exame mais atento do aninhamento de lao mostra que a nica interao
entre iteraes sucessivas ocorre por meio dos elementos de y.
DEFINIO DE SEGURANA
Exatido (correctness) o critrio isolado mais importante que um compilador precisa
atender o cdigo que ele produz precisa ter o mesmo significado do programa
de entrada. Toda vez que o otimizador aplica uma transformao, essa ao precisa
preservar a exatido da traduo.
Normalmente, significado definido como o comportamento observvel
do programa. Para um programa em lote (batch), este o estado da memria
aps ele terminar, junto com qualquer sada que ele gera. Se o programa termina,
os valores de todas as variveis visveis imediatamente antes que ele termine devero
ser os mesmos sob qualquer esquema de traduo. Para um programa interativo,
o comportamento mais complexo e difcil de capturar.
Plotkin formalizou esta noo como equivalncia observacional.
Para duas expresses, M e N, dizemos que M e N so empiricamente equivalentes se,
e somente se, em qualquer contexto C onde tanto M quanto N so fechadas (ou seja, no
possuem variveis livres), a avaliao de C[M] e C[N] ou produz resultados idnticos
ou nenhuma delas termina [286].
Assim, duas expresses so empiricamente equivalentes se seus impactos sobre
o ambiente visvel, externo, forem idnticos.
Na prtica, os compiladores usam uma noo mais simples e mais frouxa
de equivalncia do que a de Plotkin, a saber: se, em seu contexto real de programa,
duas expresses diferentes e e e produzem resultados idnticos, ento o compilador
pode substituir e por e. Esse padro lida somente com contextos que realmente
surgem no programa; moldar o cdigo ao contexto a essncia da otimizao. Ele no
menciona o que acontece quando um clculo sai errado ou diverge.
Na prtica, os compiladores cuidam de no introduzir divergncia o cdigo original
funcionaria corretamente, mas o otimizado tenta dividir por zero, ou entra em um lao
sem fim. O caso oposto, em que o cdigo original divergiria, mas o cdigo otimizado
no, raramente mencionado.
j
Lucratividade
Por que o programador achou que o desenrolamento do lao melhoraria o desempenho?
Ou, por que a transformao lucrativa? Diversos efeitos diferentes do desenrolamento
podem acelerar o cdigo.
j
Ligado memria
Lao no qual loads e stores usam mais ciclos do que a
computao considerado ligado memria.
Para determinar se um lao ligado memria,
preciso que se tenha conhecimento detalhado sobre o
lao e a mquina-alvo.
O nmero total de iteraes de lao reduzido por um fator de 16. Isto reduz as
operaes de overhead devido ao controle do lao: adies, comparaes, saltos
e desvios. Se o lao for executado com frequncia, essas economias podem ser
significativas.
j Este efeito poderia sugerir o desenrolamento por um fator ainda maior. Limites
de recurso finitos provavelmente ditaram a escolha de 16. Por exemplo, o lao
interno usa os mesmos 16 valores de x para todas as iteraes do lao interno.
Muitos processadores possuem apenas 32 registradores que podem manter um
nmero de ponto flutuante. Desenrolar por 32, a prxima potncia de dois,
criaria tantos desses valores invariantes de lao que poderiam no caber no
conjunto de registradores. Derram-los para a memria acrescentaria loads e
stores ao lao interno e desfaria os benefcios do desenrolamento.
j Os clculos de endereo de array contm trabalho duplicado. Considere o uso
de y(i). O cdigo original calculava o endereo de y(i) uma vez por multiplicao de x e m; o cdigo transformado o calcula uma vez a cada 16 multiplicaes. O cdigo desenrolado realiza 161 do trabalho para enderear y(i). As
16 referncias a m, e em menor grau x, tambm devem incluir partes comuns que
o lao pode calcular uma vez e reutilizar.
j O lao transformado realiza mais trabalho por operao de memria, onde trabalho exclui o overhead de implementar as abstraes de array e lao. O lao
original realizava duas operaes aritmticas para trs de memria, enquanto o
lao desenrolado realiza 32 operaes aritmticas para 18 de memria, supondo
que todos os valores x permaneam nos registradores. Assim, o lao desenrolado
teria menos chances de ser ligado memria. Ele tem aritmtica independente
suficiente para sobrepor os loads e ocultar algumas de suas latncias.
O desenrolamento pode ajudar com outros efeitos dependentes da mquina. Ele aumenta
a quantidade de cdigo no lao interno, que pode fornecer ao escalonador de instrues
mais oportunidades para ocultar latncias. Se o desvio de fim de lao tiver uma latncia
longa, o corpo de lao maior pode permitir que o compilador preencha mais dos slots
de atraso desse desvio. Em alguns processadores, slots de atraso no usados precisam
ser preenchidos com nops, caso em que o desenrolamento de lao pode diminuir o
nmero de nopsbuscados, reduzir o trfego de memria e, talvez, reduzir a energia
usada para executar o programa.
Risco
Se as transformaes voltadas em melhorar o desempenho tornarem mais difcil para o
compilador gerar um bom cdigo para o programa, esses potenciais problemas devem
ser considerados como questes de lucratividade. As transformaes manuais realizadas
sobre dmxpy criam novos desafios para um compilador, incluindo:
j
Forma do clculo de endereo. O lao original lida com trs endereos, um para
cada y, x e m. Como o lao transformado referencia muito mais locais distintos em cada iterao, o compilador precisa modelar os clculos de endereo
cuidadosamente para evitar clculos repetidos e demanda excessiva por registradores. No pior dos casos, o cdigo poderia usar clculos independentes para
todos os 16 elementos de x, todos os 16 de m e um elemento de y.
Se o compilador formatar os clculos de endereo de modo apropriado, pode
usar um nico ponteiro para m e outro para x, cada um com 16 deslocamentos de
valor constante. E pode reescrever o lao para usar esse ponteiro no teste de fim
de lao, evitando a necessidade de outro registrador e eliminando outra atualizao. Planejamento e otimizao fazem a diferena.
QUESTES DE REVISO
1. No fragmento de cdigo de dmxpy da biblioteca LINPACK, por que o programador
escolhe desdobrar o lao externo ao invs do interno? Como voc acha que
os resultados seriam diferentes se ele tivesse desdobrado o lao interno?
2. No fragmento de cdigo C a seguir, que fatos o compilador precisaria descobrir
antes que pudesse melhorar o cdigo alm de uma simples implementao
load/store orientada a bytes?
Escopo de otimizao
A regio de cdigo onde uma otimizao opera o seu
escopo de otimizao.
Mtodos locais
Mtodos locais operam sobre um nico bloco bsico: uma sequncia de mximo
tamanho de cdigo livre de desvio. Em um programa ILOC, o bloco bsico
comea com uma operao rotulada e termina com um desvio ou um salto. Em
ILOC, a operao aps um desvio ou salto precisa ser rotulada, ou no poder
ser alcanada; outras notaes permitem um desvio fall-through, de modo que
a operao aps um desvio ou salto no precisa ser rotulada. O comportamento
do cdigo em linha reta mais fcil de analisar e entender do que o cdigo que
contm desvios e ciclos.
Dentro de um bloco bsico, duas propriedades importantes so mantidas. Primeiro, as
instrues so executadas em sequncia. Segundo, se qualquer instruo for executada,
o bloco inteiro executado, a menos que ocorra uma exceo em runtime. Essas duas
propriedades permitem que o compilador prove, com anlises relativamente simples,
fatos que podem ser mais fortes do que aqueles demonstrveis para escopos maiores.
Assim, os mtodos locais s vezes fazem melhorias que simplesmente no podem
ser obtidas para escopos maiores. Ao mesmo tempo, so limitados a melhorias que
envolvem operaes que ocorrem todas no mesmo bloco.
Mtodos regionais
Mtodos regionais operam sobre escopos maiores do que um nico bloco, porm
menores que um procedimento inteiro. No exemplo de grafo de fluxo de controle (CFG)
na margem deste pargrafo, o compilador poderia considerar o lao inteiro, {B0, B1, B2,
B3, B4, B5, B6}, como uma nica regio. Em alguns casos, considerar um subconjunto
do cdigo para o procedimento inteiro produz anlise mais ntida, e melhores resultados
de transformao do que ocorreria com informaes do procedimento inteiro. Por
exemplo, dentro de um aninhamento de lao, o compilador pode ser capaz de provar
que um ponteiro muito utilizado invariante (valor nico), embora seja modificado
em outro lugar no procedimento. Esse conhecimento pode permitir otimizaes, como
manter em um registrador o valor referenciado por meio desse ponteiro.
O compilador pode escolher regies de muitas e diferentes maneiras. Uma regio pode
ser definida por alguma estrutura de controle de cdigo-fonte como um aninhamento de
lao. O compilador poderia examinar o subconjunto de blocos na regio que forma um
bloco bsico estendido (EBB Extended Basic Block). O CFG de exemplo contm
trs EBBs: {B0, B1, B2, B3, B4}, {B5} e {B6}. Embora os dois EBBs de bloco nico no
ofeream vantagem em relao a uma viso puramente local, o EBB grande pode oferecer
oportunidades para otimizao (ver Seo 8.5.1). Finalmente, o compilador poderia considerar um subconjunto do CFG definido por alguma propriedade terica do grafo, como
uma relao de dominncia ou um dos componentes fortemente conectados no CFG.
Mtodos regionais tm vrios pontos fortes. A limitao do escopo de uma transformao para uma regio menor que o procedimento inteiro permite que o compilador
foque seus esforos em regies bastante executadas por exemplo, o corpo de um
lao normalmente executado com muito mais frequncia do que o cdigo em volta.
O compilador pode aplicar diferentes estratgias de otimizao para regies distintas.
Finalmente, o foco em uma rea limitada no cdigo com frequncia permite que o
compilador obtenha informaes mais ntidas sobre o comportamento do programa,
que, por sua vez, expe oportunidades para melhoria.
Mtodos globais
Estes mtodos, tambm chamados mtodos intraprocedimentais, utilizam um procedimento inteiro como contexto. A motivao para os mtodos globais simples: decises
que so localmente timas podem ter consequncias ruins em algum contexto maior. O
procedimento oferece ao compilador uma fronteira natural para anlise e transformao.
Os procedimentos so abstraes que encapsulam e isolam ambientes de runtime. Ao
mesmo tempo, servem como unidades de compilao separada em muitos sistemas.
Mtodos globais normalmente operam criando uma representao do procedimento, como
um CFG, analisando-a e transformando o cdigo subjacente. Se o CFG puder ter ciclos, o
compilador deve analisar o procedimento inteiro para entender quais fatos so mantidos na
entrada de qualquer bloco especfico. Assim, a maior parte das transformaes globais possui
fases separadas para anlise e transformao. A fase analtica colhe fatos e motivos a respeito
deles. A fase de transformao usa esses fatos para determinar a segurana e a lucratividade
de uma transformao especfica. Em virtude de sua viso global, esses mtodos podem
descobrir oportunidades que nem os mtodos locais nem os regionais podem descobrir.
INTRAPROCEDIMENTAL VERSUS INTERPROCEDIMENTAL
Poucos termos na compilao criam tanta confuso quanto a palavra global. A anlise
e a otimizao global operam sobre um procedimento inteiro. Porm, a conotao
moderna do termo global sugere um escopo mais abrangente, como no uso deste
termo nas discusses sobre regras de escopo lxico. Porm, em anlise e otimizao,
global significa pertencer a um nico procedimento.
O interesse em anlise e otimizao entre fronteiras de procedimento exigiu uma
terminologia para diferenciar entre anlise global e anlise sobre escopos maiores.
O termo interprocedimental foi introduzido para descrever a anlise que abrangia
de dois procedimentos a um programa inteiro. Por conseguinte, os autores
comearam a usar intraprocedimental para as tcnicas que abrangem um nico
procedimento. Como essas palavras so muito prximas em ortografia e pronncia,
so passveis de confuso e, provavelmente, inadequadas para se usar.
A Perkin-Elmer Corporation tentou remediar essa confuso quando introduziu seu
compilador otimizador universal FORTRAN VIIZ para o PE 3200; o sistema realizava
expanses em linha extensivamente seguido por otimizao global agressiva
no cdigo resultante. Universal no pegou. Preferimos o termo programa inteiro
e o usamos sempre que possvel, porque transmite a distino correta e lembra
ao leitor e ao ouvinte que global no universal.
Mtodos interprocedimentais
Estes mtodos, s vezes chamados mtodos de programa inteiro, consideram escopos
maiores do que um nico procedimento. Consideramos qualquer transformao que
envolva mais de um procedimento como sendo interprocedimental. Assim como mover
de um escopo local para um global expe novas oportunidades, mover de procedimentos
nicos para mltiplos pode mostrar novas oportunidades. Mas tambm levanta novos
desafios. Por exemplo, as regras de vinculao de parmetros introduzem complicaes
significativas para a anlise que d suporte otimizao.
A anlise e a otimizao interprocedimentais ocorrem, pelo menos em conceito, no
grafo de chamada do programa. Em alguns casos, essas tcnicas analisam o programa
inteiro; em outros, o compilador pode examinar apenas um subconjunto do cdigofonte. Dois exemplos clssicos de otimizaes interprocedimentais so a substituio
em linha, que substitui uma chamada de procedimento por uma cpia do corpo do
procedimento chamado, e a propagao de constante interprocedimental, que propaga
e cruza informaes sobre constantes ao longo do programa inteiro.
REVISO DA SEO
Os compiladores realizam anlise e transformao sobre uma srie de escopos, variando desde blocos bsicos isolados (mtodos locais) at programas inteiros (mtodos
de programa inteiro). Em geral, o nmero de oportunidades para melhoria aumenta
com o escopo da otimizao. Porm, a anlise de escopos maiores frequentemente
resulta em um conhecimento menos preciso sobre o comportamento do cdigo.
Assim, no existe relacionamento simples entre escopo de otimizao e qualidade
do cdigo resultante. Seria intelectualmente atraente se um escopo de otimizao
maior levasse, em geral, melhor qualidade de cdigo. Infelizmente, esse
relacionamento no necessariamente verdadeiro.
QUESTES DE REVISO
1. Os blocos bsicos tm a propriedade de que, se uma instruo for executada, todas as instrues no bloco so executadas, em uma ordem especificada (a menos
que haja uma exceo). Indique a propriedade mais fraca que se mantm para um
bloco em um bloco bsico estendido, exceto o bloco de entrada, como o bloco B2
no EBB {B0, B1, B2, B3, B4}, para o grafo de fluxo de controle mostrado na margem.
2. Que tipos de melhoria o compilador poderia encontrar com a compilao
do programa inteiro? Cite algumas ineficincias que s podem ser resolvidas
examinando o cdigo entre limites de procedimento. Como a otimizao interprocedimental interage com o desejo de compilar procedimentos separadamente?
Redundante
Uma expresso e redundante em p se j tiver sido
avaliada em cada caminho que leva a p.
Esta seo apresenta dois mtodos locais como exemplos. O primeiro, numerao de
valor, encontra expresses redundantes em um bloco bsico e substitui as avaliaes
redundantes pelo reso de um valor previamente calculado. O segundo, balanceamento
de altura de rvore, reorganiza rvores de expresso para expor mais paralelismo em
nvel de instruo.
O algoritmo
A ideia por trs da numerao de valor simples. O algoritmo atravessa um bloco bsico
e atribui um nmero distinto a cada valor que ele calcula; e escolhe os nmeros de modo
que duas expresses, ei e ej, tenham o mesmo nmero de valor se, e somente se, ei e ej
tiverem valores provadamente iguais para todos os operandos possveis das expresses.
Tempo de vida
O tempo de vida de um nome a regio de cdigo
entre suas definies e seus usos. Aqui, a definio
significa atribuio.
Para ver como a LVN funciona, considere nosso exemplo original de bloco, apresentado
na pgina 365. A verso na margem mostra os nmeros de valor que ela atribui como
sobrescritos. Na primeira operao, com uma tabela de valores vazia, b e c obtm
novos nmeros de valor, 0 e 1 respectivamente. A LVN constri a string textual 0+1
como uma chave hash para a expresso b+c e realiza uma pesquisa. No encontra
uma entrada para essa chave, de modo que a pesquisa falha. Em consequncia, a LVN
cria uma nova entrada para 0+1 e atribui a ela o nmero de valor 2. Depois, cria
uma entrada para a e lhe atribui o nmero de valor da expresso; ou seja, 2. Repetindo
esse processo para cada operao, em ordem sequencial, a LVN produz o restante dos
nmeros de valor mostrados na margem.
Os nmeros de valor revelam, corretamente, que as duas ocorrncias b+c produzem
valores diferentes, devido redefinio intermediria de b. Por outro lado, as duas
ocorrncias de ad produzem o mesmo valor, pois tm os mesmos nmeros de valor
de entrada e o mesmo operador. A LVN descobre e registra isso atribuindo a b e d o
mesmo nmero de valor; a saber, 4. Esse conhecimento permite que a LVN reescreva
a quarta operao como db, como mostramos na margem. Os passos subsequentes
podem eliminar a cpia.
Estendendo o algoritmo
A LVN fornece um framework natural para realizar diversas outras otimizaes
locais.
j
NaN
Not a Number, uma constante definida que representa
um resultado invlido ou sem significado no padro
IEEE para aritmtica de ponto flutuante.
O papel da nomeao
A escolha de nomes para variveis e valores pode limitar a eficcia da numerao
de valor. Considere o que acontece quando a LVN aplicada ao bloco mostrado na
margem. Novamente, os sobrescritos indicam os nmeros de valor atribudos a cada
nome e valor.
Na primeira operao, a LVN atribui 1 a x, 2 a y e 3 a x+y e a a. Na segunda, descobre
que x+y redundante, com nmero de valor 3. De modo correspondente, reescreve
bx+y como ba. A terceira operao simples e no redundante. Na quarta
operao, ela novamente descobre que x+y redundante, com nmero de valor 3. Entretanto, no pode reescrev-la como ca, pois a no tem mais o nmero de valor 3.
Podemos tratar esse problema de duas maneiras distintas. Modificar a LVN de modo
que mantenha um mapeamento dos nmeros de valor para nomes. Em uma atribuio
a algum nome, digamos, a, ela deve remover a da lista para seu nmero de valor antigo
e acrescentar a lista para seu novo nmero de valor. Depois, em uma substituio,
pode usar qualquer nome que contenha atualmente esse nmero de valor. Essa tcnica
acrescenta algum custo ao processamento de cada atribuio e confunde o cdigo para
o algoritmo bsico.
Como alternativa, o compilador pode reescrever o cdigo de modo que d um nome
distinto a cada atribuio. Acrescentar um subscrito a cada nome, para que haja exclusividade, como vemos na margem, suficiente. Com esses novos nomes, o cdigo
define cada valor exatamente uma vez. Assim, nenhum valor redefinido ou perdido, ou
morto. Se aplicarmos a LVN a esse bloco, isto produz o resultado desejado. Isso prova
que a segunda e a quarta operaes so redundantes; cada uma pode ser substituda
por uma cpia de a0.
rvore balanceada pode ser executada em quatro ciclos, com uma unidade ociosa no
quarto ciclo.
Ao contrrio, a rvore associativa esquerda exige sete ciclos, deixando o segundo
somador ocioso durante a computao. Esta forma de rvore fora o compilador a
serializar as adies. A rvore associativa direita produzir efeito semelhante.
Esse pequeno exemplo sugere uma otimizao importante: usar as leis comutativa e
associativa da aritmtica para expor paralelismo adicional na avaliao de expresso.
O restante desta seo apresenta um algoritmo para reescrever cdigo a fim de criar
expresses cuja forma de rvore se aproxima de uma rvore balanceada. Essa transformao em particular visa melhorar o tempo de execuo, expondo mais operaes
concorrentes, ou paralelismo em nvel de instruo, ao escalonador de instrues do
compilador.
Para formalizar essas noes em um algoritmo, seguiremos um esquema simples.
1. O algoritmo identifica rvores de expresso candidatas no bloco. Todos os operadores em uma rvore candidata precisam ser idnticos, e tambm comutativos
e associativos. Igualmente importante, cada nome que rotule um n interior da
rvore candidata precisa ser usado exatamente uma vez.
2. Para cada rvore candidata, o algoritmo encontra todos os seus operandos,
atribui-lhes uma classificao e os inclui em uma fila de prioridade, ordenada
por classificao crescente. A partir dessa fila, o algoritmo ento reconstri uma
rvore que assemelha-se a uma binria balanceada.
Esse esquema de duas fases, anlise seguida pela transformao, comum na otimizao.
Valor observvel
Um valor observvel, com relao a um fragmento
de cdigo (bloco, lao etc.), se for lido fora desse
fragmento.
Exemplos
Considere o que acontece quando aplicamos o algoritmo ao nosso exemplo original
na Figura8.5. Suponha que t7 esteja vivo na sada do bloco, que t1 a t6 no estejam e
Enqueue insira antes do primeiro elemento de mesma prioridade. Neste caso, a fase 1
encontra uma nica raiz, t7, e a 2 chama Balance sobre t7. Balance, por sua vez,
chama Flatten seguido por Rebuild. Flatten monta a fila:
Rebuild retira da fila h,1 e g,1, emite n0h+g e coloca n0,2 na fila.
Em seguida, retira da fila f,1 e e,1, emite n1f+e e coloca n1,2 na fila.
Retira da fila d,1 e c,1, emite n2d+c e coloca n2,2 na fila. Depois, retira
da fila b, 1 e a, 1, emite n3b+a e coloca n3, 2 na fila.
Neste ponto, Rebuild ter produzido somas parciais com todos os oito valores originais.
A fila agora contm {n3, 2,n2, 2,n1, 2,n0, 2}. A prxima iterao retira n3, 2 e
n2, 2, emite n4n3+n2 e coloca n4, 4 na fila. Em seguida, retira n1, 2 e n0,
2 da fila, emite n5n1+n0 e coloca n5, 4 na fila, A iterao final retira n5, 4
e n4, 4 da fila e emite t7n5+n4. A sequncia de cdigo completa, mostrada na
margem, corresponde rvore balanceada mostrada na Figura8.5c; o cdigo resultante
pode ser escalonado como no lado esquerdo da Figura8.6.
Como segundo exemplo, considere o bloco bsico mostrado na Figura8.9a. Esse
cdigo poderia resultar da numerao de valor local; constantes foram desdobradas
e computaes redundantes eliminadas. O bloco contm vrias computaes entrelaadas. A Figura8.9b mostra as rvores de expresso no bloco. Observe que t3 e t7
so reutilizados por nome. A cadeia de computao de caminho mais longo a rvore
encabeada por t6 que possui seis operaes.
Quando aplicamos a fase 1 do algoritmo de balanceamento de altura de rvore ao
bloco na Figura8.9, ela encontra cinco razes, mostradas em caixas na Figura8.9c.
Marca t3 e t7 porque possuem mltiplos usos. E marca t6, t10 e t11 porque esto em
LIVEOUT(b). Ao final da fase 1, a fila de prioridade Roots contm:
REVISO DA SEO
A otimizao local opera sobre o cdigo para um nico bloco bsico. As tcnicas
contam com as informaes disponveis no bloco para reescrev-lo. No processo,
elas precisam manter as interaes do bloco com o contexto de execuo ao redor.
Em particular, precisam preservar quaisquer valores observveis calculados no bloco.
Por limitarem seu escopo a um nico bloco, as otimizaes locais podem contar
com propriedades que s so verdadeiras no cdigo em linha reta. Por exemplo,
a numerao de valor local conta com o fato de que todas as operaes no bloco
so executadas em uma ordem consistente com a execuo em linha reta. Assim,
ela pode construir um modelo de contexto anterior que expe redundncias e expresses com valor constante. De modo semelhante, o balanceamento de altura de rvore
conta com o fato de que um bloco tem apenas uma sada para determinar quais
subexpresses no bloco precisa preservar e quais ele pode rearranjar.
QUESTES DE REVISO
1. Esboce um algoritmo para encontrar os blocos bsicos em um procedimento
expresso em ILOC. Que estruturas de dados voc poderia usar para representar
o bloco bsico?
2. O algoritmo de balanceamento de altura de rvore dado nas Figuras8.7 e8.8
classifica um n N na rvore de expresso final com o nmero de folhas no
constantes abaixo dele na rvore final. Como voc modificaria o algoritmo
para produzir classificaes que correspondam altura de N na rvore? Isso
mudaria o cdigo que o algoritmo produz?
Para evitar essa complicao, o compilador pode executar a SVN em uma representao
que define cada nome uma vez. Como vimos na Seo 5.4.2, a forma SSA tem a propriedade de requisito; cada nome definido exatamente em um ponto no cdigo. O uso
da forma SSA garante que a SVN registra o nmero de valor para uma definio na
tabela que corresponde ao bloco que contm a definio. Com a forma SSA, a excluso
da tabela para um bloco desfaz todos os seus efeitos e reverte a tabela de valor ao seu
estado na sada do predecessor CFG do bloco. Conforme discutimos na Seo 8.4.1,
o uso da forma SSA tambm pode tornar a LVN mais eficiente.
A aplicao do algoritmo da Figura8.12 ao cdigo da Figura8.11a produz a sequncia
de aes apresentada na Figura8.11d. Ele comea com B0 e prossegue at B1. Ao final
de B1, visita B6, observa que B6 tem vrios predecessores e o acrescenta worklist.
Em seguida, recua e processa B2 e depois B3. Ao final de B3, acrescenta B5 worklist.
Depois, recua para B2 e processa B4. Nesse ponto, o controle retorna ao lao while, que
invoca a SVN para os dois blocos simples da worklist, B5 e B6.
Em termos de eficincia, a SVN descobre e remove computaes redundantes que
a LVN no consegue. Conforme j mencionamos na seo, a SVN descobre que as
atribuies a q0, s0 e t0 so redundantes por causa das definies em blocos anteriores.
A LVN, com seu escopo puramente local, no consegue encontrar essas redundncias.
Por outro lado, a SVN tem suas prprias limitaes. Ela no consegue encontrar
redundncias em B5 e B6. O leitor pode ver, por inspeo, que cada atribuio nesses
dois blocos redundante. Como eles tm vrios predecessores, a SVN no pode transportar o contexto para eles. Assim, perde essas oportunidades; para aproveit-las,
precisamos de um algoritmo que possa considerar uma quantidade maior de contexto.
que controla o nmero de iteraes realizadas. Para ver isso, considere o aninhamento
de lao de dmxpy usado como exemplo na Seo 8.2.
O compilador pode desenrolar o lao interno ou o externo. O resultado do desenrolamento do lao interno aparece na Figura8.13a. O desenrolamento do lao
externo produz quatro laos internos; se o compilador combinar esses corpos de
lao interno transformao chamada fuso de lao , produzir um cdigo
semelhante ao que aparece na Figura8.13b. A combinao do desenrolamento
do lao externo e a subsequente fuso dos laos internos normalmente chamada
desenrolar-e-comprimir.
Fuso de lao
O processo de combinar dois corpos de lao
em um chamado fuso.
A fuso segura quando cada definio e cada uso
no lao resultante tm o mesmo valor que tinham
nos laos originais.
REVISO DA SEO
As otimizaes que se concentram em regies maiores do que o bloco e menores
do que um procedimento inteiro podem fornecer desempenho melhorado custa
de um aumento modesto no tempo de compilao. Para algumas transformaes,
a anlise necessria para dar suporte transformao e o impacto que isso tem sobre
o cdigo compilado so ambos limitados em escopo.
As transformaes superlocais tm uma rica histria na literatura e na prtica
da otimizao de cdigo. Muitas transformaes locais adaptam-se fcil e eficientemente aos blocos bsicos estendidos. As extenses superlocais para escalonamento
de instrues tm sido um componente importante dos compiladores otimizadores
por muitos anos (ver Seo 12.4).
As otimizaes baseadas em lao, como o desenrolamento, podem produzir melhorias significativas, principalmente porque muitos programas gastam uma frao
significativa de seu tempo de execuo dentro de laos. Esse simples fato torna os
laos e aninhamentos de lao alvos interessantes para anlise e transformao.
As melhorias feitas dentro de um lao tm impacto muito maior do que aquelas
feitas no cdigo fora de todos os aninhamentos de lao. Um mtodo regional
para a otimizao de lao faz sentido porque diferentes aninhamentos de lao
podem ter caractersticas de desempenho radicalmente diferentes. Assim, a otimizao de lao tem sido foco importante da pesquisa em otimizao
h dcadas.
QUESTES DE REVISO
1. A numerao de valor superlocal estende a numerao de valor local para blocos
bsicos estendidos por meio do uso inteligente de uma tabela hash com escopo.
Considere os problemas que poderiam surgir na extenso do algoritmo
de balanceamento de altura de rvore para um escopo superlocal.
a. Como voc lidaria com um nico caminho por meio de um EBB, como (B0, B2, B3),
no grafo de fluxo de controle mostrado na margem desta pgina?
b. Que complicaes surgem quando o algoritmo tenta processar (B0, B2, B4)
depois de processar (B0, B2, B3)?
2. O fragmento de cdigo a seguir calcula uma mdia dos trs ltimos anos:
Uma varivel, v, est viva na entrada de m quando se atende uma de duas condies.
Ela pode ser referenciada em m antes de ser redefinida em m, quando v UEVAR(m).
Pode estar viva na sada de m e passar ilesa por m porque m no a redefine, quando v
LIVEOUT(m) VARKILL(m). A combinao desses dois conjuntos, com , d a contribuio necessria de m para LIVEOUT(n). Para calcular LIVEOUT(n), o analisador
combina as contribuies de todos os sucessores de n indicados como succ(n).
rpido inverte essa deciso. Se o desvio fall-through for mais rpido que o desvio
tomado, ento o layout rpido usa o desvio mais rpido com 100 vezes mais frequncia.
O compilador pode tirar proveito dos custos de desvio assimtricos. Se ele souber as
frequncias relativas de execuo esperadas dos desvios em um procedimento, poder
selecionar um layout de cdigo que melhore o desempenho de runtime.
Para realizar o posicionamento de cdigo global, o compilador reordena os blocos
bsicos de um procedimento para otimizar o uso dos desvios fall-through. E segue dois
princpios. Primeiro, ele deve fazer com que os caminhos de execuo mais provveis
usem desvios fall-through. Assim, sempre que possvel, o bloco dever ser seguido
imediatamente por seu sucessor mais frequente. Segundo, o compilador deve mover
o cdigo que executado com pouca frequncia para o final do procedimento. Juntos,
esses princpios produzem sequncias maiores, que so executadas sem um desvio disruptivo, ou seja, que rompe a execuo em linha reta (por exemplo, um desvio tomado).
Esperamos dois efeitos benficos dessa ordem de execuo. O cdigo deve executar
uma proporo maior de desvios fall-through, o que pode melhorar diretamente o
desempenho. Esse padro deve levar ao uso mais eficiente da cache de instrues.
O posicionamento de cdigo, como a maioria das otimizaes de escopo global, tem
fases separadas de anlise e transformao. A fase de anlise precisa colher estimativas da
frequncia relativa de execuo de cada desvio. A transformao usa essas frequncias de
desvio, expressas como pesos nas arestas do CFG, para construir um modelo dos caminhos
executados frequentemente. Depois, ordena-se os blocos bsicos a partir desse modelo.
Em seguida, ele percorre as arestas do CFG e constri cadeias que modelam os caminhos quentes. Ele apanha as arestas na ordem de frequncia de execuo, com as mais
usadas primeiro. Para uma aresta, x,y, o algoritmo mescla a cadeia contendo x com a
cadeia contendo y se, e somente se, x for o ltimo n em sua cadeia e y for o primeiro
de sua cadeia. Se uma dessas condies no for verdadeira, ele deixa as cadeias que
contm x e y intactas.
Se mesclar as cadeias para x e y, o algoritmo dever atribuir nova cadeia uma prioridade apropriada. Ele calcula essa prioridade como o mnimo das prioridades das
cadeias para x e y. Se tanto x quanto y forem cadeias degeneradas com sua alta prioridade inicial, ele define a prioridade da nova cadeia como o nmero ordinal de mesclagens que o algoritmo realizou, indicado como P. Esse valor coloca a cadeia atrs das
cadeias construdas a partir de arestas de frequncia mais alta e frente daquelas construdas a partir de arestas de frequncia mais baixa.
O algoritmo termina depois de examinar cada aresta, e produz um conjunto de cadeias
que modelam os caminhos quentes no CFG. Cada n pertence a exatamente uma cadeia.
As arestas nas cadeias so executadas com mais frequncia do que as que cruzam de
uma cadeia para outra. Os valores de prioridade de cada cadeia codificam uma ordem
para o layout relativo das cadeias, que se aproxima do nmero mximo de desvios
executados para a frente.
Para ilustrar a operao do algoritmo, considere seu comportamento quando aplicado
ao CFG de exemplo da seo anterior, repetido na margem. O algoritmo prossegue da
seguinte forma:
Aresta
Conjunto de cadeias
(B0, B1)
(B3, B5)
(B4, B5)
(B1, B3)
(B0, B2)
(B2, B4)
(B1, B4)
0
1
2
2
3
3
4
4
O algoritmo representa uma cadeia com um par (c, p), onde c o nome da cadeia e
p sua prioridade. Por questo de eficincia, o teste que evita posicionar uma cadeia
na worklist duas vezes pode ser eliminado se implementarmos a worklist com um
conjunto esparso (ver Apndice B.2.3). A tabela a seguir mostra o comportamento
do algoritmo sobre o primeiro conjunto de cadeias produzido para o CFG de
exemplo:
Etapa
WorkList
Layout de cdigo
1
2
A primeira linha mostra o estado inicial. Ela coloca a cadeia que contm B0 na worklist.
A primeira iterao do lao while posiciona todos os blocos dessa cadeia. Enquanto processa as arestas que saem dos blocos posicionados, acrescenta a outra cadeia, (B2, B4),
na worklist. A segunda iterao posiciona esses dois blocos; e no acrescenta nada
worklist, de modo que o algoritmo termina.
Observamos que uma mudana no desempate poderia produzir uma mudana no
conjunto de cadeias produzido para o exemplo. Colocar a aresta (B4, B5) antes de (B3,
B5) produziu as cadeias (B0, B1, B3)0 e (B2, B4, B5)1. Trabalhando a partir dessas cadeias,
o algoritmo de layout de cdigo comporta-se da seguinte forma:
Etapa
WorkList
1
2
Layout de cdigo
B0, B1, B3
B0, B1, B3, B2, B4, B5
Um exemplo final
Considere como o algoritmo de posicionamento de cdigo global trata o CFG apresentado na margem. O algoritmo de construo de cadeia prossegue da seguinte
forma:
Aresta
Conjunto de cadeias
(B3, B4)
(B0, B3)
(B2, B4)
(B0, B2)
(B1, B3)
(B0, B1)
10
1
2
2
2
2
2
Neste grafo, o algoritmo termina com uma cadeia de mltiplos ns e duas cadeias
degeneradas, ambas com prioridade alta inicial.
j O algoritmo de layout primeiro posiciona (B0, B3, B4). Ao examinar as arestas
de sada dos ns posicionados, acrescenta ambos os blocos degenerados na
worklist. As duas iteraes seguintes removem os blocos degenerados, numa
ordem qualquer, e os posiciona. No h motivo para preferir uma ou outra
ordem.
REVISO DA SEO
As otimizaes que examinam um procedimento inteiro tm oportunidades
para melhoria que no esto disponveis em escopos menores. Como o escopo
global, ou em nvel de procedimento, inclui caminhos cclicos e desvios para trs,
as otimizaes globais normalmente precisam de anlise global. Como consequncia,
esses algoritmos tm um estilo off-line; eles consistem em uma fase de anlise
seguida por uma fase de transformao.
Esta seo destacou dois tipos distintos de anlise: a anlise de fluxo de dados global
e a coleta de dados de perfil em runtime. A primeira uma tcnica em tempo de
compilao que considera, matematicamente, os efeitos ao longo de todos os caminhos possveis pelo cdigo. Ao contrrio, os dados de perfil registram o que realmente
aconteceu em uma nica execuo do cdigo, com um nico conjunto de dados de
entrada. A anlise de fluxo de dados conservadora, pois considera todas as possibilidades. O perfil de runtime agressivo, pois assume que execues futuras compartilharo caractersticas de execuo com as do perfil. Ambos podem desempenhar
papel importante na otimizao.
QUESTES DE REVISO
1. Em algumas situaes, o compilador precisa saber que uma varivel est viva
ao longo de todos os caminhos que saem de um bloco, ao invs de ao longo
de algum caminho. Reformule as equaes para LIVEOUT de modo que elas
calculem o conjunto de nomes que so usados antes da definio, junto com cada
caminho a partir do final do bloco at o n de sada do CFG, nf.
2. Para coletar perfis precisos do tipo contador de aresta, o compilador pode instrumentalizar cada aresta no CFG do procedimento em questo. Uma implementao inteligente pode instrumentalizar um subconjunto dessas arestas e deduzir
as contagens para o restante. Crie um esquema que obtenha dados precisos
de contagem de aresta sem instrumentalizar cada desvio. Sobre que princpios
baseia-se seu esquema?
A transformao
Para fazer a substituio em linha, o compilador reescreve um local de chamada com o
corpo do procedimento chamado, realizando as modificaes apropriadas para modelar
os efeitos da vinculao de parmetros. A Figura8.18 mostra dois procedimentos,
fee e fie, ambos chamando um terceiro, foe. A Figura8.19 representa o fluxo de
controle aps a colocao em linha da chamada de fie para foe. O compilador criou
Substituio em linha
Transformao que substitui um local de chamada por
uma cpia do corpo do procedimento chamado, reescrito para refletir as vinculaes de parmetros.
uma cpia de foe e a moveu para dentro de fie, conectou a sequncia de pr-chamada
de fie diretamente ao prlogo de sua cpia interna de foe e conectou o eplogo
sequncia de ps-chamada de modo semelhante. Alguns dos blocos resultantes podem
ser mesclados, permitindo melhoria com uma otimizao subsequente.
Naturalmente, o compilador deve usar uma IR que possa representar o procedimento
colocado em linha. Algumas construes da linguagem-fonte podem criar construes de
controle de fluxo arbitrrias e incomuns no cdigo resultante. Por exemplo, um procedimento chamado com diversos retornos prematuros pode gerar um grafo de fluxo de controle complexo. De modo semelhante, a construo de retorno alternativo do FORTRAN
permite que o procedimento chamador passe rtulos para o chamado; este pode ento fazer
com que o controle retorne para qualquer um desses rtulos. De qualquer forma, o grafo de
fluxo de controle resultante pode ser difcil de representar em uma AST de nvel quase fonte.
Na implementao, o construtor de compiladores deve prestar ateno proliferao
de variveis locais. Uma implementao simples criaria uma nova varivel local
no chamador para cada varivel local no procedimento chamado. Se o compilador
colocar diversos procedimentos em linha, ou vrios locais de chamada para o mesmo
procedimento chamado, o espao de nomes locais pode crescer a ponto de se tornar
muito grande. Embora o crescimento no espao de nomes no seja um problema de
exatido, ele pode aumentar o custo da compilao do cdigo transformado e, em alguns
casos, prejudicar o desempenho no cdigo final. Atentar a esse detalhe pode facilmente
evitar o problema de reutilizar nomes por vrios procedimentos chamados em linha.
O procedimento de deciso
A escolha de quais locais de chamada devem ser colocados em linha tarefa complexa.
A colocao em linha de determinado local de chamada pode melhorar o desempenho;
infelizmente, tambm pode degrad-lo. Para fazer escolhas inteligentes, o compilador
precisa considerar uma grande gama de caractersticas do procedimento chamador, do
chamado e do local de chamada, e, ainda, entender seus prprios pontos fortes e fracos.
Profundidade de aninhamento de lao. Os locais de chamada em laos so executados com mais frequncia do que os fora deles. E tambm atrapalham a capacidade
do compilador de escalonar o lao como uma unidade isolada (ver Seo 12.4).
j Frao de tempo de execuo. O clculo da frao de tempo de execuo gasto
em cada procedimento a partir de dados de perfil pode impedir que o compilador
coloque em linha rotinas que no podem ter impacto significativo sobre o
desempenho.
Na prtica, os compiladores pr-calculam algumas ou todas essas mtricas e depois
aplicam uma heurstica ou um conjunto de heursticas para determinar quais locais
de chamada colocar em linha. A Figura8.20 mostra uma heurstica tpica, que conta
com uma srie de parmetros de patamar, denominados t0 a t4. Os valores especficos
escolhidos para os parmetros controlaro grande parte do comportamento da heurstica; por exemplo, t3 deve, sem dvida alguma, ter valor maior do que o tamanho das
sequncias padro de pr-chamada e ps-retorno. Os melhores valores para esses
parmetros so, sem dvida, especficos de cada programa.
Para resolver este problema, podemos tratar o grafo de chamada como um conjunto
de restries sobre o posicionamento relativo de procedimentos no cdigo executvel.
Cada aresta do grafo de chamada, (p,q), especifica uma adjacncia que deve ocorrer
no cdigo executvel. Infelizmente, o compilador no pode satisfazer todas essas
adjacncias. Por exemplo, se p chama q, r e s, ele no consegue colocar todos os trs
prximos de p. Assim, compiladores que realizam posicionamento de procedimento
tendem a usar uma tcnica aproximada gulosa para encontrar um bom posicionamento,
ao invs de tentar calcular um posicionamento timo.
O posicionamento de procedimento difere sutilmente do problema de posicionamento
de cdigo global discutido na Seo 8.6.2. Aquele algoritmo melhora o cdigo, garan-
tindo que os caminhos quentes possam ser implementados com desvios fall-through.
Assim, o algoritmo de construo de cadeia na Figura8.16 ignora qualquer aresta de
CFG, a menos que seja executado do final de uma cadeia para o incio de outra. Ao
contrrio, quando o algoritmo de posicionamento de procedimento constri cadeias
de procedimentos, pode usar arestas que so executadas entre procedimentos que se
encontram no meio de suas cadeias, pois seu objetivo simplesmente colocar procedimentos prximos um do outro para reduzir tamanhos de conjuntos de trabalho
e interferncia na cache de instrues. Se p chama q e a distncia entre p e q menor
que o tamanho da cache de instrues, o posicionamento tem sucesso. Assim, de certa
forma, o algoritmo de posicionamento de procedimento tem mais liberdade do que o
algoritmo de layout de bloco.
O posicionamento de procedimento consiste em duas fases: anlise e transformao. A
anlise opera sobre o grafo de chamada do programa. E repetidamente seleciona dois
ns no grafo de chamada e os combina. A ordem de combinao controlada pelos
dados de frequncia de execuo, medida ou estimada, e determina o layout final. A
fase de layout simples; simplesmente rearruma o cdigo para os procedimentos na
ordem escolhida pela fase de anlise.
A Figura8.21 mostra um algoritmo guloso para a fase de anlise do posicionamento
de procedimento. Ele opera sobre o grafo de chamada do programa e constri iterativamente um posicionamento considerando arestas em ordem de sua frequncia de
execuo estimada. Como primeiro passo, constri o grafo de chamada, atribui a cada
aresta um peso que corresponde sua frequncia de execuo estimada e combina
todas as arestas entre dois ns em uma nica aresta. Como parte final de seu trabalho
de inicializao, constri uma fila de prioridade das arestas do grafo de chamada,
ordenada por seus pesos.
A segunda metade do algoritmo constri iterativamente uma ordem para o posicionamento do procedimento. O algoritmo associa a cada n do grafo uma lista
Exemplo
Para ver como funciona o algoritmo de posicionamento de procedimento, considere o
exemplo de grafo de chamada apresentado no painel 0da Figura8.22. A aresta de P5
para si mesmo aparece em cinza porque s afeta o algoritmo alterando as frequncias de
execuo. Um lao para si mesmo no pode afetar o posicionamento, pois sua origem
e destino so idnticos.
O painel 0 mostra o estado do algoritmo imediatamente antes que a reduo iterativa
comece. Cada n tem a lista trivial que contm seu prprio nome. A fila de prioridades tem cada aresta, exceto o lao para si mesmo, classificada por frequncia
de execuo.
O painel 1 mostra o estado do algoritmo aps a primeira iterao do lao while. O
algoritmo juntou P6 em P5, e atualizou a lista para P5 e a fila de prioridades.
No painel 2, o algoritmo juntou P4 em P5. Redirecionou (P1, P4) para P5 e mudou o
nome da aresta correspondente na fila de prioridades. Alm disso, removeu P4 do grafo
e atualizou a lista para P5.
As outras iteraes procedem de modo semelhante. O painel 4 mostra uma situao
onde combinou arestas. Quando juntou P5 em P1, redirecionou (P0, P5) para P1. Como
(P0, P1) j existia, ele simplesmente combinou seus pesos e atualizou a fila de prioridades excluindo (P0, P5) e alterando o peso em (P0, P1).
Ao final das iteraes, o grafo foi encolhido para um nico n, P0. Embora este exemplo
tenha construdo um layout que comea com o n de entrada, isso aconteceu por causa
dos pesos de aresta, e no por projeto algortmico.
Unidade de compilao
A poro de um programa apresentada ao compilador
normalmente chamada unidade de compilao.
modifique x, essa mudana pode invalidar a compilao anterior de ambos fee e fie,
pela mudana dos fatos sobre os quais a otimizao se baseia. Assim, uma mudana
em foe pode necessitar de uma recompilao de outros procedimentos no programa.
Para resolver esta questo fundamental e fornecer ao compilador acesso a todo o cdigo
fonte que precise, vrias estruturas diferentes foram propostas para compiladores que
realizam otimizao do programa inteiro ou interprocedimental: ampliar as unidades
de compilao, embutir o compilador em um ambiente de desenvolvimento integrado
e realizar a otimizao em tempo de ligao.
j
Ampliao de unidades de compilao. A soluo mais simples para os problemas prticos introduzidos pela otimizao interprocedimental esta ampliao.
Se o compilador s considera otimizao e anlise dentro de uma unidade de
compilao, e essas unidades so aplicadas de modo consistente, ento pode evitar os problemas. Ele s pode analisar e otimizar o cdigo que compilado junto;
assim, no pode introduzir dependncias entre unidades de compilao nem deve
exigir acesso ao cdigo-fonte ou a fatos sobre outras unidades. O compilador
otimizador PL/I da IBM usou esta tcnica; a qualidade do cdigo melhorava
medida que procedimentos relacionados eram agrupados no mesmo arquivo.
j Naturalmente, esta tcnica limita as oportunidades para otimizao interprocedimental. E tambm encoraja o programador a criar unidades de compilao
maiores e agrupar procedimentos que chamam um ao outro. Ambos podem
introduzir problemas prticos em um sistema com vrios programadores. Ainda
assim, por uma questo prtica, essa organizao atraente porque atrapalha
menos nosso modelo de comportamento do compilador.
j Ambiente de desenvolvimento integrado. Se o projeto embute o compilador em
um ambiente de desenvolvimento integrado (IDE Integrated Development
Environment), o compilador pode acessar o cdigo conforme necessrio por meio
do IDE, que pode notificar ao compilador quando o cdigo-fonte mudar, de
modo que ele possa determinar se a recompilao necessria. Esse modelo desloca a propriedade dos cdigos-fonte e compilado do desenvolvedor para o IDE.
A colaborao entre o IDE e o compilador, ento, garante que aes apropriadas
sejam tomadas para assegurar a otimizao consistente e correta.
j Otimizao em tempo de ligao. O construtor de compiladores pode deslocar a
otimizao interprocedimental para o ligador, onde ter acesso a todo o cdigo
ligado estaticamente. Para obter os benefcios da otimizao interprocedimental,
o linker tambm pode ter que realizar uma otimizao global subsequente. Como
os resultados da otimizao em tempo de ligao so apenas registrados no
executvel, e este descartado na prxima compilao, esta estratgia contorna o
problema da recompilao. E quase certamente realiza mais anlise e otimizao
do que as outras tcnicas, alm de oferecer simplicidade e exatido bvia.
REVISO DA SEO
Anlise e otimizao entre fronteiras de procedimento podem revelar novas oportunidades para melhoria do cdigo. Alguns exemplos incluem ajustar a ligao de procedimentos (sequncias de pr-chamada, prlogo, eplogo e ps-chamada) para um local
de chamada especfico, por meio da exposio de valores constantes ou valores redundantes em uma chamada. Muitas tcnicas foram propostas para reconhecer e explorar
essas oportunidades; substituio em linha uma das mais conhecidas e eficientes.
Um compilador que aplica anlise e otimizao interprocedimentais deve garantir que os
executveis que cria sejam baseados em uma viso consistente do programa inteiro. O
uso de fatos de um procedimento para modificar o cdigo em outro pode introduzir dependncias sutis entre o cdigo em procedimentos distantes, que o compilador precisa
reconhecer e respeitar. Vrias estratgias foram propostas para aliviar esses efeitos; talvez
a mais simples seja realizar transformaes interprocedimentais em tempo de ligao.
QUESTES DE REVISO
1. Suponha que o procedimento a invoque b e c. Se o compilador colocar em linha
a chamada para b, que economias nos espaos de cdigo e de dados poderiam
surgir? Se ele tambm colocar c em linha, outras economias no espao de dados
so possveis?
2. No posicionamento de procedimento, o que acontece com um procedimento
cujas arestas de entrada possuem frequncias de execuo estimadas iguais a
zero? Onde o algoritmo deve posicionar tal procedimento? O tratamento de
um procedimento desse tipo afeta o desempenho de tempo de execuo? O
compilador pode elimin-las como inteis?
NOTAS DO CAPTULO
O campo da otimizao de cdigo conta com uma literatura extensa e detalhada. Para
um tratamento mais profundo, o leitor deve considerar alguns dos livros especializados
sobre o assunto [20,268,270]. Seria intelectualmente agradvel se a otimizao de
EXERCCIOS
Seo 8.4
1. Aplique o algoritmo da Figura8.4 a cada um dos seguintes blocos:
Captulo
9.1INTRODUO
Como vimos no Captulo8, otimizao o processo de analisar um programa e transform-lo de forma a melhorar seu comportamento durante o tempo de execuo. Antes
que o compilador possa melhorar o cdigo, precisa localizar pontos no programa
onde a mudana do cdigo provavelmente o melhore, e deve provar que a mudana
do cdigo nesses pontos segura. Essas duas tarefas exigem um entendimento mais
profundo do cdigo do que o front end do compilador normalmente deriva. Para colher
as informaes necessrias a fim de localizar oportunidades para otimizao e justificar
essas otimizaes, os compiladores usam alguma forma de anlise esttica.
Em geral, anlise esttica envolve o raciocnio em tempo de compilao sobre o fluxo de
valores em tempo de execuo. Este captulo explora tcnicas que os compiladores usam
para analisar programas para dar suporte otimizao. Apresenta a anlise de fluxo
de dados em um nvel mais profundo do que o fornecido no Captulo8. Em seguida, a
Seo9.3 apresenta algoritmos para a construo e destruio da forma de atribuio
nica esttica. A Seo9.4 discute problemas na anlise de programa inteiro. A seo
Tpicos avanados apresenta mais material sobre dominncia de computao e uma
discusso sobre redutibilidade de grafos.
Roteiro conceitual
Compiladores usam a anlise esttica para determinar onde as transformaes de
otimizao podem ser aplicadas de modo seguro e lucrativo. No Captulo8, vimos que
as otimizaes operam sobre diferentes escopos, do local ao interprocedimental. Em
geral, uma transformao precisa de informaes analticas que cubram pelo menos
um escopo to grande quanto a transformao; ou seja, uma otimizao local precisa,
pelo menos, de informao local, enquanto uma otimizao de procedimento inteiro,
ou global, de informaes globais.
A anlise esttica geralmente comea com a anlise de fluxo de controle anlise da
forma IR do cdigo para entender o fluxo de controle entre as operaes. O resultado
399
da anlise de fluxo de controle um grafo de fluxo de controle. Em seguida, os compiladores analisam os detalhes de como os valores fluem pelo cdigo. E utilizam a
informao resultante para encontrar oportunidades para melhoria e para provar
asegurana das transformaes. A comunidade de otimizao desenvolveu a anlise
de fluxo de dados global para responder a essas questes.
A forma de atribuio nica esttica uma representao intermediria que unifica os
resultados das anlises de fluxo de controle e de fluxo de dados em uma nica estrutura
de dados esparsa, que provou ser til na anlise e na transformao, e tornou-se uma
representao padro usada nos compiladores de pesquisa e de produo.
Viso geral
O Captulo8 introduziu o assunto de anlise e transformao de programas examinando
mtodos locais, regionais, globais e interprocedimentais. A numerao de valor
algoritmicamente simples, embora alcance efeitos complexos; encontra expresses
redundantes, simplifica o cdigo com base nas identidades algbricas e zero, e propaga
valores constantes conhecidos. Ao contrrio, encontrar uma varivel no inicializada
conceitualmente simples, mas exige que o compilador analise o procedimento inteiro
para rastrear definies e usos.
Ponto de juno
Em um CFG, um ponto de juno um n que possui
vrios predecessores.
A diferena entre esses dois problemas est nos tipos de fluxos de controle que cada
mtodo precisa entender. A numerao de valor local e superlocal s lida com subconjuntos do CFG que formam rvores. Para identificar uma varivel no inicializada, o
compilador precisa raciocinar a respeito do CFG inteiro, incluindo ciclos e pontos de
juno, ambos complicando a anlise. Em geral, os mtodos que se restringem a grafos
de fluxo de controle que podem ser expressos como rvores so receptivos a solues
on-line, enquanto aqueles que lidam com ciclos no CFG exigem solues off-line a
anlise inteira precisa ser completada antes que a reescrita possa comear.
Anlise esttica, ou em tempo de compilao, uma coleo de tcnicas que os
compiladores usam para provar a segurana e a lucratividade de uma transformao em
potencial. A anlise esttica sobre blocos nicos ou rvores de blocos normalmente
simples. Este captulo concentra-se na anlise global, onde o CFG pode conter ciclos
e pontos de juno. E mencionar vrios problemas na anlise interprocedimental,
que operam sobre o grafo de chamada do programa ou algum grafo relacionado. Para
realizar a anlise interprocedimental, o compilador deve ter acesso a informaes sobre
outros procedimentos no programa.
Em casos simples, a anlise esttica pode produzir resultados precisos o compilador pode saber exatamente o que acontecer quando o cdigo for executado. Se o
compilador puder obter informaes precisas, ser capaz de substituir a avaliao em
tempo de execuo de uma expresso ou funo por um load imediato do resultado. Por
outrolado, se o cdigo l valores de qualquer fonte externa, envolve quantidades at
mesmo modestas de fluxo de controle ou encontra quaisquer referncias de memria
ambguas (de ponteiros, referncias de array ou parmetros de chamada por referncia),
ento a anlise esttica torna-se muito mais difcil e seus resultados so menos precisos.
Este captulo comea examinando problemas clssicos na anlise de fluxo de dados.
Focamos um algoritmo iterativo para solucion-los, pois ele simples, robusto e
fcil de entender. A Seo9.3 apresenta um algoritmo para construir uma forma de
atribuio nica esttica para um procedimento. A construo conta bastante com os
resultados da anlise de fluxo de dados. A seo Tpicos avanados explora a noo
de redutibilidade de grafo de fluxo, apresenta uma tcnica mais rpida para calcular
dominadores e fornece uma introduo anlise de fluxo de dados interprocedimental.
9.2.1Dominncia
Muitas tcnicas de otimizao precisam raciocinar a respeito das propriedades estruturais do cdigo subjacente e seu grafo de fluxo de controle. Uma ferramenta-chave que
os compiladores usam para raciocinar a respeito da forma e estrutura do CFG a noo
de dominadores. Conforme veremos, os dominadores desempenham um papel-chave
na construo da forma de atribuio nica esttica. Embora muitos algoritmos tenham
sido propostos para calcular informaes de dominncia, um problema de fluxo de
dados extremamente simples ser suficiente para incluir em cada n bi no CFG, que
representa um bloco bsico, um conjunto DOM(bi) que contm os nomes de todos os
ns que dominam bi.
Para tornar essa noo de dominncia concreta, considere o n B6 no CFG apresentado
a seguir. (Observe que esse CFG difere ligeiramente do exemplo no Captulo8.) Todos
os ns B0, B1, B5 e B6 encontram-se em cada caminho de B0 a B6, de modo que DOM(B6)
{B0, B1, B5, B6}. O conjunto completo de conjuntos DOM para o CFG o seguinte:
DOM(n)
B0
{0}
B1
{0,1}
B2
{0,1,2}
B3
{0,1,3}
B4
{0,1,3,4}
B5
{0,1,5}
B6
B7
B8
{0,1,5,6} {0,1,5,7} {0,1,5,8}
Dominncia
Em um grafo de fluxo com n de entrada b0, o n bi
domina o n bj, escrito como bi >> bj, se, e somente
se, bi estiver em cada caminho de b0 a bj. Por definio,
bi >> bi.
p roduzir os conjuntos DOM para cada bloco. Para DOM, a etapa 2 trivial. Lembre-se
de que as equaes para LIVEOUT usaram dois conjuntos por bloco: UEVAR(b) e
VARKILL(b). Como a dominncia lida apenas com a estrutura do grafo, e no com
o comportamento do cdigo em cada bloco, a nica informao local necessria para
um bloco bi seu nome, i.
A Figura9.1 mostra um solucionador iterativo em rodzio (round-robin) para as equaes de dominncia, que considera os ns na ordem de seu nome no CFG, B0, B1, B2, e
assim por diante. Ele inicializa o conjunto DOM para cada n, e ento, repetidamente,
recalcula esses conjuntos DOM at que parem de mudar. Isto produz os seguintes
valores nos conjuntos DOM para o nosso exemplo:
DOM(n)
1
2
3
B0
B1
B2
B3
B4
B5
B6
B7
B8
{0}
{0}
{0}
{0}
N
{0,1}
{0,1}
{0,1}
N
{0,1,2}
{0,1,2}
{0,1,2}
N
{0,1,2,3}
{0,1,3}
{0,1,3}
N
{0,1,2,3,4}
{0,1,3,4}
{0,1,3,4}
N
{0,1,5}
{0,1,5}
{0,1,5}
N
{0,1,5,6}
{0,1,5,6}
{0,1,5,6}
N
{0,1,5,6,7}
{0,1,5,7}
{0,1,5,7}
N
{0,1,5,8}
{0,1,5,8}
{0,1,5,8}
A primeira coluna mostra o nmero da iterao; a linha marcada com um trao mostra
os valores iniciais para os conjuntos DOM. A primeira iterao calcula os conjuntos
DOM corretos para qualquer n com um caminho nico a partir de B0, mas tambm
conjuntos DOM extremamente grandes para B3, B4 e B7. Na segunda iterao, o conjunto DOM menor para B7 corrige o conjunto para B3, que, por sua vez, encurta DOM(B4).
De modo semelhante, o conjunto para B8 corrige o conjunto para B7. A terceira iterao
exigida para reconhecer que o algoritmo alcanou um ponto fixo. Observe que os
conjuntos DOM finais correspondem nossa tabela anterior.
Trs perguntas crticas surgem com relao a este procedimento de soluo. Primeiro, o
algoritmo termina? Ele repete at que os conjuntos DOM parem de mudar, de modo que
o argumento para o trmino no bvio. Segundo, produz conjuntos DOM corretos? A
Trmino
O clculo iterativo dos conjuntos DOM termina porque os conjuntos aproximados ao
longo da computao encolhem monotonicamente. O algoritmo inicializa o conjunto
DOM para n0 como {0}, para o n de entrada n0, e inicializa todos os outros como N,
o conjunto de todos os ns. Um conjunto DOM no pode ser menor que um nome de
n nem ser maior que |N|. Um raciocnio cuidadoso sobre o lao while mostra que um
conjunto DOM, digamos DOM(ni), no pode crescer de uma iterao para outra. Ou
ele encolhe, medida que o conjunto DOM de um de seus predecessores encolhe, ou
permanece inalterado.
O lao while termina assim que faz uma passagem sobre os ns em que nenhum
conjunto DOM muda. Como os conjuntos DOM s podem mudar encolhendo e so
limitados em tamanho, o lao while por fim ter que parar. Quando termina, ter encontrado um ponto fixo para esse particular clculo de DOM.
Exatido
Lembre-se da definio de um dominador. O n ni domina nj se cada caminho do
n de entrada n0 para nj contm ni. A dominncia uma propriedade dos caminhos
no CFG.
DOM(nj) contm i se, e somente se, i DOM(nk) para todo k preds(j), ou se i=j.
O algoritmo calcula DOM(nj) como a interseo dos conjuntos DOM de todos os
predecessores de nj, mais o prprio nj. Como esse clculo local sobre arestas individuais
relaciona-se propriedade de dominncia definida sobre todos os caminhos por meio
do CFG?
Os conjuntos DOM calculados pelo algoritmo iterativo formam uma soluo de ponto
fixo das equaes de dominncia. A teoria da anlise de fluxo de dados iterativa, que est
alm do escopo deste texto, garante-nos que existe um ponto fixo para essas equaes
em particular e que ele nico [210]. A soluo de todos os caminhos da definio
tambm um ponto fixo para as equaes, chamada soluo encontro-sobre-todos-os
-caminhos. A unicidade do ponto fixo garante que a soluo encontrada pelo algoritmo
iterativo esta soluo.
Eficincia
A unicidade da soluo de ponto fixo para as equaes DOM para um CFG especfico
garante que a soluo seja independente da ordem em que o solucionador calcula esses
conjuntos. Assim, o construtor de compiladores est livre para escolher uma ordem de
avaliao que melhore o tempo de execuo do analisador.
A travessia em ps-ordem reversa (RPO Reverse PostOrder) do grafo particularmente eficaz para o algoritmo iterativo. Uma travessia em ps-ordem visita o mximo
possvel dos filhos de um n, em uma ordem consistente, antes de visitar o n. (Em
um grafo cclico, o filho de um n tambm pode ser seu ancestral.) A travessia RPO
o oposto ela visita o mximo possvel dos predecessores de um n antes de visitar
o prprio n. O nmero de RPO de um n simplesmente |N|+1 menos seu nmero
de ps-ordem, onde N o conjunto de ns do grafo. Os grafos mais interessantes tero
mltiplas numeraes de ps-ordem reversa; do ponto de vista do algoritmo iterativo,
elas so equivalentes.
Nmero de ps-ordem
Rtulo dos ns de um grafo com sua ordem de visita
em uma travessia de ps-ordem.
CFG reverso
CFG com suas arestas invertidas; o compilador pode ter
que acrescentar um n de sada nico de modo que o
CFG reverso tenha um nico n de entrada.
Para um problema de fluxo de dados para a frente, como DOM, o algoritmo iterativo deve usar um RPO calculado no CFG. Para um problema de fluxo de dados
para trs, como LIVEOUT, o algoritmo deve usar um RPO calculado sobre o CFG
reverso.
Para ver o impacto da ordenao, considere o impacto de uma travessia RPO em nosso
clculo de DOM de exemplo. Uma numerao de RPO para o CFG de exemplo :
B0
0
RPO (n)
B1
1
B2
6
B3
7
B4
8
B5
2
B6
4
B7
5
B8
3
B0
B1
B2
B3
B4
B5
B6
B7
B8
{0}
{0}
{0}
N
{0,1}
{0,1}
N
{0,1,2}
{0,1,2}
N
{0,1,3}
{0,1,3}
N
{0,1,3,4}
{0,1,3,4}
N
{0,1,5}
{0,1,5}
N
{0,1,5,6}
{0,1,5,6}
N
{0,1,5,7}
{0,1,5,7}
N
{0,1,5,8}
{0,1,5,8}
Trabalhando em RPO, o algoritmo calcula conjuntos DOM precisos para esse grafo na
primeira iterao e termina depois da segunda iterao. Usando RPO, o algoritmotermina em duas passagens pelo grafo, ao invs de trs. Conforme veremos, ele no calcula
conjuntos DOM precisos na primeira passagem para todos os grafos.
Como um segundo exemplo, considere o CFG mostrado na margem. Sua estrutura
mais complexa do que o CFG anterior. Tem dois laos, (B2,B3) e (B3,B4), com vrias
entradas. Em particular, (B2,B3) tem entradas de (B0,B1,B2) e (B0,B5,B3), enquanto (B3,B4)
tem entradas de (B0,B5,B3) e (B0,B5,B4). Essa propriedade torna o grafo mais difcil de
ser analisado (ver Seo9.5.1).
Para aplicar o algoritmo iterativo, precisamos de uma numerao de ps-ordem reversa.
Esta numerao para esse CFG :
RPO (n)
B0
0
B1
2
B2
3
B3
4
B4
5
B5
1
B0
B1
B2
B3
B4
B5
{0}
{0}
{0}
{0}
N
{0,1}
{0,1}
{0,1}
N
{0,1,2}
{0,2}
{0,2}
N
{0,3}
{0,3}
{0,3}
N
{0,4}
{0,4}
{0,4}
N
{0,5}
{0,5}
{0,5}
O algoritmo exige duas iteraes para calcular os conjuntos DOM corretos. A iterao
final reconhece que a computao alcanou um ponto fixo.
O clculo de dominncia baseia-se apenas na estrutura do grafo; ignora o comportamento do cdigo em qualquer um dos blocos do CFG. Assim, poderia ser considerado
uma forma de anlise de fluxo de controle. A maioria dos problemas de fluxo de
A comparao das equaes para LIVEOUT e DOM revela diferenas entre os problemas. LIVEOUT um problema de fluxo de dados para trs, pois LIVEOUT(n)
calculado como uma funo da informao conhecida na entrada de cada um dos
sucessores de n no CFG. DOM um problema de fluxo de dados para a frente, pois
DOM(n) calculado como uma funo da informao conhecida ao final de cada um
dos predecessores de n no CFG. LIVEOUT procura um uso futuro em qualquer caminho no CFG; e, assim, junta informaes de vrios caminhos com o operador de unio.
DOM procura predecessores que se encontrem em todos os caminhos a partir do n de
entrada; e, assim, junta informaes de vrios caminhos com o operador de interseo.
Finalmente, LIVEOUT raciocina a respeito dos efeitos das operaes. Por este motivo,
usa os conjuntos de constantes especficos de bloco, UEVAR e VARKILL, que so
derivados do cdigo para cada bloco. Ao contrrio, DOM s lida com a estrutura do
CFG. Por consequncia, seu conjunto de constantes especfico de bloco contm apenas
o nome do bloco.
Terminao
A anlise de varivel viva iterativa termina porque os conjuntos crescem monotonicamente. Toda vez que o algoritmo avalia a equao LIVEOUT em um n do CFG, esse
conjunto LIVEOUT ou cresce ou permanece inalterado. A equao no pode encolher
o conjunto LIVEOUT. Em cada iterao, um ou mais conjuntos LIVEOUT crescem
em tamanho, a menos que todos permaneam inalterados. Quando o conjunto completo
de conjuntos LIVEOUT permanece inalterado em uma iterao, eles no mudaro em
iteraes subsequentes. Ele ter alcanado um ponto fixo.
Exatido (Correctness)
A anlise viva iterativa est correta se, e somente se, encontrar todas as variveis que
satisfazem a definio de vivncia (liveness) ao final de cada bloco. Lembre-se da
definio: uma varivel v est viva no ponto p se, e somente se, houver um caminho
de p para um uso de v ao longo do qual v no redefinida. Assim, vivncia definida
em termos dos caminhos no CFG. Um caminho que no contm definies de v precisa
existir de p para um uso de v, chamado caminho limpo de v.
LIVEOUT(n) deve conter v se, e somente se, v estiver viva ao final do bloco n.
Para formar LIVEOUT(n), o solucionador iterativo calcula a contribuio de cada
sucessor de n no CFG. Combina essas contribuies usando a unio, pois v
LIVEOUT(n) se v estiver viva em qualquer caminho saindo de n. Como essa
computao local sobre arestas individuais relaciona-se com a vivncia definida
sobre todos os caminhos?
Os conjuntos LIVEOUT calculados pelo solucionador iterativo so uma soluo de
ponto fixo para as equaes vivas. Novamente, a teoria da anlise de fluxo de dados
iterativa nos garante que essas equaes em particular tm um nico ponto fixo [210].
A unicidade do ponto fixo garante que a soluo de ponto fixo calculada pelos algoritmos iterativos idntica soluo de encontro-sobre-todos-os-caminhos, exigida
pela definio.
Ele contm uma expresso redundante, y*z, se, e somente se, p no contm o
endereo de y ou z. Em tempo de compilao, o valor de p e o endereo de y e z
podem ser desconhecidos. Em tempo de execuo, so conhecidos e podem ser
testados. Testar esses valores em tempo de execuo permitiria que o cdigo evitasse
recalcular y*z, embora a anlise em tempo de compilao seria incapaz de responder
a questo.
Porm, o custo de testar se p == &y ou p == &z ou nenhum deles e atuar sobre o
resultado provavelmente exceder o custo de recalcular y*z. Para a anlise dinmica
fazer sentido, ela precisaria ser, a priori, lucrativa ou seja, as economias precisam
exceder o custo da anlise. Isto acontece em alguns casos, mas na maioria no. Ao
contrrio, o custo da anlise esttica pode ser amortizado sobre vrias execues do
cdigo executvel, de modo que, em geral, seja mais atraente.
Eficincia
tentador pensar que o RPO no CFG reverso equivalente pr-ordem reversa no CFG. Veja o Exerccio 4, ao
final do captulo, para obter um contraexemplo.
RPO (n)
B0
8
B1
7
B2
6
B3
1
B4
0
B5
5
B6
4
B7
2
B8
3
FIGURA 9.5 Iteraes de anlise viva usando RPO sobre o CFG reverso.
anterior, podemos ver por qu. Na primeira iterao, o algoritmo calculou os conjuntos
LIVEOUT corretos para todos os ns, exceto B3. Foi necessria uma segunda iterao
para B3 devido aresta de volta de B3 para B1. A terceira iterao necessria para
reconhecer que o algoritmo alcanou seu ponto fixo.
A anlise de fluxo de dados considera que todos os caminhos pelo CFG so viveis.
Assim, a informao que eles calculam resume os possveis eventos de fluxo de
dados, considerando que cada caminho pode ser tomado. Isso limita a preciso
da informao resultante; dizemos que a informao precisa at a execuo
simblica. Com essa suposio, x LIVEOUT(B0) e tanto B0 quanto B1 precisam
ser preservados.
Outra forma de impreciso nos resultados da anlise de fluxo de dados vem do tratamento de arrays, ponteiros e chamadas de procedimento. Uma referncia de array,
como A[i,j,k,], refere-se a um nico elemento de A. Porm, sem a anlise que
revele os valores de i, j e k, o compilador no pode saber qual elemento de A est
sendo acessado. Por este motivo, os compiladores tradicionalmente tm agregado todas
as referncias a um array A. Assim, um uso de A[x,y,z] conta como um uso de A,
e uma definio de A[c,d,e] conta como uma definio de A.
Porm, deve-se tomar cuidado para evitar fazer uma inferncia muito forte. O compilador, sabendo que sua informao sobre arrays imprecisa, precisa interpretar essa
informao conservadoramente. Assim, se o objetivo da anlise determinar onde
um valor no est mais vivo (ou seja, o valor deve ter sido morto), uma definio de
A[i,j,k] no mata o valor de A. Se o objetivo reconhecer onde um valor poderia
no sobreviver, ento uma definio de A[i,j,k] poderia definir qualquer elemento
de A.
Ponteiros acrescentam outro nvel de impreciso aos resultados da anlise esttica. A
aritmtica explcita sobre ponteiros torna as coisas piores. Sem uma anlise que rastreie
especificamente os valores de ponteiros, o compilador precisa interpretar uma atribuio
a uma varivel do tipo ponteiro como uma potencial definio para cada varivel que o
ponteiro poderia alcanar. A segurana de tipo pode limitar o conjunto de objetos definidos potencialmente por uma atribuio por meio de um ponteiro; um ponteiro declaradocomo apontando para um objeto do tipo t s pode ser usado para modificar objetos
do tipo t. Sem a anlise dos valores de ponteiro ou uma garantia da segurana de
tipo, a atribuio a uma varivel do tipo ponteiro pode forar o analisador a assumir
que cada varivel foi modificada. Na prtica, este efeito frequentemente impede o
compilador de manter o valor de uma varivel do tipo ponteiro em um registrador por
meio de qualquer atribuiobaseada em ponteiro. A menos que o compilador possa
provar especificamente que o ponteiro usado na atribuio no pode se referir ao local
de memria correspondente ao valor registrado, no poder manter com segurana o
valor em um registrador.
A complexidade da anlise do uso de ponteiro leva muitos compiladores a evitar manter
valores em registradores se estes puderem ser o alvo de um ponteiro. Normalmente,
algumas variveis podem ser excludas desse tratamento como variveis locais
cujo endereo nunca foi tomado explicitamente. A alternativa realizar a anlise de
fluxode dados voltada para remover a ambiguidade de referncias baseadas em ponteiro
reduzindo o conjunto de possveis variveis que um ponteiro poderia referenciar
em cada ponto do cdigo. Se o programa puder passar ponteiros como parmetros
ou us-los como variveis globais, a remoo da ambiguidade de ponteiro torna-se
inerentemente interprocedimental.
Chamadas de procedimento fornecem uma fonte de impreciso final. Para entender o
fluxo de dados no procedimento atual, o compilador precisa saber o que o procedimento
chamado pode fazer a cada varivel que acessvel aos procedimentos chamador e
chamado. O procedimento chamado pode, por sua vez, chamar outros procedimentos
que tm seus prprios efeitos colaterais em potencial.
A menos que o compilador calcule informaes de resumo precisas para cada chamada
de procedimento, ele deve estimar seu comportamento de pior caso. Embora as suposies especficas variem de um problema para outro, a regra geral considerar que o
procedimento chamado tanto usa quanto modifica cada varivel que pode enderear,
e que os parmetros de chamada por referncia criam referncias ambguas. Como
poucos procedimentos exibem este comportamento, esta suposio normalmente
superestima os efeitos de uma chamada e introduz mais impreciso aos resultados da
anlise de fluxo de dados.
Expresses disponveis
Para identificar expresses redundantes, o compilador pode calcular informaes sobre
a disponibilidade das expresses. Uma expresso e est disponvel no ponto p em um
procedimento se, e somente se, em cada caminho a partir da entrada do procedimento
at p, e avaliada e nenhuma das suas subexpresses constituintes redefinida entre
essa avaliao e p. Essa anlise associa a cada n n no CFG um conjunto AVAILIN(n),
que contm os nomes de todas as expresses no procedimento que esto disponveis
na entrada para o bloco correspondente a n. Para calcular AVAILIN, o compilador
inicialmente define
Aqui, DEEXPR(n) o conjunto de expresses expostas para baixo em n. Uma expresso e DEEXPR(n) se, e somente se, o bloco n avalia e e nenhum dos operandos de
e definido entre a ltima avaliao de e em n e o final de n. EXPRKILL(n) contm
todas aquelas expresses que esto mortas por uma definio em n. Uma expresso
est morta se um ou mais de seus operandos so redefinidos no bloco. Observe que a
equao define um problema de fluxo de dados para a frente.
Uma expresso e est disponvel na entrada de n se, e somente se, estiver disponvel
na sada de cada um dos predecessores de n no CFG. Como a equao indica, uma
expresso e est disponvel na sada de algum bloco m se uma de duas condies for
satisfeita: ou e exposta para baixo em m, ou est disponvel na entrada de m e no
est morta em m.
Conjuntos AVAILIN podem ser usados para realizar a eliminao de redundncia global,
s vezes chamada eliminao de subexpresso comum global. Talvez o modo mais
simples de conseguir esse efeito seja calcular conjuntos AVAILIN para cada bloco e us
-los na numerao de valor local (ver Seo 8.4.1). O compilador pode simplesmente
inicializar a tabela hash para um bloco b como AVAILIN(b) antes da numerao de valor
de b. A movimentao de cdigo preguioso uma forma mais forte de eliminao de
subexpresso comum, que tambm usa disponibilidade (ver Seo 10.3.1).
Definies de alcance
Em alguns casos, o compilador precisa saber onde um operando foi definido. Se vrios caminhos no CFG levarem operao, ento vrias definies podem fornecer
o valor do operando. Para encontrar o conjunto de definies que alcanam um
bloco, o compilador pode calcular definies de alcance. O domnio de REACHES
o conjunto de definies no procedimento. Uma definio d de alguma varivel
v alcana a operao i se, e somente se, i l o valor de v e existe um caminho de
d at i que no define v.
O compilador associa a cada n n no CFG um conjunto REACHES(n), calculado como
um problema de fluxo de dados para a frente:
Expresses antecipveis
Uma expresso e considerada antecipvel, ou muito ocupada, na sada do bloco b
se, e somente se, (1) cada caminho que sai de b avalia e subsequentemente usa e, e
(2) a avaliao de e no final de b produzir o mesmo resultado da primeira avaliao
de e ao longo de cada um desses caminhos. O termo antecipvel deriva da segunda
condio, que implica que uma avaliao de e em b antecipa as avaliaes subsequentes
ao longode todos os caminhos. O conjunto de expresses antecipveis na sada de um
bloco pode ser calculado como um problema de fluxo de dados para trs no CFG. O
domnio do problema o conjunto de expresses.
onde e=(p, q) uma aresta de p para q no grafo de chamada. A funo unbinde mapeia
um conjunto de nomes em outro. Para uma aresta do grafo de chamada e=(p,q), unbinde(x) mapeia cada nome em x do espao de nomes de q para o espao de nomes de
p, usando as ligaes no local de chamada especfico que corresponde a e. Finalmente,
LOCALMOD(p) contm todos os nomes modificados localmente em p que so visveis
Insensvel ao fluxo
Esta formulao de MAYMOD ignora o fluxo de controle
dentro dos procedimentos. Tal formulao considerada como insensvel ao fluxo.
REVISO DA SEO
A anlise de fluxo de dados iterativa funciona reavaliando repetidamente a equao
de fluxo de dados em cada n no grafo subjacente at que os conjuntos definidos
pelas equaes alcancem um ponto fixo. Muitos problemas de fluxo de dados tm
um nico ponto fixo, o que garante a soluo correta, independente da ordem de
avaliao, e a propriedade de cadeia descendente finita, que garante a terminao
independente da ordem de avaliao. Como o analisador pode escolher qualquer
ordem, deve optar por uma que produza terminao rpida. Para a maioria dos problemas de fluxo de dados para a frente (forward), esta ordem a ps-ordem reversa;
para a maioria dos problemas para trs (backward), a ordem a ps-ordem reversa
no CFG reverso. Essas ordens foram o algoritmo iterativo a avaliar o mximo de
predecessores (para problemas para frente) ou sucessores (para problemas para trs)
possveis antes de avaliar um n n.
Muitos problemas de fluxo de dados aparecem na literatura e nos compiladores
modernos. Exemplos incluem anlise viva, usada na alocao de registradores; disponibilidade e antecipao, usada na eliminao de redundncia e movimentao
de cdigo; e informao de resumo interprocedimental, usada para aperfeioar
os resultados da anlise de fluxo de dados de procedimento nico. A forma SSA,
descrita na prxima seo, fornece uma estrutura unificada que codifica tanto
informaes de fluxo de dados, como definies de alcance, quanto informaes
de fluxo de controle, como dominncia. Muitos compiladores modernos usam a
forma SSA como uma alternativa para resolver vrios problemas distintos de fluxo
de dados.
QUESTES DE REVISO
1. Calcule conjuntos DOM para o CFG apresentado a seguir, avaliando os ns na
ordem {B4, B2, B1, B5, B3, B0}. Explique por que esse clculo usa um nmero de
iteraes diferente daquele da verso mostrada na pgina 404.
2. Antes que um compilador possa calcular informaes de fluxo de dados interprocedimental, precisa montar um grafo de chamada para o programa. Assim como
os saltos ambguos complicam a construo do CFG, tambm as chamadas ambguas complicam a construo do grafo de chamada. Que recursos da linguagem
poderiam levar a um local de chamada ambguo um onde o compilador fique
incerto quanto identidade do procedimento chamado?
Dominncia estrita
a domina estritamente b se, e somente se, a
DOM(b) {b}.
rvores de dominadores
Antes de dar um algoritmo para calcular fronteiras de dominncia, temos que introduzir mais uma noo, a rvore de dominadores. Dado um n n em um grafo de
fluxo, o conjunto de ns que dominam estritamente n dado por (DOM(n)n). O n
nesteconjunto que est mais prximo de n chamado de dominador imediato de n, indicado por IDOM(n). O n de entrada do grafo de fluxo no possui dominador imediato.
A rvore de dominadores de um grafo de fluxo contm cada n do grafo de fluxo. Suas
arestas codificam os conjuntos IDOM de um modo simples. Se m IDOM(n), ento a
rvore de dominadores tem uma aresta de m para n. Esta rvore para o nosso CFG de
exemplo aparece na margem. Observe que B6, B7 e B8 so todos filhos de B5, embora
B7 no seja um sucessor imediato de B5 no CFG.
rvore de dominadores
rvore que codifica a informao de dominncia
paraum grafo de fluxo.
DOM
IDOM
B0
B1
B2
B3
B4
B5
B6
B7
B8
{0}
{0,1}
0
{0,1,2}
1
{0,1,3}
1
{0,1,3,4}
3
{0,1,5}
1
{0,1,5,6}
5
{0,1,5,7}
5
{0,1,5,8}
5
DF
B0
B1
{B1}
B2
{B3}
B3
{B1}
B4
B5
{B3}
B6
{B7}
B7
{B3}
B8
{B7}
Exemplo
A Figura9.10 resume nosso exemplo corrente. O painel a mostra o cdigo; o painel b,
o CFG; o painel c, as fronteiras de dominncia para cada bloco; e o painel e, a rvore
de dominadores montada a partir do CFG.
funes-
a
{B1,B3 }
b
{B1,B3 }
c
{B1,B3,B7 }
d
{B1,B3,B7 }
i
{B1 }
Limitar o algoritmo a nomes globais permite que ele evite a insero de funes-
mortas para x e y no bloco B1. (B1 DF(B3) e B3 contm definies de x e y.) Porm,
a distino entre nomes locais e globais no suficiente para evitar todas as funes-
mortas. Por exemplo, a funo- para b em B1 no est viva porque b redefinido
antes que seu valor seja usado. Para evitar a insero dessas funes-, o compilador
pode construir conjuntos LIVEOUT e acrescentar um teste com base na vivncia
para o lao interno do algoritmo de insero-. Essa modificao faz que o algoritmo
produza a forma SSA podada.
Melhorias na eficincia
Para melhorar a eficincia, o compilador deve evitar dois tipos de duplicao. Primeiro,
o algoritmo deve evitar colocar qualquer bloco na worklist mais de uma vez por nome
global. E pode manter uma lista de verificao de blocos que j foram processados. Como
o algoritmo precisa reiniciar a lista de verificao para cada nome global, a implementao deve usar um conjunto esparso ou uma estrutura semelhante (ver Apndice B.2.3).
Segundo, um determinado bloco pode estar na fronteira de dominncia de vrios ns
que aparecem na Worklist. Como vemos na figura, o algoritmo precisa examinar o
bloco para procurar uma funo- preexistente. Para evitar essa busca, o compilador
pode manter uma lista de verificao de blocos que j contm funes- para x. Isto
exige um nico conjunto esparso, reinicializado juntamente com Worklist.
9.3.4Renomeao
Na descrio da forma SSA mxima, indicamos que a renomeao de variveis era
conceitualmente simples. Os detalhes, porm, exigem alguma explicao.
Na forma SSA final, cada nome global torna-se um nome bsico, e definies individuais desse nome bsico so distinguidas pela adio de um subscrito numrico. Para
um nome que corresponde a uma varivel na linguagem-fonte, digamos, x, oalgoritmo
usa x como o nome bsico. Assim, a primeira definio de x que o algoritmo de
renomeao encontra ser chamado x0 e o segundo x1. Para um temporrio gerado
pelo compilador, o algoritmo precisa gerar um nome bsico distinto.
O algoritmo, apresentado na Figura9.12, renomeia definies e usos em um percurso em
pr-ordem pela rvore de dominadores do procedimento. Em cada bloco, primeiro renomeia os valores definidos por funes- no incio do bloco, depois visita cada operao
no bloco, em ordem. Reescreve os operandos com os nomes SSA atuais, depois cria um
novo nome SSA para o resultado da operao. Este ltimo ato torna o novo nome o nome
corrente. Depois que todas as operaes no bloco tiverem sido reescritas, o algoritmo
reescreve os parmetros apropriados da funo- em cada sucessor do bloco no CFG,
usando os nomes SSA atuais. Finalmente, ele se repete em quaisquer filhos do bloco
na rvore de dominadores. Ao retornar dessas chamadas recursivas, restaura o conjunto
de nomes SSA atuais para o estado que existia antes que o bloco atual fosse visitado.
Para controlar este processo, o algoritmo usa um contador e uma pilha para cada nome
global. A pilha de um nome global mantm o subscrito do nome SSA atual do nome.
Em cada definio, o algoritmo gera um novo subscrito para o nome visado, empilhando o valor do seu contador atual e incrementando o contador. Assim, o valor no
topo da pilha para n sempre o subscrito do nome SSA atual de n. Como ltima etapa
no processamento de um bloco, o algoritmo remove todos os nomes gerados nesse
bloco de suas respectivas pilhas, para restaurar os nomes que se mantinham no final do
dominador imediato desse bloco. Esses nomes podem ser necessrios para processar
os irmos restantes do bloco na rvore de dominadores.
A pilha e o contador servem a finalidades distintas e separadas. medida que o controle
no algoritmo sobe e desce na rvore de dominadores, a pilha gerenciada para simular o
tempo de vida da definio mais recente no bloco atual. O contador, por outro lado, cresce
monotonicamente para garantir que cada definio sucessiva receba um nico nome SSA.
A Figura9.12 resume o algoritmo. Ela inicializa as pilhas e contadores, depois chama
Rename para a raiz da rvore de dominadores o n de entrada do CFG. Rename
reescreve o bloco e se repete nos sucessores da rvore de dominadores. Para terminar
com o bloco, Rename remove quaisquer nomes que foram colocados nas pilhas durante
o processamento do bloco. A funo Newname manipula os contadores e pilhas para
criar novos nomes SSA conforme a necessidade.
Resta um ltimo detalhe. Ao final do bloco b, Rename precisa reescrever os parmetros
da funo- para cada um dos sucessores CFG de b. O compilador deve atribuir um
slot ordinal de parmetro nestas funes- para b. Quando desenhamos a forma SSA,
sempre consideramos uma ordem da esquerda para a direita que combina com a ordem
da esquerda para a direita em que as arestas so desenhadas. Internamente, o compilador
pode numerar as arestas e slots de parmetro em qualquer padro consistente que produza o resultado desejado. Isto requer cooperao entre o cdigo que constri a forma
SSA e o que constri o CFG. (Por exemplo, se a implementao CFG usa uma lista
de arestas saindo de cada bloco, a ordem desta lista pode determinar o mapeamento.)
Exemplo
Para terminar o exemplo em uso, vamos aplicar o algoritmo de renomeao ao cdigo
contido na Figura9.11. Suponha que a0, b0, c0 e d0 sejam definidos na entrada de B0.
A Figura9.13 mostra os estados dos contadores e pilhas para nomes globais em vrios
pontos durante o processo.
O algoritmo faz um percurso em pr-ordem sobre a rvore de dominadores, que corresponde a visitar os ns em ordem crescente de nome, de B0 a B8. A configurao inicial
das pilhas e contadores aparece na Figura9.13a. medida que o algoritmo prossegue
pelos blocos, realiza as seguintes aes:
j
Bloco B0. Este bloco contm apenas uma operao. Rename reescreve i
comi0, incrementa o contador e coloca i0 na pilha para i. Em seguida, visita
osucessor de B0 no CFG, B1, e reescreve os parmetros da funo- que correspondem a B0 com seus nomes atuais: a0, b0, c0, d0 e i0. Depois, o algoritmo
serepete sobre o filho de B0 na rvore de dominadores, B1. Depois disso, remove
o topo da pilha para i e retorna.
Bloco B1. Rename entra em B1 com o estado apresentado na Figura9.13b. Ele
reescreve os alvos da funes- com nomes novos, a1, b1, c1, d1 e i1. Em
seguida, cria novos nomes para as definies de a e c e as reescreve. Reescreve
os usos de a e c na comparao. Nenhum dos sucessores de B1 no CFG tem
funes-, de modo que ele se repete sobre os filhos da rvore de dominadores
de B1, ou seja, B2, B3 e B5. Finalmente, remove os topos das pilhas e retorna.
Bloco B2. Rename entra em B2 com o estado apresentado na Figura9.13c. Esse
bloco no tem funes- para reescrever. Rename reescreve as definies de b,
c e d, criando um novo nome SSA para cada um. Depois, reescreve os parmetros da funo- no sucessor de B2 no CFG, que B3. A Figura9.13d mostra
as pilhas e os contadores imediatamente antes de serem removidos da pilha.
Finalmente, ele remove os topos das pilhas e retorna.
Bloco B3. Rename entra em B3 com o estado mostrado na Figura9.13e. Observe
que as pilhas voltaram ao seu estado quando Rename entrou em B2, mas os
contadores refletem os nomes criados dentro de B2. Em B3, Rename reescreve
os alvos da funo-, criando novos nomes SSA para cada um. Em seguida,
reescreve cada atribuio no bloco, usando os nomes SSA atuais para os usos
edepois criando novos nomes SSA para a definio. (Como y e z no so nomes
globais, ele os deixa intactos.)
B3 tem dois sucessores no CFG, B1 e B4. Em B1, ele reescreve os parmetros
dafuno- que correspondem aresta de B3, usando as pilhas e contadores
mostrados na Figura9.13f. B4 no tem funes-. Em seguida, Rename se
repete sobre o filho da rvore de dominadores de B3, que B4. Quando essa
chamada retorna, Rename remove os topos das pilhas e retorna.
Bloco B4. Esse bloco s contm uma instruo de retorno, e no possui
funes-, definies, usos ou sucessores no CFG ou na rvore
de dominadores. Assim, Rename no realiza aes e deixa as pilhas
e contadores inalterados.
Bloco B5. Aps B4, Rename remove da pilha de B3 at B1. Com as pilhas conforme mostradas na Figura9.13g, ele se repete para o filho da rvore de dominadores final de B1, que B5. B5 no possui funes-. Rename reescreve as duas
instrues de atribuio e a expresso no condicional, criando novos nomes SSA
conforme a necessidade. Nenhum dos sucessores de B5 no CFG tem funes-.
Rename, em seguida, se repete sobre os filhos da rvore de dominadores de B5,
que so B6, B7 e B8. E, finalmente, remove os topos das pilhas e retorna.
Bloco B6. Rename entra em B6 com o estado apresentado na Figura9.13h. B6 no
tem funes-. Rename reescreve a atribuio a d, gerando o novo nome SSA
d5. Em seguida, visita as funes- no sucessor de B6 no CFG, que B7. Reescreve os argumentos da funo- que correspondem ao caminho a partir de B6
com seus nomes atuais, c2 e d5. Como B6 no tem filhos na rvore de dominadores, ele remove o topo da pilha para d e retorna.
acreditar que ele possa simplesmente remover os subscritos dos nomes SSA, reverter
aos nomes bsicos e excluir as funes-. Se o compilador simplesmente criar a forma
SSA e convert-la de volta em cdigo executvel, esta tcnica funcionar. Porm, se
o cdigo tiver sido rearrumado ou os valores sido renomeados, ela pode produzir um
cdigo incorreto.
Como exemplo, vimos na Seo 8.4.1 que o uso de nomes SSA poderia permitir a
numerao de valor local (LVN Local Value Numbering) para descobrir e eliminar
mais redundncias.
Antes da LVN
Aps a LVN
a x+y
a x+y
b x+y
ba
a 17
c x+y
a 17
c x+y
Antes da LVN
Aps a LVN
a 0 x0 + y0
a 0 x0 + y0
b0 x 0 + y 0
b0 a 0
a 1 17
a 1 17
c0 x0 + y0
c0 a 0
Aresta crtica
Em um CFG, uma aresta cuja fonte tem vrios sucessores e cujo destino tem mltiplos predecessores
chamada aresta crtica.
Chamamos uma aresta como (B3, B1) de aresta crtica. Quando o compilador insere um
bloco no meio de uma aresta crtica, ele a divide. Algumas transformaes na forma
SSA consideram que o compilador divide todas as arestas crticas antes de aplicar a
transformao.
Na traduo a partir da forma SSA, o compilador pode dividir arestas crticas para
criar locais para as operaes de cpia necessrias. Essa transformao resolve a
maior parte dos problemas que surgem durante a traduo a partir da SSA. Porm,
dois problemas mais sutis podem aparecer. O primeiro, que chamamos problema da
cpia perdida, surge de uma combinao de transformaes de programa agressivas
e arestas crticas no divididas. O segundo, chamado problema de troca (swap), surge
de uma interao de algumas transformaes de programa agressivas e a definio
detalhada da forma SSA.
de um bloco com uma ou mais operaes de cpia e um salto pode ter impacto contrrio sobre a velocidade de execuo. De modo semelhante, a incluso de blocos
e arestas nos ltimos estgios da compilao pode interferir com o escalonamento
regional, com a alocao de registradores e com otimizaes como o posicionamento
de cdigo.
O problema de cpia perdida surge da combinao de desdobramento de cpia e arestas crticas que no podem ser divididas. A Figura9.16 mostra um exemplo. O painel
a mostra o cdigo original um lao simples. No painel b, o compilador converteu
o lao para a forma SSA e desdobrou a cpia de i para y, substituindo o nico uso
de y por uma referncia a i1. O painel c mostra o cdigo produzido pela insero
de cpia direta para os blocos predecessores da funo-. Esse cdigo atribui valor
errado a z0. O cdigo original atribui a z0 o segundo valor de i; o cdigo no painel
c atribui a z0 o ltimo valor de i. Com a diviso de aresta crtica, como no painel d,
a insero de cpia produz o comportamento correto. Porm, acrescenta um salto a
cada iterao do lao.
A combinao de uma aresta crtica no dividida e desdobramento de cpia cria a
cpia perdida. O desdobramento de cpia eliminou a atribuio y i desdobrando
i1 para a referncia a y no bloco que vem aps o lao. Assim, o desdobramento de
cpia estendeu o tempo de vida de i1. Depois, o algoritmo de insero de cpia substituiu a funo- no topo do corpo do lao por uma operao de cpia em cada um dos
predecessores do bloco, inserindo a cpia i1 i 2 no final do bloco em um ponto
onde i1 ainda est vivo.
O compilador pode evitar o problema de cpia perdida verificando a vivncia do nome-alvo para cada cpia que tenta inserir durante a traduo a partir da forma SSA. Ao
descobrir um alvo de cpia que est vivo, ele deve preservar o valor vivo em um nome
temporrio e reescrever os usos subsequentes para se referir ao nome temporrio. Essa
etapa de reescrita pode ser feita com um algoritmo modelado na etapa de renomeao
do algoritmo de construo da SSA. A Figura9.16e mostra o cdigo que essa tcnica
produz.
De fato, o problema de troca pode surgir sem um ciclo de cpias; tudo o que preciso
um conjunto de funes- que tenha, como entradas, variveis definidas como sadas de
outras funes- no mesmo bloco. No caso acclico, em que funes- referenciam os
resultados de outras funes- no mesmo bloco, o compilador pode evitar o problema
ordenando cuidadosamente as cpias inseridas.
Em geral, para resolver este problema, o compilador pode detectar casos em que funes- referenciam os alvos de outras funes- no mesmo bloco. Para cada ciclo de
referncias, ele precisa inserir uma cpia em um temporrio que quebre o ciclo. Depois,
pode escalonar as cpias para que respeitem as dependncias implicadas pelas funes-.
Semirreticulado
Um conjunto L e um operador de reunio tal que
a, b e c L,
3. a (b c)=(a b) c
Compiladores usam semirreticulados para modelar os
domnios de dados dos problemas de anlise.
ab
a>b
se e somente se
se e somente se
a b=b, e
ab e a b
1. a a=a,
2. a b=b a, e
x=x
x= x
ci cj=ci se ci=cj
ci cj= se ci cj
Complexidade
A fase de propagao de SSCP um esquema clssico de ponto fixo. Os argumentos para
terminao e complexidade seguem da extenso das cadeias descendentes pelo reticulado usado para representar valores, conforme mostra a Figura9.18. O Value associado
a qualquer nome SSA pode ter um de trs valores iniciais , alguma constante
ci diferente de ou , ou . A fase de propagao s pode reduzir seu valor. Para um
determinado nome SSA, isso pode acontece no mximo duas vezes de para ci para
. O algoritmo SSCP acrescenta um nome SSA worklist somente quando seu valor
muda, de modo que cada nome SSA aparece na worklist no mximo duas vezes. SSCP
avalia uma operao quando um de seus operandos removido da worklist. Assim, o
nmero total de avaliaes de, no mximo, o dobro do nmero de usos no programa.
para cada procedimento no programa e acrescenta uma aresta ao grafo para cada local
de chamada. Esse processo toma um tempo proporcional ao nmero de procedimentos
e o nmero de locais de chamada no programa; na prtica, o fator limitador ser o custo
de varredura dos procedimentos para encontrar os locais de chamada.
Caractersticas da linguagem-fonte podem tornar a construo do grafo de chamada
muito mais difcil. At mesmo programas em FORTRAN e C tm complicaes. Por
exemplo, considere o pequeno programa C mostrado na Figura9.20a. Seu grafo de
chamada exato aparece na Figura9.20b. As prximas subsees esboam as caractersticas de linguagem que complicam a construo do grafo de chamada.
FIGURA 9.20 Criao de um grafo de chamada com parmetros com valor de funo.
Para uma linguagem que permite que o programa importe cdigo executvel ou novas
definies de classe em tempo de execuo, o compilador precisa construir um grafo
de chamada conservador, que reflete o conjunto completo de potenciais procedimentos
chamados em cada local de chamada. Uma forma de realizar este objetivo construir
um n no grafo de chamada que represente procedimentos desconhecidos e dot-lo
com comportamento de pior caso; seus conjuntos MAYMOD e MAYREF devem ser
o conjunto completo de nomes visveis.
A anlise que reduz o nmero de locais de chamada que podem nomear mltiplos
procedimentos pode melhorar a preciso do grafo de chamada, reduzindo o nmero
de arestas irrelevantes arestas para chamadas que no podero ocorrer em tempo de
execuo. De mesma ou maior importncia, quaisquer locais de chamada que possam
ser reduzidos a um nico procedimento chamado podem ser implementados com uma
chamada simples; aqueles com mltiplos procedimentos chamados podem exigir
pesquisas em tempo de execuo para o despacho da chamada (ver Seo 6.3.3).
de valores constantes conhecidos em cada local de chamada. Para fazer isto, constri um
pequeno modelo para cada parmetro real, chamado funo de salto. Um local de chamada s com n parmetros tem um vetor de funes de salto, J s = J sa , J sb , J sc , , J sn ,
onde a o primeiro parmetro formal no procedimento chamado, b o segundo, e assim
X
por diante. Cada funo de salto, J S , conta com os valores de algum subconjunto dos
parmetros formais para o procedimento p que contenha s; indicamos este conjunto
X
como Support( J S ).
X
Por enquanto, considere que J S consista em uma rvore de expresses cujas folhas
so todos os parmetros formais do procedimento chamador ou constantes literais.
X
X
preciso que J S retorne se Value(y) for para qualquer y Support( J S ).
O algoritmo
A Figura9.21 mostra um algoritmo simples para a propagao de constante interprocedimental pelo grafo de chamada. Este semelhante ao algoritmo SSCP apresentado
na Seo 9.3.6.
O algoritmo associa um campo Value(x) a cada parmetro formal x de cada procedimento p. (Considera nomes exclusivos, ou totalmente qualificados, para cada parmetro
formal.) A fase de inicializao define de forma otimista todos os campos Value como
. Em seguida, ele percorre cada parmetro real a em cada local de chamada s no programa, atualiza o campo Value do parmetro formal f correspondente a a como Value
f
(f ) J s , e acrescenta f worklist. Essa etapa fatora o conjunto inicial de constantes
representadas pelas funes de salto para os campos Value e define a worklist para que
contenha todos os parmetros formais.
Estendendo o algoritmo
O algoritmo apresentado na Figura9.21 apenas propaga parmetros reais com valor constante para a frente ao longo das arestas do grafo de chamada. Podemos estend-lo, de modo
simples, para lidar com valores retornados e variveis que sejam globais a um procedimento.
Assim como o algoritmo constri funes de salto para modelar o fluxo de valores do
procedimento chamador para o chamado, pode construir funes de salto de retorno
para modelar os valores retornados do procedimento chamado para o chamador. As
funes de salto de retorno so particularmente importantes para rotinas que inicializam
valores, seja preenchendo um bloco common em FORTRAN ou definindo valores
iniciais para um objeto ou classe em Java. O algoritmo pode tratar as funes de salto de
retorno da mesma maneira como trata de funes de salto comuns; a nica complicao
significativa que a implementao precisa evitar a criao de ciclos de funes de salto
de retorno que divergem (por exemplo, para um procedimento com recurso de cauda).
Para estender o algoritmo de modo que abranja uma classe de variveis maior, o compilador pode simplesmente estender o vetor de funes de salto de modo apropriado.
A expanso do conjunto de variveis aumentar o custo da anlise, mas dois fatores
aliviam o custo. Primeiro, na construo de funo de salto, o analisador pode observar
que muitas dessas variveis no tm um valor que possa ser facilmente modelado; ele
pode mapear essas variveis para uma funo de salto universal que retorna e evitar
X
Por exemplo, Support( J S ) poderia conter um valor
X
lido de um arquivo, de modo que J S = .
coloc-las na worklist. Segundo, para as variveis que poderiam ter valores constantes,
a estrutura do reticulado garante que elas estaro na worklist no mximo duas vezes.
Assim, o algoritmo deve ainda ser bastante rpido.
REVISO DA SEO
Os compiladores realizam anlise interprocedimental para capturar o comportamento
de todos os procedimentos no programa e trazer esse conhecimento para a otimizao dentro dos procedimentos individuais. Para realizar a anlise interprocedimental,
ocompilador precisa acessar todo o cdigo do programa.
Um problema interprocedimental tpico exige que o compilador construa um grafo de
chamada (ou algum semelhante), inclua neste grafo informaes obtidas diretamente
dos procedimentos individuais e propague essa informao pelo grafo.
Os resultados da informao interprocedimental so aplicados diretamente na anlise
e otimizao intraprocedimentais. Por exemplo, os conjuntos MAYMOD e MAYREF
podem ser usados para reduzir o impacto de um local de chamada sobre a anlise de
fluxo de dados global, ou para evitar a necessidade de funes- aps um local de
chamada. A informao da propagao de constante interprocedimental pode ser
usada para inicializar um algoritmo global, como SSCP ou SCCP.
QUESTES DE REVISO
1. Que caractersticas do software moderno poderiam complicar a anlise interprocedimental?
2. Como o analisador poderia incorporar a informao de MAYMOD na propagao
de constante interprocedimental? Que efeito voc esperaria obter disso?
e o substitui por um nico n para represent-lo. Isto cria uma srie de grafos derivados,
em que cada um difere do seu predecessor na srie pelo efeito de uma nica etapa de
transformao. medida que o analisador transforma o grafo, calcula os conjuntos de
fluxo de dados para os novos ns representantes em cada grafo derivado com sucesso.
Esses conjuntos resumem os efeitos do subgrafo substitudo. As transformaes reduzem grafos bem comportados a um nico n. O algoritmo, ento, inverte o processo,
seguindo do grafo derivado final, com seu nico n, de volta ao grafo de fluxo original.
Enquanto ele expande o grafo de volta sua forma original, o analisador calcula os
conjuntos de fluxo de dados finais para cada n.
Basicamente, a fase de reduo colhe informaes do grafo inteiro e as consolida,
enquanto a de expanso propaga os efeitos no conjunto consolidado de volta aos
ns do grafo original. Qualquer grafo para o qual essa fase de reduo tem sucesso
considerado redutvel. Se o grafo no puder ser reduzido a um nico n, ele
irredutvel.
A Figura9.22 mostra um par de transformaes que pode ser usado para testar a
redutibilidade e construir um algoritmo de fluxo de dados estrutural. T1 remove um
lao prprio, uma aresta que vai de um n de volta para si mesmo. A figura mostra T1 aplicada a b, indicado como T1(b). T2 retorna um n b que tem exatamente
um predecessor a de volta para a; ele remove a aresta a, b e torna a a origem
de quaisquer arestas que originalmente saam de b. Se isto deixar vrias arestas a
Grafo redutvel
Um grafo de fluxo redutvel se as duas transformaes,
T1 e T2, o reduzirem a um nico n. Se esse processo
falhar, o grafo irredutvel.
Existem outras maneiras de testar a redutibilidade.
Por exemplo, se o framework DOM iterativo, usando
uma ordem de travessia RPO, precisar de mais de duas
iteraes sobre um grafo, esse grafo irredutvel.
IDOM(n)
B0
?
B1
0
B2
1
B3
1
B4
3
B5
1
B6
5
B7
5
B8
5
Observe que a rvore de dominadores e os IDOMs so isomrficos. IDOM(b) simplesmente o predecessor de b na rvore de dominadores, cuja raiz no tem predecessor;
de modo correspondente, seu conjunto IDOM indefinido.
O compilador pode ler os conjuntos DOM de um grafo a partir de sua rvore de
dominadores. Para um n n, seu conjunto DOM pode ser lido como o conjunto de ns
que se encontra no caminho de n at a raiz da rvore de dominadores, inclusive das
extremidades. No exemplo, o caminho da rvore de dominadores de B7 a B1 consiste em
(B7, B5, B1, B0), que corresponde ao conjunto calculado para DOM(B7) na Seo 9.2.1.
O topo da Figura9.24 mostra um algoritmo iterativo reformulado que evita o problema de inicializar os conjuntos IDOM e usa o algoritmo Intersect. Ele mantm
a informao do IDOM em um array, IDoms o, e inicializa a entrada do IDOM
para a raiz, b 0, como ele prprio. Depois, processa os ns em ps-ordem reversa.
Na computao das intersees, ele ignora os predecessores cujos IDOMs ainda no
foram computados.
NOTAS DO CAPTULO
O crdito para a primeira anlise de fluxo de dados geralmente dado a Vyssotsky, do
Bell Labs, no incio da dcada de 1960 [338]. O trabalho mais antigo, no compilador
FORTRAN original, inclua a construo de um grafo de fluxo de controle e uma anlise
no estilo Markov sobre o CFG, para estimar as frequncias de execuo [26]. Esse
analisador, criado por Lois Haibt, pode ser considerado um analisador de fluxo de dados.
A anlise iterativa de fluxo de dados tem uma longa histria na literatura. Entre os
primeiros artigos sobre este tpico esto o artigo de 1973 de Kildall [223], o trabalho
de Hecht e Ullman [186] e dois artigos de Kam e Ullman [210,211]. O tratamento
neste captulo segue o trabalho de Kam.
Este captulo focou a anlise iterativa do fluxo de dados. Muitos outros algoritmos para
solucionar problemas de fluxo de dados foram propostos [218]. O leitor interessado
deve explorar as tcnicas estruturais, incluindo anlise de intervalo [17,18,62]; anlise
T1-T2 [336,185]; o algoritmo de Graham-Wegman [168,169]; rvore balanceada,
algoritmo de compactao de caminho [330,331]; gramticas de grafo [219]; e a
tcnica de varivel particionada [359].
Dominncia tem uma longa histria na literatura. Prosser a introduziu em 1959, mas no
ofereceu um algoritmo para calcular dominadores [290]. Lowry e Medlock descrevem
o algoritmo usado em seu compilador [252]; ele toma um tempo pelo menos O(N2),
onde N o nmero de instrues no procedimento. Diversos autores desenvolveram
algoritmos mais rpidos, baseados na remoo de ns do CFG [8,3,291]. Tarjam
props um algoritmo O(N log N+E) baseado na pesquisa em profundidade e descoberta
de unio [329]. Lengauer e Tarjan melhoraram esse limite de tempo [244], assim como
outros [180,23,61]. A formulao de fluxo de dados para dominadores retirada de
Allen [12,17]. As estruturas de dados rpidas para dominncia iterativa devem-se a
Harvey [100]. O algoritmo da Figura9.8 de Ferrante, Ottenstein e Warren [145].
A construo de SSA baseada no trabalho inicial de Cytron e outros [110], que, por sua
vez, baseou-se nos trabalhos de Shapiro e Saint [313]; Reif [295,332]; e Ferrante, Ottenstein
e Warren [145]. O algoritmo na Seo9.3.3 constri a forma SSA semipodada [49]. Os
detalhes do algoritmo de renomeao e do algoritmo para reconstruir o cdigo executvel
so descritos por Briggs e outros [50]. As complicaes introduzidas por arestas crticas h
muito tempo tm sido reconhecidas na literatura sobre otimizao [304,133,128,130,225];
no deve ser surpresa que elas tambm apaream na traduo da SSA de volta para o cdigo
executvel. O algoritmo de constante simples esparsa, SSCP, deve-se a Reif e Lewis [296].
Wegman e Zadeck reformularam a SSCP para usar a forma SSA [346,347].
O compilador otimizador PL/I da IBM foi um dos primeiros sistemas a realizar anlise
de fluxo de dados interprocedimental [322]. A anlise de efeito colateral fez surgir
uma vasta literatura [34,32,102,103]. O algoritmo de propagao de constante interprocedimental vem da tese de Torczon e artigos subsequentes [68,172,263]; Cytron
e Wegman sugeriram outras tcnicas para o problema [111,347]. Burke e Torczon
[64] formularam uma anlise que determina quais mdulos em um programa grande
precisam ser recompilados em resposta a uma mudana na informao interprocedimental de um programa. A anlise de ponteiro inerentemente interprocedimental;
uma quantidade cada vez maior de literatura descreve este problema [348,197,77,238,
80,123,138,351,312,190,113,191]. Ayers, Gottlieb e Schooler descreveram um
sistema prtico que analisava e otimizava um subconjunto do programa inteiro [25].
EXERCCIOS
Seo 9.2
1. O algoritmo para a anlise viva na Figura9.2 inicializa o conjunto LIVEOUT
de cada bloco como . Existem outras inicializaes possveis? Elas mudam
oresultado da anlise? Justifique sua resposta.
Captulo
10
Otimizaes escalares
VISO GERAL DO CAPTULO
Um compilador otimizador melhora a qualidade do cdigo que gera aplicando transformaes que reescrevem o cdigo. Este captulo baseia-se na introduo otimizao,
doCaptulo8, e no material sobre anlise esttica do Captulo9, para focar a otimizao do
cdigo para uma nica thread de controle a chamada otimizao escalar. O captulo
introduz uma grande seleo de transformaes independentes de mquina que tratam
de uma srie de ineficincias no cdigo compilado.
Palavras-chave: Otimizao, Transformao, Dependente de mquina, Independente
de mquina, Redundncia, Cdigo morto, Propagao de constante
10.1INTRODUO
Um otimizador analisa e transforma o cdigo com a inteno de melhorar seu desempenho. O compilador usa anlises estticas, como a de fluxo de dados (ver Captulo9),
para descobrir oportunidades de transformaes e provar sua segurana. Essas anlises
so preldios das transformaes a menos que o compilador reescreva o cdigo,
nada mudar.
A otimizao de cdigo tem uma histria que to longa quanto a dos compiladores.
O primeiro compilador FORTRAN inclua a otimizao cuidadosa com a inteno de
fornecer um desempenho que competisse com o cdigo assembly codificado mo.
Desde esse primeiro compilador otimizador no final da dcada de 1950, a literatura
sobre otimizao tem aumentado para incluir milhares de artigos que descrevem
anlises e transformaes.
Decidir quais transformaes usar e selecionar uma ordem de aplicao para elas
continua sendo uma das decises mais assustadoras que um construtor de compiladores
enfrenta. Este captulo se concentra na otimizao escalar, ou seja, aquela do cdigo ao
longo de uma nica thread de controle. Identifica cinco fontes-chave de ineficincias
no cdigo compilado e depois apresenta um conjunto de otimizaes que ajuda a
remov-las. O captulo organizado em torno desses cinco efeitos; esperamos que um
construtor de compiladores, ao escolher otimizaes, possa usar o mesmo esquema
organizacional.
Otimizao escalar
Tcnicas de melhoria de cdigo que se concentram em
uma nica thread de controle.
Roteiro conceitual
Otimizao baseada em compilador o processo de anlise do cdigo para determinar
suas propriedades e uso dos resultados dessa anlise para reescrever o cdigo em um
formato mais eficiente ou mais eficaz. Essa melhoria pode ser medida de vrias maneiras, incluindo menos tempo de execuo, tamanho de cdigo reduzido ou menor uso
deenergia pelo processador durante a execuo. Cada compilador tem algum conjunto de
programas de entrada para os quais produz um cdigo altamente eficaz. Um bom
otimizador deve tornar esse desempenho disponvel em um conjunto muito maior de
entradas. O otimizador deve ser robusto, ou seja, pequenas mudanas na entrada no
devem produzir mudanas extravagantes no desempenho.
451
Independente de mquina
Considera-se independente de mquina uma transformao que melhora o cdigo na maioria das
mquinas-alvo.
Dependente de mquina
Considera-se dependente de mquina uma transfor
mao que conta com o conhecimento do processadoralvo.
Viso geral
A maioria dos otimizadores construda como uma srie de passos, como apresentado
na margem. Cada passo toma o cdigo na forma IR como entrada e produz uma verso
reescrita do cdigo IR como sada. Esta estrutura quebra a implementao em pedaos
menores e evita algumas das complexidades que surgem em programas grandes, monolticos. Ela permite que os passos sejam construdos e testados independentemente,
o que simplifica o desenvolvimento, teste e manuteno. Ela cria um caminho natural
para o compilador fornecer diferentes nveis de otimizao; cada nvel especifica um
conjunto de passos a serem executados. A estrutura de passos permite ao construtor
de compiladores executar alguns passos vrias vezes, se desejvel. Na prtica, alguns
passos devem ser executados uma vez, enquanto outros podem ser executados vrias
vezes em diferentes pontos da sequncia.
SEQUNCIAS DE OTIMIZAO
A escolha de transformaes especficas e da ordem de sua aplicao tem forte
impacto sobre a eficcia de um otimizador. Para tornar o problema mais difcil,
transformaes individuais tm efeitos superpostos (por exemplo, numerao de valor
local versus numerao de valor superlocal), e aplicaes individuais tm diferentes
conjuntos de ineficincias.
Igualmente difceis, as transformaes que visam diferentes efeitos interagem umas
com as outras. Uma determinada transformao pode criar oportunidades para outras.
Simetricamente, uma determinada transformao pode obscurecer ou eliminar
oportunidades para outras.
Os compiladores otimizadores clssicos disponibilizam vrios nveis de otimizao
(por exemplo, 0, 01, 02, ...) como um meio de oferecer ao usurio final vrias
sequncias que eles possam experimentar. Os pesquisadores tm focado as tcnicas
para derivar sequncias personalizadas para cdigos de aplicao especficos,
selecionando tanto o conjunto de transformaes quanto uma ordem de aplicao. A
Seo10.7.3 discute este problema com mais profundidade.
No projeto de um otimizador, a seleo e a ordenao de transformaes desempenham papel crtico para determinar a eficcia geral do otimizador. A seleo de transformaes determina quais ineficincias especficas no programa em IR o otimizador
descobre e como reescreve o cdigo para reduzi-las. A ordem em que o compilador
aplica as transformaes determina como os passos interagem.
Por exemplo, no contexto apropriado (r2>0 e r5=4), um otimizador poderia substituir mult r2,r5 r17 por lshiftI r2,2 r17. Essa mudana substitui uma
multiplicao de inteiros em mltiplos ciclos por uma nica operao shift de nico ciclo e reduz a demanda por registradores. Na maioria dos casos, essa reescrita lucrativa.
Porm, se o prximo passo contar com a comutatividade para rearrumar as expresses,
ento a substituio de uma multiplicao por um shift impede uma oportunidade (a
multiplicao comutativa, o shift no). Na medida em que uma transformao torne
os passos posteriores menos eficientes, pode prejudicar a qualidade geral do cdigo.
Adiar a substituio de multiplicaes por shifts pode evitar este problema; o contexto
necessrio para provar a segurana e a lucratividade dessa reescrita provavelmente deve
sobreviver aos passos intermedirios.
O primeiro obstculo no projeto e na construo de um otimizador conceitual. A literatura
sobre otimizao descreve centenas de algoritmos distintos para melhorar os programas
em IR. O construtor de compiladores deve selecionar um subconjunto dessas transformaes para implementar e aplicar. Embora a leitura de artigos originais possa
ajudar na implementao, ele fornece poucos critrios para o processo decisrio, pois
a maioria dos artigos defende o uso de suas prprias transformaes.
Os construtores de compilador precisam entender quais ineficincias aparecem em
aplicaes traduzidas por seus compiladores e que impacto essas ineficincias tm
sobre a aplicao. Dado um conjunto de falhas especficas a resolver, eles podem
ento selecionar transformaes especficas para resolv-las. Muitas transformaes,
na verdade, resolvem vrias ineficincias, e, portanto, a seleo cuidadosa pode reduzir
o nmero de passos necessrios. Como a maioria dos otimizadores construda com
recursos limitados, o construtor de compiladores pode priorizar as transformaes por
seu impacto esperado sobre o cdigo final.
Conforme mencionamos no roteiro conceitual, as transformaes podem estar em
duas categorias gerais: transformaes independentes de mquina e transformaes
dependentes de mquina. Alguns exemplos das primeiras, dos captulos anteriores,
incluem numerao de valor local, substituio em linha e propagao de constante. As
segundas normalmente encontram-se no mbito da gerao de cdigo algunsexemplos incluem otimizao peephole (ver Seo 11.5), escalonamento de instrues e
alocao de registradores ; outras encontram-se no mbito do otimizador; alguns
exemplos sobalanceamento de altura de rvore, posicionamento de cdigo global e
posicionamento de procedimento. Algumas transformaes resistem classificao; o
desenrolamento de lao pode resolver questes independentes de mquina, como
overhead de lao, ou questes dependentes de mquina, como escalonamento de
instrues.
Os Captulos8 e9 j apresentaram uma srie de transformaes, selecionadas para
ilustrar pontos especficos. Os trs captulos seguintes se concentram na gerao de
cdigo, uma atividade dependente de mquina. Muitas das tcnicas apresentadas
nesses captulos, como otimizao peephole, escalonamento de instrues e alocao
de registradores, so transformaes dependentes de mquina. Este captulo apresenta
uma grande seleo de transformaes, principalmente transformaes independentes
de mquina. As transformaes so organizadas em torno do efeito que tm sobre o
cdigo final. Vamos nos preocupar com cinco efeitos especficos.
j
Intil
Uma operao intil se nenhuma operao utiliza seu
resultado, ou se todos os usos do resultado so, por si
ss, mortos.
Inalcanvel
Uma operao inalcanvel se nenhum caminho
vlido no fluxo de controle contiver a operao.
s vezes, os programas contm computaes que no possuem efeito visvel externamente. Se o compilador puder determinar que uma dada operao no afeta os
resultados do programa, ele pode elimin-la. A maioria dos programadores no escreve tal cdigo intencionalmente. Porm, ele surge na maioria dos programas como
resultado direto da otimizao no compilador e normalmente pela expanso de macro
ou traduo ingnua no front end do compilador.
Dois efeitos distintos podem tornar uma operao elegvel para remoo. Ela pode
ser intil, significando que seu resultado no tem efeito visvel externamente. Alternativamente, ela pode ser inalcanvel, significando que no pode ser executada. Se
uma operao cai em qualquer dessas categorias, pode ser eliminada. O termo cdigo
morto usado normalmente para significar cdigo intil ou inalcanvel; usamos o
termo para significar intil.
A remoo de cdigo intil ou inalcanvel encolhe a forma IR do cdigo, o que leva
a um programa executvel menor, compilao mais rpida e, normalmente, execuo
mais rpida. Isto tambm pode aumentar a capacidade do compilador de aperfeioar
o cdigo. Por exemplo, o cdigo inalcanvel pode ter efeitos que aparecem nos
resultados da anlise esttica e impedem a aplicao de algumas transformaes. Neste
caso, a remoo do bloco inalcanvel pode mudar os resultados da anlise e permitir
mais transformaes (veja, por exemplo, a propagao de constante condicional, ou
SCCP, na Seo10.7.1).
Algumas formas de eliminao de redundncia tambm removem cdigo intil. Por
exemplo, a numerao de valor local aplica identidades algbricas para simplificar o
cdigo. Alguns exemplos so x+0 x, y1 y e max(z,z) z. Cada uma dessas simplificaes elimina uma operao intil por definio, operao que, quando
removida, no faz diferena no comportamento visvel externamente do programa.
Como os algoritmos nesta seo modificam o grafo de fluxo de controle (CFG) do
programa, distinguimos cuidadosamente entre os termos desvio, como em um cbr
da ILOC, e salto, como em um jump da ILOC. Ateno cuidadosa a esta distino
ajudar o leitor a entender os algoritmos.
marcada aos desvios que ela torna teis, o algoritmo conta com a noo de dependncia
de controle.
Ps-dominncia
Em um CFG, j ps-domina i se, e somente se, cada
caminho de i at o n de sada passar por j.
Veja tambm a definio de dominncia na pgina 417.
marca seus desvios de trmino de bloco como teis. Ao marc-los, ela os acrescenta
na worklist, terminando quando esta estiver vazia.
Sweep substitui qualquer desvio no marcado por um salto at seu primeiro ps-dominador que contm uma operao marcada. Se o desvio for no marcado,
ento seus sucessores, at seu ps-dominador imediato, no contm operaes
teis. (Caso contrrio, quando essas operaes fossem marcadas, o desvio teria
sido marcado.) Um argumento semelhante aplica-se se o ps-dominador imediato
no tiver operaes marcadas. Para encontrar o ps-dominador til mais prximo,
o algoritmo pode percorrer a rvore de ps-dominadores at encontrar um bloco
que contenha uma operao til. Como, por definio, o bloco de sada til, essa
busca deve terminar.
Depois que Dead executado, o cdigo no contm computaes inteis. Ele pode
conter blocos vazios, que podem ser removidos pelo prximo algoritmo.
um salto, de modo que Clean no pode combin-lo com B3. Seu predecessor termina
com um desvio, ao invs de um salto, de modo que Clean no pode combinar B2 com
B1 nem desdobrar seu desvio para B1.
Porm, a cooperao entre Clean e Dead pode eliminar o lao vazio. Dead usou
a dependncia de controle para marcar desvios teis. Se B1 e B3 tiverem operaes
teis, mas B2 no, ento o passo de Mark em Dead decidir que o desvio terminando
em B2 no til, pois B2 RDF(B3). Como o desvio intil, o cdigo que calcula
a condiode desvio tambm . Assim, Dead elimina todas as operaes em B2 e
converte o desvio que o termina em um salto para o seu ps-dominador til mais
prximo, B3, eliminando assim o lao original e produzindo o CFG rotulado como
Aps Dead na margem.
Dessa forma, Clean desdobra B2 para B1, para produzir o CFG rotulado como Remove B2 na margem. Essa ao tambm torna redundante o desvio ao final de B1. Clean
o reescreve com um salto, produzindo o CFG rotulado como Desdobra o desvio na
margem. Nesse ponto, se B1 o nico predecessor restante de B3, Clean agrupa os
dois blocos em um nico.
Essa cooperao mais simples e mais eficaz do que acrescentar uma transformao a
Clean que trate de laos vazios. Tal transformao poderia reconhecer um desvio de
Bi para si mesmo e, para um Bi vazio, reescrev-lo com um salto para o outro destino
do desvio. O problema est em determinar quando Bi est verdadeiramente vazio. Se
Bi no tiver operaes alm do desvio, ento o cdigo que calcula a condio de desvio
precisa estar fora do lao. Assim, a transformao s segura se o autolao (self-loop)
nunca for executado. O raciocnio sobre o nmero de execues do autolao exige
conhecimento sobre o valor em tempo de execuo da comparao, uma tarefa que,
em geral, est alm da capacidade de um compilador. Se o bloco contm operaes,
mas somente operaes que controlam o desvio, ento a transformao precisaria
reconhecer a situao com um casamento de padres. De qualquer forma, essa nova
transformao seria mais complexa do que as quatro includas em Clean. Contar
com a combinao de Dead e Clean alcana o resultado apropriado de forma mais
simples, mais modular.
Se a linguagem-fonte permitir aritmtica sobre ponteiros ou rtulos do cdigo, o compilador deve preservar
todos os blocos. Caso contrrio, pode limitar o conjunto
preservado a blocos cujos rtulos so referenciados.
REVISO DA SEO
As transformaes de cdigo normalmente criam cdigo intil ou inalcanvel.
Porm, para determinar exatamente quais operaes so mortas, preciso haver
uma anlise global. Muitas transformaes simplesmente deixam as operaes
mortas na forma IR do cdigo e contam com transformaes separadas, especializadas, como Dead e Clean, para remov-las. Assim, a maioria dos compiladores
otimizadores inclui um conjunto de transformaes para remover o cdigo morto.
Frequentemente, essas passagens so executadas vrias vezes durante a sequncia
de transformaes.
As trs transformaes apresentadas neste captulo realizam um trabalho completo de eliminao de cdigo intil e inalcanvel. A anlise subjacente, porm,
pode limitar a capacidade dessas transformaes em provar que o cdigo est
morto. Ouso dos valores baseados em ponteiro pode impedir que o compilador
determine queumvalor no usado. Os desvios condicionais podem ocorrer em
locais onde ocompilador no pode detectar o fato de que eles sempre seguem o
mesmo caminho; a Seo10.8 apresenta um algoritmo que resolve parcialmente
este problema.
QUESTES DE REVISO
1. Os programadores experientes normalmente questionam a necessidade da eliminao de cdigo intil. Eles parecem estar certos de que no escrevem cdigo que seja
intil ou inalcanvel. Quais transformaes do Captulo8 poderiam criar cdigo intil?
2. Como o compilador, ou o ligador, poderia detectar e eliminar procedimentos
inalcanveis? Que benefcios haveria com o uso desta tcnica?
de cdigo quanto locais onde ele pode colocar essas operaes. O algoritmo opera
sobre a forma IR do programa e seu CFG, ao invs da SSA. O algoritmo usa trs
conjuntos diferentes de equaes de fluxo de dados e deriva conjuntos adicionais a
partir desses resultados. Ele produz, para cada aresta no CFG, um conjunto de expresses que devem ser avaliadas ao longo desta aresta e, para cada n no CFG, um
conjunto de expresses cujas avaliaes expostas para cima devem ser removidas do
bloco correspondente. Uma estratgia de reescrita simples interpreta esses conjuntos
e modifica o cdigo.
LCM combina a movimentao de cdigo com a eliminao de computaes redundantes e parcialmente redundantes. A redundncia foi introduzida no contexto da
numerao de valor local e superlocal na Seo 8.4.1. Uma computao parcialmente redundante no ponto p se ocorrer em algum, mas no todos os caminhos que
alcanam p e nenhum de seus operandos constituintes mudar entre essas avaliaes
e p. A Figura10.3 mostra duas maneiras como uma expresso pode ser parcialmente
redundante. Na Figura10.3a, a bxc ocorre em um caminho levando ao ponto
de juno, mas no no outro. Para tornar a segunda computao redundante, a LCM
insere uma avaliao de a bc no outro caminho, como mostra a Figura10.3b.
Na Figura10.3c, a bxc redundante ao longo da aresta de volta do lao, mas
no ao longo da aresta que entra no lao. A insero de uma avaliao de a bxc
antes do lao torna a ocorrncia dentro dele redundante, como mostra a Figura10.3d.
Tornando a computao invariante no lao redundante e eliminando-a, a LCM a retira
do lao, uma otimizao chamada movimentao de cdigo invariante de lao quando
realizada por si s.
Redundante
Uma expresso e redundante em p se j tiver sido
avaliada em cada caminho que leva a p.
Parcialmente redundante
Uma expresso e parcialmente redundante em p se ela
ocorrer em alguns, mas no em todos os caminhos que
alcanam p.
Forma de cdigo
Observe que essas regras so consistentes com as de
nomeao de registrador descritas na Seo 5.4.2.
LCM baseia-se em diversas suposies implcitas sobre a forma do cdigo. Expresses textualmente idnticas sempre definem o mesmo nome. Assim, cada ocorrncia
de ri+rj sempre visa o mesmo rk. Desta forma, o algoritmo pode usar rk como um
substituto para ri+rj. Esse esquema de nomeao simplifica a etapa de reescrita;
o otimizador pode simplesmente substituir uma avaliao redundante de ri+rj por
uma cpia de rk, ao invs de criar um novo nome temporrio e inserir cpias nesse
nome aps cada avaliao prvia.
A LCM move avaliaes de expresso, e no atribuies. A disciplina de nomeao
requer uma segunda regra para variveis de programa, pois elas recebem valores de
diferentes expresses. Assim, essas variveis so definidas por operaes de cpia
do tipo registrador-para-registrador. Um modo simples de dividir o espao de nomes
entre variveis e expresses exigir que as variveis tenham subscritos menores do
que qualquer expresso, e que, em qualquer operao diferente de uma cpia, o subscrito do registrador definido deve ser maior do que os subscritos dos argumentos da
operao. Assim, em ri+rj rk, i<k e j<k. O exemplo na Figura10.4 tem
esta propriedade.
Expresses disponveis
A primeira etapa na LCM calcula expresses disponveis, de maneira semelhante
que foi definida na Seo 9.2.4. A LCM precisa de disponibilidade ao final do bloco,
e, assim, calcula AVAILOUT ao invs de AVAILIN. Uma expresso e est disponvel
na sada do bloco b se, ao longo de cada caminho de n0 at b, e tiver sido avaliada e
nenhum de seus argumentos tiver sido definido subsequentemente.
LCM calcula AVAILOUT da seguinte forma:
A LCM usa os conjuntos AVAILOUT para ajudar a determinar possveis posicionamentos para uma expresso no CFG. Se uma expresso e AVAILOUT (b), o
compilador poderia colocar uma avaliao de e ao final do bloco b e obter o resultado
produzido por sua avaliao mais recente em qualquer caminho de fluxo de controle
de n0 at b.
Se e AVAILOUT(b), ento uma das subexpresses constituintes de e foi modificada
desde a avaliao mais recente de e, e uma avaliao no final do bloco b possivelmente
produziria um valor diferente. Em vista disso, conjuntos AVAILOUT () dizem ao
compilador o quanto para a frente no CFG ele pode mover a avaliao de e, ignorando
quaisquer usos de e.
Expresses antecipveis
Para capturar informaes para a movimentao de expresses para trs, a LCM calcula
a antecipao. Lembre-se, pela Seo 9.2.4, que uma expresso antecipvel no ponto
p se, e somente se, for calculada em cada caminho que sai de p e produzir o mesmo
valor em cada uma dessas computaes. Como a LCM precisa de informaes sobre as
expresses antecipveis no incio e no final de cada bloco, refatoramos a equao para
introduzir um conjunto ANTIN(n) que mantm o conjunto de expresses antecipveis
para a entrada do bloco correspondente ao n n no CFG. A LCM inicializa os conjuntos
ANTOUT da seguinte forma:
Posicionamento posterior
O ltimo problema de fluxo de dados na LCM determina quando um posicionamento
mais cedo pode ser adiado para um ponto mais adiante no CFG enquanto alcana o
mesmo efeito. A anlise posterior formulada como um problema de fluxo de dados
para a frente no CFG com um conjunto LATERIN (n) associado a cada n e outro
conjunto LATER(i, j) associado a cada aresta i,j. A LCM inicializa os conjuntos
LATERIN da seguinte forma:
Reescrever o cdigo
A etapa final da realizao da LCM reescrever o cdigo de modo que ele aproveite o
conhecimento derivado das computaes de fluxo de dados. Para controlar o processo
de reescrita, a LCM calcula dois conjuntos adicionais, INSERT e DELETE.
O conjunto INSERT especifica, para cada aresta, as computaes que a LCM deve
inserir nessa aresta.
O compilador interpreta os conjuntos INSERT e DELETE e reescreve o cdigo conforme apresentado na Figura10.5. A LCM exclui as expresses que definem r20 e r21
de B2 e as insere na aresta de B1 a B2.
Como B1 tem dois sucessores e B2 tem dois predecessores, B1,B2 uma aresta crtica.
Assim, a LCM a divide, criando um novo bloco B2a para manter as computaes
inseridas de r20 e r21. A diviso de B1,B2 acrescenta um jump extra ao cdigo. O
trabalho subsequente na gerao de cdigo quase certamente implementar o jump
em B2a como um fall-through, eliminando qualquer custo associado a ele.
Agrupamento
Passagem que determina quando uma cpia de registrador para registrador pode ser seguramente eliminada
e os nomes de origem e destino combinados.
Observe que a LCM deixa a cpia definindo r8 em B2. Ela move expresses, e no
atribuies. (Lembre-se de que r8 uma varivel, no uma expresso.) Se a cpia for
desnecessria, o agrupamento de cpia subsequente, seja no alocador de registradores
ou como uma passagem independente, deve descobrir este fato e eliminar a operao
de cpia.
fornece um modo direto de alcanar este objetivo. Ela usa os resultados da anlise de
antecipao de maneira particularmente simples.
Se uma expresso e ANTOUT(b) para algum bloco b, isto significa que e avaliada ao longo de cada caminho que sai de b, e que a avaliao de e ao final de b
tornaria redundante a primeira avaliao ao longo de cada caminho. (As equaes
para ANTOUT garantem que nenhum dos operandos de e redefinido entre o final
de b e a prxima avaliao de e ao longo de cada caminho saindo de b.) Para reduzir
o tamanho do cdigo, o compilador pode inserir uma avaliao de e ao final de b e
substituir a primeira ocorrncia de e em cada caminho saindo de b por uma referncia
ao valor previamente calculado. O efeito desta transformao substituir vrias cpias
da avaliao de e por uma nica, reduzindo o nmero geral de operaes no cdigo
compilado.
Para substituir essas expresses diretamente, o compilador precisaria localiz-las. Ele poderia inserir e, depois resolver outro problema de fluxo de dados,
provando que o caminho de b para alguma avaliao de e est livre de definies para os operandos de e. Alternativamente, poderia atravessar cada um
dos caminhos saindo de b para encontrar o primeiro bloco onde e definido
examinando no conjunto UEEXPR do bloco. Cada uma dessas tcnicas parece
complicada.
Uma tcnica mais simples faz com que o compilador visite cada bloco b e insira uma
avaliao de e ao final de b, para cada expresso e ANTOUT(b). Se o compilador
usar uma disciplina uniforme para nomeao, conforme sugerido na discusso da
LCM, ento cada avaliao definir o nome apropriado. A aplicao subsequente da
LCM ou da numerao de valor superlocal, ento, remover as expresses recm-redundantes.
REVISO DA SEO
Os compiladores realizam movimentao de cdigo por dois motivos principais. A
movimentao de uma operao para um ponto onde ela execute menos vezes do que
em sua posio original deve reduzir o tempo de execuo. A movimentao de uma operao para um ponto onde uma ocorrncia pode abranger vrios caminhos no CFG deve
reduzir o tamanho do cdigo. Esta seo apresentou um exemplo de cada uma delas.
LCM um exemplo clssico de otimizao global controlada por fluxo de dados. Ela
identifica expresses redundantes e parcialmente redundantes, calcula o melhor local
para essas expresses, e as movimenta. Por definio, uma expresso invariante de
lao redundante ou parcialmente redundante; a LCM move uma grande classe
de expresses invariantes de lao para fora dos laos. Elevao usa uma tcnica muito
mais simples; encontra operaes que so redundantes em cada caminho saindo de
algum ponto p e substitui todas as ocorrncias redundantes por uma nica ocorrncia
em p. Assim, a elevao normalmente realizada para reduzir o tamanho do cdigo.
QUESTES DE REVISO
1. A elevao descobre a situao quando alguma expresso e existe ao longo de
cada caminho que sai do ponto p e cada uma dessas ocorrncias pode ser substituda com segurana por uma avaliao de e em p. Formule a otimizao simtrica e equivalente, abaixamento de cdigo (code sinking), que descobre quando
mltiplas avaliaes de expresso podem ser seguramente movidas para a frente
no cdigo de pontos que precedem p para p.
2. Considere o que aconteceria se voc aplicasse sua transformao de abaixamento
de cdigo durante o ligador, quando todo o cdigo para a aplicao inteira est
presente. Que efeito isso poderia ter sobre o cdigo de ligao de procedimento?
10.4ESPECIALIZAO
Na maioria dos compiladores, a forma do programa IR determinada pelo front end,
antes de qualquer anlise detalhada do cdigo. Necessariamente, isto produz um
cdigo geral que funciona em qualquer contexto que o programa em execuo poderia
encontrar. Com a anlise, porm, o compilador, em geral, pode descobrir o suficiente
para estreitar os contextos em que o cdigo deve operar. Isto cria a oportunidade para
que o compilador especialize a sequncia de operaes de forma que aproveitem seu
conhecimento do contexto em que o cdigo ser executado.
As principais tcnicas que realizam especializao aparecem em outras sees deste
livro. A propagao de constantes, descrita nas Sees 9.3.6 e 10.8, analisa um procedimento para descobrir valores que sempre tm o mesmo valor, e, depois, desdobra esses
valores diretamente na computao. A propagao de constantes interprocedimental,
introduzida na Seo 9.4.2, aplica as mesmas ideias no escopo de programa inteiro.
A reduo de fora de operador, apresentada na Seo 10.4, substitui as sequncias
indutivas de computaes dispendiosas por sequncias equivalentes de operaes mais
rpidas. A otimizao peephole, da Seo 11.5, usa o casamento de padres sobre
curtas sequncias de instrues para descobrir melhorias locais. A numerao de valor,
das Sees 8.4.1 e 8.5.1, simplifica sistematicamente a forma IR do cdigo, aplicando
identidades algbricas e desdobramento de constante local. Cada uma dessas tcnicas
implementa uma forma de especializao.
Os compiladores otimizadores contam com essas tcnicas gerais para melhorar o
cdigo. Alm disso, a maioria deles contm tcnicas de especializao que visam especificamente propriedades das linguagens-fonte ou de aplicaes que o construtor de
compiladores espera encontrar. O restante desta seo apresenta trs dessas tcnicas,
que visam ineficincias especficas em chamadas de procedimento: otimizao de
chamada de cauda, otimizao de chamada de folha e promoo de parmetros.
Durante a traduo de um procedimento folha, o compilador pode evitar inserir operaes cuja nica finalidade se preparar para chamadas subsequentes. Por exemplo, o
cdigo de prlogo de procedimento pode salvar o endereo de retorno de um registrador
em um slot no AR. Essa ao desnecessria, a menos que o prprio procedimento
faa outra chamada. Se o registrador que mantm o endereo de retorno for necessrio
para alguma outra finalidade, o alocador de registradores pode derramar o valor. De
modo semelhante, se a implementao usa um display para fornecer endereabilidade
para variveis no locais, conforme descrevemos na Seo 6.4.3, ela pode evitar a
atualizao do display na sequncia de prlogo.
O alocador de registradores deve tentar usar registradores de salvamentos do chamador
antes dos registradores de salvamentos do procedimento chamado em um procedimento
folha. Se chegar ao ponto de poder deixar os registradores de salvamento do procedimento chamado intocveis, pode evitar o cdigo de salvamento e restaurao para eles
no prlogo e eplogo. Em pequenos procedimentos folha, o compilador pode ser capaz
de evitar todo o uso dos registradores de salvamento do procedimento chamado. Se o
compilador tiver acesso aos procedimentos chamador e chamado, poder fazer melhor;
para procedimentos folha que precisam de menos registradores do que o conjunto de
salvamento do chamador inclui, tambm poder evitar alguns dos salvamentos e restauraes de registrador no procedimento chamador.
Alm disso, o compilador pode evitar o overhead de runtime da alocao de registro
de ativao para os procedimentos folha. Em uma implementao em que o heap
aloca ARs, esse custo pode ser significativo. Em uma aplicao com nica thread de
controle, o compilador pode alocar estaticamente o AR de qualquer procedimento
folha. Um compilador mais agressivo poderia alocar um AR esttico que seja grande
o suficiente para trabalhar para qualquer procedimento folha e fazer com que todos os
procedimentos folha compartilhem esse AR.
Se o compilador tiver acesso ao procedimento folha e a seus chamadores, pode alocar
espao para o AR do procedimento folha em cada um dos ARs de seus chamadores.
Esse esquema amortiza o custo da alocao de AR por pelo menos duas chamadas a
invocao do chamador e a chamada ao procedimento folha. Se o chamador invocar o
procedimento folha vrias vezes, as economias so multiplicadas.
REVISO DA SEO
A especializao inclui muitas tcnicas eficazes para adaptar as computaes de uso
geral aos seus contextos detalhados. Outros captulos e sees apresentam poderosas
tcnicas de especializao global e regional, como propagao de constantes,
otimizao peephole e reduo de fora de operador.
Esta seo concentrou-se nas otimizaes que o compilador pode aplicar ao cdigo
envolvido em uma chamada de procedimento. A otimizao de chamada de cauda
uma ferramenta valiosa, que converte a recurso de cauda para uma forma que
compete com a iterao convencional em termos de eficincia; e tambm se aplica a
chamadas de cauda no recursivas. Os procedimentos folha oferecem oportunidades
especiais para melhoria, pois o procedimento chamado pode omitir partes importantes da sequncia de ligao padro. Promoo de parmetros um exemplo de
uma classe de transformaes importantes que removem ineficincias relacionadas a
referncias ambguas.
QUESTES DE REVISO
1. Muitos compiladores incluem uma forma simples de reduo de fora, em que
operaes individuais que possuem um operando de valor constante so substitudas por outras operaes mais eficientes, menos gerais. O exemplo clssico
substituir uma multiplicao de inteiros de um nmero positivo por uma srie de
shifts e adds. Como voc poderia desdobrar essa transformao em numerao
de valor local?
2. A substituio em linha poderia ser uma alternativa para as otimizaes de
chamada de procedimento nesta seo. Como voc poderia aplicar a substituio
em linha em cada caso? Como o compilador poderia escolher a alternativa mais
lucrativa?
Promoo
Categoria de transformaes que move um valor
ambguo para um nome escalar local para exp-lo
alocao de registrador.
tunidades, pois est limitada a EBBs. Lembre-se de que o algoritmo SVN propaga
informaes ao longo de cada caminho por um EBB. Por exemplo, no fragmento de
CFG mostrado na margem, a SVN processar os caminhos (B0,B1,B2) e (B0,B1,B3).
Assim, otimiza tanto B2 quanto B3 no contexto do caminho de prefixo (B0, B1). Como
B4 forma seu prprio EBB degenerado, a SVN o otimiza sem contexto anterior.
Sob o ponto de vista algortmico, a SVN inicia cada bloco com uma tabela que inclui
os resultados de todos os predecessores em seu caminho EBB. O bloco B4 no tem
predecessores, de modo que comea sem contexto anterior. Para melhorar essa situao,
temos que responder pergunta: em qual estado B4 poderia confiar? B4 no pode
confiar em valores calculados em B2 ou B3, pois nenhum se encontra em cada caminho
que alcana B4. Ao contrrio, B4 pode confiar nos valores calculados em B0 e B1, pois
ocorrem em cada caminho que o alcana. Assim, poderamos estender a numerao de
valor para B4 com informaes sobre clculos em B0 e B1. Porm, devemos considerar
o impacto de atribuies nos blocos intermedirios, B2 ou B3.
Considere uma expresso, x+y, que ocorre ao final de B1 e novamente no incio de B4.
Se nem B2 nem B4 redefinirem x ou y, ento a avaliao de x+y em B4 redundante,
e o otimizador pode reutilizar o valor calculado em B1. Por outro lado, se um desses
blocos redefine x ou y, ento a avaliao de x+y em B4 calcula um valor distinto da
avaliao em B1, e a avaliao no redundante.
Felizmente, o espao de nomes SSA codifica exatamente essa distino. Em SSA,
um nome que usado no mesmo bloco Bi s pode entrar em Bi de duas maneiras:
ou ele definido por uma funo- no topo de Bi, ou definido em algum bloco
que domina Bi. Assim, uma atribuio a x em B2 ou B3 cria um novo nome para x e
fora a insero de uma funo- para x no incio de B4. Esta funo- cria um novo
nome SSA para x, e o processo de renomeao muda o nome SSA usado no clculo
subsequente de x+y. Assim, a forma SSA codifica a presena ou ausncia de uma
atribuio intermediria em B2 ou B3 diretamente nos nomes usados na expresso.
Nosso algoritmo pode contar com os nomes SSA para evitar este problema.
A outra pergunta importante que devemos responder antes que possamos estender a
SVN para regies maiores : dado um bloco como B4, como localizamos o predecessor mais recente com informaes que o algoritmo possa usar? A informao de
dominncia, bastante discutida nas Sees 9.2.1 e 9.3.2, captura precisamente este
efeito. DOM(B4)={B0,B1,B4}. O dominador imediato de B4, definido como o n em
(DOM(B4) B4) que est mais prximo de B4, B1, o ltimo n que ocorre em todo o
caminho desde o n de entrada B0 at B4.
A tcnica de numerao de valor baseada em dominador (DVNT) fundamenta-se nas
ideias por trs da SVN; usa uma tabela hash com escopo para manter nmeros de valor;
abre um novo escopo para cada bloco e os descarta quando no so mais necessrios.
A DVNT, na realidade, usa nomes SSA como nmeros de valor; assim, o nmero de
valor para uma expresso a ixb j o nome SSA definido na primeira avaliao
deaibj. (Ou seja, se a primeira avaliao ocorre em tk aixbj, ento o nmero de
valor para aibj tk.)
A Figura10.6 mostra o algoritmo; ele toma a forma de um procedimento recursivo
que o otimizador chama no bloco de entrada de um procedimento; segue o CFG para
o procedimento, representado pela rvore de dominadores, e o fluxo de valores na
forma SSA. Para cada bloco B, a DVNT realiza trs etapas: processa as funes-
em B, se houver alguma; numera as atribuies com nmeros de valor e propaga
informaes para os sucessores de B; e recorre sobre os filhos de B na rvore de
dominadores.
Processar as funes- em B
A DVNT precisa atribuir a cada funo- p um nmero de valor. Se p no tiver significado ou seja, todos os seus argumentos tiverem o mesmo nmero de valor , a
DVNT define seu nmero de valor como o nmero de valor de um de seus argumentos
e exclui p. Se p redundante ou seja, produz o mesmo nmero de valor que outra
funo- em B , a DVNT atribui a p o mesmo nmero de valor da funo- que ela
duplica, e, ento, exclui p.
Caso contrrio, a funo- calcula um novo valor. Surgem dois casos. Os argumentos
de p possuem nmeros de valor, mas a combinao especfica de argumento no foi
vista antes nesse bloco, ou um ou mais dos argumentos de p no tm nmero de valor.
Este ltimo caso pode surgir a partir de uma aresta de volta no CFG.
Processar as atribuies em B
REVISO DA SEO
A eliminao de redundncia opera sobre a suposio de que mais rpido reutilizar
um valor do que recalcul-lo. Com base nessa suposio, esses mtodos identificam o
mximo de computaes redundantes possvel e eliminam a computao duplicada.
As duas principais noes de equivalncia usadas por essas transformaes so a
identidade de valor e a identidade de nome. Esses diferentes testes de identidade
produzem diferentes resultados.
A numerao de valor e a LCM eliminam computao redundante. LCM elimina a
avaliao de expresso redundante e parcialmente redundante; mas no elimina
atribuies. A numerao de valor no reconhece redundncias parciais, mas pode
eliminar atribuies. Alguns compiladores usam uma tcnica baseada em valor,
como DVNT, para descobrir redundncia e ento codificar essa informao no espao
de nomes para uma transformao baseada em nome, como LCM. Na prtica, essa
tcnica combina os pontos fortes das duas ideias.
QUESTES DE REVISO
1. O algoritmo DVNT semelhante fase de renomeao do algoritmo de construo de SSA. Voc consegue reformular esta fase de modo que realize a numerao
de valor enquanto renomeia valores? Que impacto essa mudana teria sobre o
tamanho da forma SSA para um procedimento?
2. O algoritmo DVNT no propaga um valor ao longo de uma aresta de fechamento
de lao uma aresta de volta no grafo de chamada. LCM propagar informaes
ao longo dessas arestas. Escreva vrios exemplos de expresses redundantes que
uma verdadeira tcnica global, como LCM, consegue encontrar e que a DVNT no.
10.6.4Renomeao
A maior parte das transformaes escalares reescreve ou reordena as operaes no
cdigo. Vimos, em vrios pontos no texto, que a escolha de nomes pode esconder ou
expor oportunidades para melhoria. Por exemplo, na LVN, a converso dos nomes em
um bloco para o espao de nomes SSA exps algumas oportunidades para reutilizao
que, de outra forma, seriam difceis de capturar.
Para muitas transformaes, a construo cuidadosa do espao de nomes correto
pode expor oportunidades adicionais, seja tornando mais fatos visveis anlise seja
evitando alguns dos efeitos colaterais que surgem da reutilizao de armazenamento.
Como exemplo, considere a LCM. Por contar com a anlise de fluxo de dados para
identificar oportunidades, a anlise conta com uma noo de identidade lxica
operaes redundantes precisam ter a mesma operao e seus operandos os mesmos
nomes. Assim, a LCM no pode descobrir que x+x e 2 x tm o mesmo valor, ou que
x+x e x+y tm o mesmo valor quando x=y.
Para melhorar os resultados da LCM, o compilador pode codificar a identidade de
valor no espao de nomes antes de aplic-la. O compilador usaria uma tcnica deredundncia baseada em valor, como DVNT, e depois reescreveria o espao de nomes
de modo que valores equivalentes compartilhem o mesmo nome. Codificando a
REVISO DA SEO
Como vimos no Captulo7, a forma da IR para um procedimento tem efeito sobre
o cdigo que o compilador pode gerar para ele. As tcnicas discutidas nesta seo
criam oportunidades para outras otimizaes mudando a forma do cdigo. Elas
utilizam replicao, reescrita seletiva e renomeao para criar locais no cdigo que
sejam receptivos melhoria por transformaes especficas.
A clonagem, no nvel de bloco ou de procedimento, consegue seus resultados
eliminando os efeitos danosos que ocorrem nos pontos de juno do fluxo de controle. Ao eliminar arestas, no CFG ou no grafo de chamada, a clonagem tambm cria
oportunidades para mesclar cdigo. O loop unswitching realiza a movimentao de
cdigo especializado de estruturas de controle, mas seu principal benefcio vem da
criao de laos mais simples, que no contm fluxo de controle condicional. Este
ltimo benefcio melhora os resultados de transformaes que variam da LCM ao
escalonamento de instrues. A renomeao uma ideia poderosa, com aplicaes
amplas; o caso especfico da codificao de identidade de valor na identidade lxica
tem sido provado em diversos compiladores bem conhecidos.
QUESTES DE REVISO
1. A clonagem de superbloco cria novas oportunidades para outras otimizaes.
Considere o balanceamento de altura de rvore. Quanto a clonagem de superbloco pode ajudar? Voc pode imaginar uma transformao que venha aps a
clonagem de superbloco que exponha mais oportunidades para o balanceamento
de altura de rvore? Para a SVN, como os resultados do seu uso aps a clonagem
se comparam com os da execuo da LCM sobre o mesmo cdigo?
2. A clonagem de procedimento ataca algumas das mesmas ineficincias que a substituio em linha. Existe um papel para essas duas transformaes em um nico
compilador? Quais so os benefcios e riscos em potencial de cada transformao?
Como um compilador poderia escolher entre elas?
O GRAFO SSA
Em alguns algoritmos, a visualizao da forma SSA do cdigo como um grafo
simplifica a discusso ou a implementao. O algoritmo para reduo de fora
interpreta a forma SSA do cdigo como um grafo.
Na forma SSA, cada nome tem uma definio exclusiva, de modo que um nome
especifica uma operao particular no cdigo que calculou seu valor. Cada uso de
um nome ocorre em uma operao especfica, de modo que este uso pode ser
interpretado como uma cadeia que vai do uso at sua definio. Assim, uma simples
tabela de pesquisa que mapeia nomes s operaes que os definem cria uma cadeia
de cada uso at a definio correspondente. O mapeamento de uma definio at as
operaes que a utilizam um pouco mais complexo. Porm, esse mapeamento pode
ser facilmente construdo durante a fase de renomeao da construo da SSA.
Desenhamos grafos SSA com arestas que vo de um uso at sua definio
correspondente, o que indica o relacionamento implicado pelos nomes SSA. O
compilador precisa percorrer as arestas nos dois sentidos. A reduo de fora se move,
principalmente, dos usos para as definies. O algoritmo SCCP transmite valores das
definies at os usos. O construtor de compiladores pode facilmente acrescentar as
estruturas de dados necessrias para permitir a travessia nos dois sentidos.
Em conceito, a SCCP opera de um modo simples. Inicializa as estruturas de dados, e percorre dois grafos, o CFG e o grafo SSA. Ela propaga informaes de acessibilidade no
CFG e informaes de valor no grafo SSA. Termina quando a informao de valor alcana
um ponto fixo; como o reticulado de propagao de constante muito superficial, ela
termina rapidamente. Combinando esses dois tipos de informao, a SCCP pode descobrir
tanto cdigo inalcanvel quanto valores constantes que o compilador simplesmente no
poderia descobrir com qualquer combinao da SSCP e eliminao de cdigo inalcanvel.
Para simplificar a explicao da SCCP, consideramos que cada bloco no CFG representa
apenas uma instruo e mais algumas funes- opcionais. Um n do CFG com um
nico predecessor mantm, ou uma instruo de atribuio, ou um desvio condicional.
Um n do CFG com mltiplos predecessores mantm um conjunto de funes-,
seguido por uma atribuio ou um desvio condicional.
Em detalhes, a SCCP muito mais complexa do que a SSCP ou a eliminao de cdigo
inalcanvel. O uso de dois grafos introduz trabalho adicional. Fazer com que o fluxo
de valores dependa da acessibilidade introduz trabalho adicional para o algoritmo. O
resultado um algoritmo poderoso, porm complexo.
O algoritmo procede da seguinte forma: inicializa cada campo Value com e marca
cada aresta do CFG como no executada; inicializa duas listas de trabalho (worklists),
uma para as arestas do CFG e a outra para as do grafo SSA. A worklist do CFG recebe
o conjunto de arestas que saem do n de entrada do procedimento, n0. A worklist da
SSA recebe o conjunto vazio.
Aps a fase de inicializao, o algoritmo repetidamente apanha uma aresta de uma
das duas worklists e a processa. Para uma aresta (m,n) do CFG, a SCCP determina se
ela est marcada como executada. Se (m,n) assim estiver marcada, a SCCP no toma
outra ao para (m,n). Se (m,n) est marcada como no executada, ento a SCCP a
marca como executada e avalia todas as funes- no incio do bloco n. Em seguida,
determina se o bloco n foi alcanado anteriormente ao longo de outra aresta. Se no,
avalia a atribuio ou desvio condicional em n. Esse processamento pode acrescentar
arestas a qualquer uma das worklists.
Para uma aresta da SSA, o algoritmo, primeiro, verifica se o bloco de destino alcanvel. Se for, a SCCP chama um dentre EvaluatePhi, EvaluateAssign ou
EvaluateConditional, com base no tipo de operao que usa o nome da SSA.
Quando a SCCP tiver que avaliar uma atribuio ou uma condicional sobre o reticulado
de valores, segue o mesmo esquema usado na SSCP discutido na Seo 9.3.6. Toda
vez que o valor do reticulado para uma definio mudar, todos os usos desse nome so
acrescentados worklist da SSA.
Como a SCCP s propaga valores em blocos que ela j provou serem executveis,
evita o processamento dos blocos no alcanveis. Como cada etapa de propagao
de valor protegida por um teste sobre a marca de executvel para a aresta que entra,
valores de blocos inalcanveis no fluem desses blocos. Assim, valores de blocos
inalcanveis no tm papel na definio dos valores de reticulado em outros blocos.
Aps a etapa de propagao, uma passagem final necessria para substituir operaes que
possuem operandos com tags Value diferentes de . Ela pode especializar muitas dessas
operaes, e tambm deve reescrever desvios que possuem resultados conhecidos por
operaes de salto apropriadas. Outras passagens podem remover o cdigo inalcanvel
(ver Seo10.2). O algoritmo no pode reescrever o cdigo at que a propagao termine.
Eficcia
A SCCP pode encontrar constantes que o algoritmo SSCP no consegue. De modo
semelhante, ela pode descobrir cdigo inalcanvel que nenhuma combinao dos
algoritmos da Seo10.2 pode. Ela deriva seu poder da combinao da anlise de
acessibilidade com a propagao dos valores de reticulado; pode eliminar algumas
arestas do CFG porque os valores de reticulado so suficientes para determinar
qual caminho um desvio toma; e pode ignorar arestas da SSA que surgem a partir
de operaes inalcanveis (inicializando essas definies como ), pois essas
operaes sero avaliadas se o bloco torna-se marcado como alcanvel. O poder
da SCCP vem da interao entre essas anlises propagao de constante e acessibilidade.
Se a acessibilidade no afetou os valores finais do reticulado, ento os mesmos efeitos
poderiam ser alcanados realizando a propagao de constante (e reescrevendo como
saltos os desvios com valor constante) seguida pela eliminao de cdigo inalcanvel.
Se a propagao de constante no teve papel algum na acessibilidade, ento os mesmos
efeitos poderiam ser alcanados pela outra ordem eliminao de cdigo inalcanvel
seguida por propagao de constante. O poder da SCCP para encontrar simplificaes
alm dessas combinaes vem exatamente do fato de que as duas otimizaes so
interdependentes.
O cdigo est na forma SSA semipodada; as variveis puramente locais (r2, r2, r3
e r4) no possuem subscritos nem funes-. Observe como a referncia a a(i) se
expande para quatro operaes o subI, multI e addI que calculam (i-1)x4
@a e o load que define r4.
Para cada iterao, essa sequncia de operaes calcula o endereo de a(i) a partir
do zero como uma funo da varivel de ndice de lao i. Considere as sequncias de
valores assumidas por ri1, r1, r2 e r3.
Constante de regio
Um valor que no varia dentro de determinado lao
uma constante de regio para esse lao.
Varivel de induo
Um valor que aumenta ou diminui por uma quantidade
constante em cada iterao de um lao uma varivel
de induo.
Operaes candidatas
Fundamentos
A reduo de fora procura contextos em que uma operao, como uma multiplicao,
executada dentro de um lao e seus operandos so (1) um valor que no varia nesse
lao, chamado constante de regio, e (2) um valor que varia sistematicamente de
uma iterao para outra, chamado varivel de induo. Ao encontrar esta situao,
ela cria uma nova varivel de induo que calcula a mesma sequncia de valores da
multiplicao original de modo mais eficiente. As restries na forma dos operandos
da operao de multiplicao garantem que essa nova varivel de induo possa ser
calculada usando adies, ao invs de multiplicaes.
Chamamos uma operao que pode assim ser reduzida de operao candidata. Para
simplificar a apresentao do OSR, consideramos apenas operaes candidatas que
tm uma das cinco formas dispostas na margem, onde c uma constante de regio e
i uma varivel de induo. A chave para encontrar e reduzir operaes candidatas a
identificao eficaz de constantes de regio e variveis de induo. Uma operao
uma candidata se, e somente se, tiver uma dessas formas, incluindo as restries sobre
operandos.
Uma constante de regio pode ser uma constante literal, como 10, ou um valor invariante de lao, ou seja, que no modificado dentro do lao. Com o cdigo na forma
SSA, o compilador pode determinar se um argumento invariante de lao verificando
o local de sua nica definio sua definio deve dominar a entrada para o lao que
define a varivel de induo. OSR pode verificar essas duas condies em tempo constante. A realizao da LCM e propagao de constante antes da reduo de fora pode
expor mais constantes de regio.
Intuitivamente, uma varivel de induo uma varivel cujos valores no lao formam
uma progresso aritmtica. Para os propsitos deste algoritmo, podemos usar uma
definio muito mais especfica e restrita: uma varivel de induo um componente
fortemente conectado (SCC Strongly Connected Component) do grafo SSA em
que cada operao que atualiza seu valor um, dentre (1) uma varivel de induo
mais uma constante de regio, (2) uma varivel de induo menos uma constante
de regio, (3) uma funo-, ou (4) uma cpia registrador-para-registrador de outra
varivel de induo. Embora esta definio seja muito menos genrica do que as
definies convencionais, suficiente para permitir que o algoritmo OSR encontre
e reduza operaes candidatas. Para identificar variveis de induo, OSR encontra
SCCs no grafo SSA e itera sobre eles para determinar se cada operao no SCC de
um desses quatro tipos.
Como OSR define variveis de induo no grafo SSA e constantes de regio relativas
a um lao no CFG, o teste para determinar se um valor constante em relao ao lao
contendo uma varivel de induo especfica complicado. Considere uma operao o
na forma x ixc, onde i uma varivel de induo. Para que o seja um candidato
para reduo de fora, c precisa ser uma constante de regio em relao ao lao mais
externo em que i varia. Para testar se c tem essa propriedade, OSR precisa relacionar
o SCC para i no grafo SSA de volta para um lao no CFG.
OSR encontra o n do grafo SSA com o nmero de ps-ordem reversa mais baixo no
SCC definindo i. Ele considera esse n como sendo o cabealho do SCC e registra
este fato no campo de cabealho de cada n do SCC. (Qualquer n no grafo SSA que
no faz parte de uma varivel de induo tem seu campo de cabealho definido como
null.) Na forma SSA, o cabealho da varivel de induo a funo- no incio
do lao mais externo em que ela varia. Em uma operao x ixc, onde i uma
varivel de induo, c uma constante de regio se o bloco CFG que contm sua
definio dominar o bloco que contm o cabealho de i. Essa condio garante que c
invariante no lao mais externo em que i varia. Para realizar esse teste, a construo
da SSA deve produzir um mapeamento de cada n da SSA para o bloco do CFG onde
ele foi originado.
O campo de cabealho desempenha papel crtico na determinao de se uma operao
pode ou no ter a fora reduzida. Quando OSR encontra uma operao x yz,
pode determinar se y uma varivel de induo seguindo a aresta do grafo SSA para
a definio de y e inspecionando seu campo de cabealho. Um campo de cabealho
null indica que y no uma varivel de induo. Se tanto y quanto z tiverem campos
de cabealho null, a operao no pode ter a fora reduzida.
Se dentre y ou z um tiver um campo de cabealho no null, ento OSR usa este campo
de cabealho para determinar se o outro operando uma constante de regio. Suponha
que o cabealho de y seja no null. Para encontrar o bloco CFG para a entrada do
lao mais externo onde y varia, OSR consulta o mapeamento SSA-para-CFG indexado
pelo cabealho de y. Se o bloco CFG contendo a definio de z domina o bloco CFG
do cabealho de y, ento z uma constante de regio relativa varivel de induo y.
O algoritmo
Para realizar a reduo de fora, OSR precisa examinar cada operao e determinar se
um de seus operandos uma varivel de induo, e o outro, uma constante de regio.
Se a operao atender a esses critrios, OSR pode reduzi-la criando uma nova varivel
de induo que calcule os valores necessrios e substituindo a operao por uma cpia
registrador-para-registrador dessa nova varivel de induo. (Ele deve evitar a criao
de variveis de induo duplicadas.)
Com base na discusso anterior, sabemos que OSR pode identificar variveis de induo
localizando SCCs no grafo SSA. E pode, ainda, descobrir uma constante de regio
examinando a definio do valor. Se a definio resulta de uma operao imediata,
ou seu bloco CFG domina o bloco CFG do cabealho da varivel de induo, ento o
valor uma constante de regio. A chave colocar essas ideias juntas em um algoritmo
eficiente.
OSR usa o localizador de regio fortemente conectada de Tarjan para controlar o
processo inteiro. Como vemos na Figura10.13, OSR toma um grafo SSA como seu
argumento e aplica repetidamente o localizador de regio fortemente conectada, DFS,
a ele. (Esse processo termina quando DFS tiver visitado cada n em G.)
DFS, realiza uma busca em profundidade no grafo SSA; atribui a cada n um nmero,
correspondente ordem em que visita o n; empilha cada n e o rotula com o menor
nmero de profundidade de um n que pode ser alcanado a partir de seus filhos.
Quando retorna do processamento dos filhos, se o n mais baixo alcanvel a partir de
n tiver o nmero de n, ento n o cabealho de um SCC. DFS remove os ns da pilha
at alcanar n; todos esses ns so membros do SCC.
DFS remove SCCs da pilha em uma ordem que simplifica o restante do OSR. Quando
um SCC removido da pilha e passado para Process, DFS j visitou todos os seus
filhos no grafo SSA. Se interpretarmos o grafo SSA de modo que suas arestasvo
dos usos para as definies, como mostra o grafo SSA na Figura10.12, ento as
operaes candidatas so encontradas somente depois que seus operandos tiverem sido
passados para Process. Quando Process encontra uma operao que candidata
para reduo de fora, seus operandos j foram classificados. Assim, Process pode
examinar operaes, identificar candidatos e invocar Replace para reescrev-las na
forma de fora reduzida durante a busca em profundidade.
Operaes candidatas
DFS passa cada SCC para Process. Se o SCC consiste em um nico n n que tem
a forma de uma operao candidata, mostrada na margem, Process passa n para
Replace, junto com sua varivel de induo, iv, e sua constante de regio, rc.
Reescrevendo o cdigo
A parte restante do OSR implementa a etapa de reescrita. Tanto Process quanto
ClassifyIV chamam Replace para realiz-la. A Figura10.14 mostra o cdigo
para Replace e suas funes de suporte Reduce e Apply.
Replace usa trs argumentos, um n do grafo SSA n, uma varivel de induo iv,
e uma constante de regio rc. Os dois ltimos so operandos de n. Replace chama
Reduce para reescrever a operao representada por n. Em seguida, ele substituir n
por uma operao de cpia do resultado produzido por Replace. Ele define o campo
de cabealho de n e retorna.
Reduce e Apply fazem a maior parte do trabalho, usando uma tabela hash para evitar
a insero de operaes duplicadas. Como OSR atua sobre nomes SSA, uma nica
tabela hash global suficiente, que pode ser inicializada no OSR antes da primeira chamada ao DFS. Insert acrescenta entradas tabela hash; Lookup consulta a tabela.
O plano para Reduce simples. Ele usa um cdigo de operao (opcode) e seus dois
operandos, e cria uma nova varivel de induo para substituir a computao ou retorna
o nome de uma varivel de induo criada anteriormente para a mesma combinao
de cdigo de operao e operandos, e consulta a tabela hash para evitar trabalho duplicado. Se a varivel de induo desejada no estiver na tabela hash, Reduce cria
a varivel de induo em um processo de duas etapas. Primeiro, chama Clone para
copiar a definio para iv, a varivel de induo na operao que est sendo reduzida.
Em seguida, recorre sobre os operandos dessa nova definio.
Esses operandos podem estar em duas categorias. Se o operando estiver definido dentro
do SCC, faz parte da iv, de modo que Reduce recorre sobre esse operando. Isso forma
a nova varivel de induo clonando seu caminho pelo SCC da varivel de induo
original iv. Um operando definido fora do SCC precisa ser o valor inicial de iv ou
um valor pelo qual iv incrementada. O valor inicial precisa ser um argumento de
funo- de fora do SCC; Reduce chama Apply sobre cada argumento desse tipo.
Reduce pode deixar um incremento de varivel de induo intacto, a menos que a
operao candidata seja uma multiplicao. Para esta, Reduce deve calcular um novo
incremento como o produto do incremento antigo e a constante de regio original rc.
Ele invoca Apply para gerar este clculo.
Apply usa um cdigo de operao e dois operandos, localiza um ponto apropriado
no cdigo e insere esta operao. Ele retorna o novo nome SSA para o resultado
dessa operao. Alguns detalhes precisam de mais explicao. Se essa nova operao
for, por si s, uma candidata, Apply invoca Reduce para tratar dela. Caso contrrio, Apply recebe um novo nome, insere a operao e retorna o resultado. (Se
tanto o1 quanto o2 forem constantes, Apply pode avaliar a operao e inserir um
load imediato.) Apply localiza um bloco apropriado para a nova operao usando
a informao de dominncia. Intuitivamente, a nova operao deve entrar em um
bloco dominado pelos blocos que definem seus operandos. Se um operando for uma
constante, Apply pode duplicar a constante no bloco que define o outro operando.
Caso contrrio, os dois operandos precisam ter definies que dominam o bloco de
cabealho, e um deve dominar o outro. Apply pode inserir a operao imediatamente
aps esta ltima definio.
De volta ao exemplo
Considere o que acontece quando OSR encontra o exemplo da Figura10.12. Suponha
que ele comece com o n rotulado como rs2 e visite os filhos da esquerda antes dos
da direita. Ele recorre pela cadeia de operaes que definem r4, r3, r2, r1 e ri1. Em
ri1, recorre sobre ri2 e depois ri0. Ele encontra os dois SCCs de n nico que contm
a constante literal um. Nenhuma candidata, de modo que Process as marca como
variveis no de induo definindo seus cabealhos como null.
O primeiro SCC no trivial que DFS descobre contm ri1 e ri2. Todas as operaes so
atualizaes vlidas para uma varivel de induo, de modo que ClassifyIV marca
cada n como uma varivel de induo, definindo seu campo de cabealho para que
aponte para o n com o nmero mais baixo em profundidade do SCC o n para ri1.
Agora, DFS retorna ao n para r1. Seu filho da esquerda uma varivel de induo e o
da direita, uma constante de regio, de modo que invoca Reduce para criar uma varivel
de induo. Neste caso, r1 ri1 1, de modo que a varivel de induo tem um valor
inicial igual a um a menos do que o valor inicial da antiga varivel de induo, ou zero.
O incremento o mesmo. A Figura10.15 mostra o SCC que Reduce e Apply criam,
sob o rtulo para r1. Finalmente, a definio de r1 substituda por uma operao
de cpia, r1 rt1. A operao de cpia marcada como uma varivel de induo.
Em seguida, DFS descobre o SCC que consiste no n rotulado como r2. Process
descobre que ele um candidato porque seu operando da esquerda (a cpia que agora
define r1) uma varivel de induo e o da direita uma constante de regio. Process
invoca Replace para criar uma varivel de induo que tem o valor r14. Reduce
e Apply clonam a varivel de induo para r1, ajustam o incremento, pois a operao
uma multiplicao, e acrescentam uma cpia a r2.
DFS em seguida passa o n de r3 para Process, criando outra varivel de induo
com @a como seu valor inicial e copia este valor para r3.
Process trata do load, seguido pelo SCC que calcula a soma. E descobre que nenhuma dessas operaes so candidatas.
Finalmente, OSR invoca DFS no n no visitado para o cbr. DFS visita a comparao,
a varivel de induo marcada anteriormente e a constante 100. Nenhuma outra reduo
acontece depois disso.
O grafo SSA na Figura10.15 mostra todas as variveis de induo criadas por este
processo. As variveis de induo rotuladas com para r1 e para r2 esto mortas.
A varivel de induo para i estaria morta, exceto pelo teste de fim de lao ainda a
utilizar. Para eliminar essa varivel de induo, o compilador pode aplicar a substituio
de teste de funo linear para transferir o teste para a varivel de induo para r3.
As operaes que LFTR visa comparam o valor de uma varivel de induo com uma
constante de regio. OSR examina cada operao no programa para determinar se
uma candidata para reduo de fora. Ele pode montar, de modo fcil e pouco dispendioso, uma lista de todas as operaes de comparao que envolvem variveis
de induo. Aps OSR terminar seu trabalho, LFTR deve revisitar cada uma dessas
comparaes. Se o argumento varivel de induo de uma comparao teve a fora
reduzida por OSR, LFTR deve redirecionar a comparao para usar a nova varivel
de induo.
Para facilitar este processo, Reduce pode registrar o relacionamento aritmtico que
usa para derivar cada nova varivel de induo. Ele pode inserir uma aresta LFTR especial a partir de cada n na varivel de induo original at o n correspondente em
seu equivalente reduzido e rotul-lo com a operao e constante de regio da operao
candidata responsvel por criar essa varivel de induo. A Figura10.16 mostra o grafo
SSA com essas arestas adicionais em preto. A sequncia de redues no exemplo cria
uma cadeia de arestas rotuladas. Comeando da varivel de induo original, encontramos os rtulos 1, x4 e +@a.
Quando LFTR encontra uma comparao que deve ser substituda, pode seguir as
arestas a partir de seu argumento varivel de induo at a varivel de induo final
que resultou de uma cadeia de uma ou mais redues. A comparao deve usar essa
varivel de induo com uma nova constante de regio apropriada.
Os rtulos nas arestas LFTR descrevem a transformao que deve ser aplicada constante de regio original para derivar a nova constante de regio. No exemplo, a trilha
de arestas leva de ri2 a rt8 e produz o valor (100 1)x4+@a para o teste transformado. A Figura10.16 mostra as arestas e o teste reescrito.
Esta verso da LFTR simples, eficiente e eficaz. Ela conta com a colaborao de
OSR para identificar comparaes que poderiam ser redirecionadas e para registrar as
redues enquanto as aplica. Usando essas duas estruturas de dados, a LFTR pode encontrar comparaes para redirecionar, encontrar o local apropriado para redirecion-las
e encontrar a transformao necessria para o argumento constante da comparao.
Sequncia de otimizao
Um conjunto de otimizaes e uma ordem de sua
aplicao.
O problema da sequncia de otimizao surge porque a eficcia de qualquer transformao dada depende de diversos fatores.
1. A oportunidade que a transformao visa aparece no cdigo? Se no, a transformao no pode melhorar o cdigo.
2. Uma transformao anterior escondeu ou encobriu essa oportunidade? Por exemplo, a otimizao de identidades algbricas na LVN pode converter 2xa em
uma operao shift, que substitui uma operao comutativa por uma otimizao
no comutativa mais rpida. Qualquer transformao que precisa de comutatividade para efetuar sua melhoria poderia ver oportunidades perdidas pela aplicao
anterior da LVN.
3. Alguma outra transformao j eliminou a ineficincia? As transformaes
possuem efeitos sobrepostos e idiossincrticos; por exemplo, a LVN consegue
alguns dos efeitos da propagao de constante global e o desenrolamento de lao
alcana efeito semelhante clonagem de superbloco. O construtor de compiladores poderia incluir as duas transformaes por seus efeitos no superpostos.
As interaes entre transformaes tornam difcil prever a melhoria a partir da
aplicao de qualquer transformao isolada ou de qualquer sequncia de transformaes.
Alguns compiladores de pesquisa tentam descobrir boas sequncias de otimizao. As
abordagens variam em detalhamento e em tcnica. Os diversos sistemas tm procurado
sequncias nos nveis de bloco, de arquivo fonte e de programa inteiro. A maior parte
desses sistemas tem usado algum tipo de busca sobre o espao das sequncias de otimizao.
O espao de potenciais sequncias de otimizao imenso. Por exemplo, se o compilador escolhe uma sequncia de tamanho 10 a partir de um pool de 15 transformaes,
ele tem 1015 sequncias possveis que pode gerar um nmero impraticavelmente
grande para explorar. Assim, os compiladores que procuram boas sequncias usam
tcnicas heursticas para amostrar partes menores do espao de busca. Em geral,
essas tcnicas encontram-se em trs categorias: (1) algoritmos genticos adaptados
para atuar como buscas inteligentes, (2) algoritmos de busca aleatria, e (3) tcnicas
estatsticas de aprendizado de mquina. Todas as trs abordagens tm se mostrado
promissoras.
Apesar do imenso tamanho dos espaos de busca, algoritmos de busca bem ajustados
podem encontrar boas sequncias de otimizao com 100 a 200 sondagens do espao
de busca. Embora esse nmero ainda no seja prtico, refinamentos adicionais podem
reduzir o nmero de sondagens a um nvel prtico.
Uma aplicao interessante dessas tcnicas derivar as sequncias usadas pelas opes de linha de comando do compilador, como O2. O construtor de compiladores
pode usar um conjunto de aplicaes representativas para descobrir boas sequncias
gerais e depois aplicar essas sequncias como as sequncias default do compilador.
Uma abordagem mais agressiva, usada em diversos sistemas, derivar algumas boas
sequncias para conjuntos de aplicaes diferentes e fazer com que o compilador teste
cada uma dessas sequncias e retenha o melhor resultado.
NOTAS DO CAPTULO
Embora os algoritmos apresentados neste captulo sejam modernos, muitas das ideias
bsicas eram bem conhecidas nas dcadas de 1960 e 1970. A eliminao de cdigo
morto, movimentao de cdigo, reduo de fora e eliminao de redundncia so
todos descritos por Allen [11] e Cocke e Schwartz [91]. Diversos artigos fornecem
vises gerais do estado do campo em diferentes pontos no tempo [16,28,30,316].
Os livros de Morgan [268] e Muchnick [270] discutem o projeto, a estrutura e a implementao dos compiladores otimizadores. Wolfe [352] e Allen e Kennedy [20]
focam a anlise baseada em dependncia e transformaes.
Dead implementa um estilo marcar-varrer de eliminao de cdigo morto que foi
introduzido por Kennedy [215,217]. Ele reminiscente do algoritmo de marcao
de Schorr-Waite [309]. Dead adaptado especificamente pelo trabalho de Cytron e
outros [110, Seo 7.1]. Clean foi desenvolvido e implementado em 1992 por Rob
Shillner [254].
LCM melhora o algoritmo clssico de Morel e Renvoise para eliminao de redundncia
parcial [267]. Esse artigo inspirou muitas melhorias, incluindo [81,130,133,321].
A LCM de Knoop, Rthing e Steffen [225] melhorou o posicionamento de cdigo;
a formulao na Seo10.3 usa equaes de Drechsler e Stadel [134]. Bodik, Gupta e
Soffa combinaram esta tcnica com replicao para encontrar e remover todo o cdigo
redundante [43]. O algoritmo DVNT deve-se a Briggs [53]. Ele foi implementado em
diversos compiladores.
A elevao aparece no catlogo de Allen-Cocke como uma tcnica para reduzir o espao de cdigo [16]. A formulao usando antecipabilidade aparece em vrios lugares,
incluindo Fischer e LeBlanc [147]. O abaixamento de cdigo (code sinking) ou cross
jumping descrito por Wulf e outros [356].
Tanto a otimizao peephole quanto a eliminao de recurso de cauda datam do
incio da dcada de 1960. A primeira foi descrita inicialmente por McKeeman [260]; a
segunda mais antiga; a cultura popular diz que McCarthy a descreveu no quadro-negro
durante uma palestra em 1963. A tese de Steele [323] uma referncia clssica para
eliminao de recurso de cauda.
A clonagem de superbloco foi introduzida por Hwu e outros [201]. As otimizaes
de lao como a remoo de condicionais (loop unswitching) e desenrolamento (loop
unrolling) tm sido bastante estudadas [20,28]; Kennedy usou o desenrolamento para
evitar operaes de cpia no final de um lao [214].
Cytron, Lowrey e Zadeck apresentam uma alternativa interessante ao loop unswitching
[111]. McKinley e outros oferecem ideias prticas para o impacto de otimizaes de
memria sobre o desempenho [94,261].
EXERCCIOS
Seo 10.1
1. Uma das principais funes de um otimizador remover o overhead
que o compilador introduziu durante a traduo da linguagem-fonte para a IR.
a. D quatro exemplos de ineficincias que voc esperaria que um otimizador
melhorasse, junto com as construes da linguagem-fonte que lhes do origem.
b. D quatro exemplos de ineficincias que voc esperaria que um otimizador
deixasse escapar, embora elas possam ser melhoradas. Explique por que um
otimizador teria dificuldade para melhor-las.
Seo 10.2
2. A Figura10.1 mostra o algoritmo para Dead. A passagem de marcao
uma computao clssica de ponto fixo.
a. Explique por que essa computao termina.
b. O ponto fixo que ela encontra nico? Prove sua resposta.
c. Derive um limitante apertado de tempo para o algoritmo.
3. Considere o algoritmo Clean da Seo 10.2. Ele remove o fluxo de controle
intil e simplifica o CFG.
a. Por que o algoritmo termina?
b. D um limitante de tempo geral para o algoritmo.
Seo 10.3
4. LCM usa a anlise de fluxo de dados para encontrar redundncia e realizar
amovimentao de cdigo. Assim, ela conta com uma noo lxica de identidade para encontrar redundncia duas expresses s podem ser redundantes
sea anlise de fluxo de dados mape-las para o mesmo nome interno. Ao contrrio, anumerao de valor calcula a identidade com base em valores.
a. D um exemplo de uma expresso redundante que a LCM descobrir,
masum algoritmo baseado em valor (digamos, uma verso global da numerao de valor) no.
Seo 10.6
9. Desenvolva um algoritmo para renomear o valor em um procedimento paraaquele que codifica a identidade de valor em nomes de varivel.
10. A clonagem de superbloco pode causar um crescimento significativo no cdigo.
a. Como o compilador poderia aliviar o crescimento de cdigo na clonagem
desuperbloco, mas retendo o mximo possvel do benefcio?
b. Que problemas poderiam surgir se o otimizador permitisse que a clonagem
de superbloco continuasse por um desvio de fechamento de lao? Compare
sua abordagem com o desenrolamento de lao.
Captulo
11
Seleo de instrues
VISO GERAL DO CAPTULO
O front end e o otimizador do compilador operam sobre o cdigo em sua forma IR.
Antes que o cdigo possa ser executado em um processador-alvo, a forma IR do cdigo
precisa ser reescrita para o conjunto de instrues do processador. O processo de
mapeamento de operaes da IR para operaes na mquina-alvo chamado seleo
de instrues.
Este captulo introduz duas tcnicas diferentes para seleo de instrues. A primeira
usa a tecnologia de algoritmos de casamento de padres de rvore. A segunda baseia-se
na transformao clssica de ltimo estgio, a otimizao peephole. Ambas foram bastante utilizadas em compiladores reais.
Palavras-chave: Seleo de instrues, Casamento de padres de rvore, Otimizao
peephole
11.1INTRODUO
Para traduzir um programa de uma representao intermediria como uma rvore
sinttica abstrata ou um cdigo linear de baixo nvel para a forma executvel, o compilador precisa mapear cada construo da IR para uma construo correspondente e
equivalente no conjunto de instrues do processador-alvo. Dependendo dos nveis
de abstrao relativos na IR e ISA da mquina-alvo, essa traduo pode envolver a
elaborao de detalhes que esto ocultos no programa em IR, ou a combinao de
mltiplas operaes da IR em uma nica instruo de mquina. As escolhas especficas
que o compilador faz tm impacto sobre a eficincia global do cdigo compilado.
A complexidade da seleo de instrues deriva do grande nmero de implementaes
alternativas que uma ISA tpica fornece at mesmo para operaes simples. Na dcada
de 1970, o PDP-11da DEC tinha um conjunto de instrues pequeno e compacto; assim, um bom compilador, como o BLISS-11, poderia realizar a seleo de instrues
com um passo simples, codificado mo. medida que as ISAs do processador se
expandiram, o nmero de codificaes possveis para cada programa cresceu de forma
incontrolvel. Essa exploso levou a tcnicas sistemticas para seleo de instrues,
como as apresentadas neste captulo.
Roteiro conceitual
A seleo de instrues, que mapeia a IR do compilador na ISA alvo, um problema
de casamento de padro (pattern-matching). Na forma mais simples, o compilador
pode fornecer uma nica sequncia de ISA alvo para cada operao da IR. O seletor
resultante forneceria uma expanso tipo gabarito (template) que produziria o cdigo
correto. Infelizmente, esse cdigo poderia fazer um mau uso dos recursos da mquina-
alvo. Melhores tcnicas consideram muitas sequncias de cdigo possveis para cada
operao da IR e escolhem a sequncia que tem o menor custo esperado.
Este captulo apresenta duas tcnicas para seleo de instrues: uma baseada em
casamento de padres de rvore e outra em otimizao peephole. A primeira conta com
497
uma notao de rvore de alto nvel tanto para a IR do compilador quanto para a ISA
da mquina-alvo; a segunda traduz a ISA do compilador para uma IR linear de baixo
nvel, melhora-a sistematicamente e depois a mapeia para a ISA alvo. Cada uma dessas
tcnicas pode produzir cdigo de alta qualidade que leva em considerao o contexto
local. Cada uma tem sido incorporada em ferramentas que tomam uma descrio da
mquina-alvo e produzem um seletor de instruo funcional.
Viso geral
Tcnicas sistemticas para gerao de cdigo tornam mais fcil redirecionar um
compilador. O objetivo deste trabalho minimizar o esforo exigido para transportar
o compilador para um novo processador ou sistema. O ideal que o front end e o
otimizador precisem de mudanas mnimas, e grande parte do back end pode ser
reutilizado tambm. Essa estratgia faz bom uso do investimento na criao, depurao
e manuteno das partes comuns do compilador.
Na prtica, uma nova linguagem normalmente precisa
de algumas novas operaes na IR. O objetivo, porm,
estend-la, ao invs de reinvent-la.
o nmero de mudanas em outras partes do compilador que so necessrias para transport-lo para um novo processador alvo.
Este captulo examina duas tcnicas para automatizar a construo de seletores de instruo. A Seo11.3 revisita o esquema simples de percurso em rvore do Captulo7 e o
utiliza como uma introduo detalhada para as complexidades da seleo de instrues. As
duas sees seguintes apresentam diferentes maneiras de aplicar tcnicas de casamento de
padres para transformar as sequncias IR em sequncias assembly. A primeira tcnica,
na Seo 11.4, baseia-se em algoritmos para correspondncia entre padres de rvore e
rvores. A segunda, na Seo 11.5, baseia-se em ideias da otimizao peephole. Esses
dois mtodos so baseados em descrio. O construtor de compiladores escreve uma
descrio da ISA alvo; uma ferramenta, ento, constri um seletor para uso em tempo
de compilao. Os dois mtodos tm sido usados em compiladores portteis de sucesso.
Processadores reais so mais complexos que a ILOC. Eles podem incluir operaes de
nvel mais alto e modos de endereamento que o gerador de cdigo deve considerar.
Embora esses recursos permitam que um programador habilitado ou um compilador
cuidadosamente preparado criem programas mais eficientes, tambm aumentam o
nmero de escolhas com que o seletor de instruo se depara e aumentam o espao
de potenciais implementaes.
Cada sequncia alternativa tem seus prprios custos. A maioria das mquinas modernas
implementa operaes simples, como i2i, add e lshift, de modo que sejam executadas em um nico ciclo. Algumas operaes, como multiplicao e diviso de inteiros,
podem levar mais tempo. A velocidade de uma operao da memria depende de muitos
fatores, incluindo o estado atual detalhado do sistema de memria do computador.
Em alguns casos, o custo real de uma operao pode depender do contexto. Se, por
exemplo, o processador tem vrias unidades funcionais, pode ser melhor realizar
uma cpia de registrador para registrador usando uma operao diferente de cpia,
que ser executada em uma unidade funcional pouco utilizada. Se a unidade estiver
ociosa, a operao efetivamente no tem custo; pass-la para esta unidade realmente
acelera a computao inteira. Se o gerador de cdigo tiver que reescrever a cpia para
umaoperao especfica que executada apenas na unidade pouco utilizada, isto um
problema de seleo. Se a mesma operao puder ser executada em qualquer unidade,
um problema de escalonamento.
Na maior parte dos casos, o construtor de compiladores deseja que o back end produza
cdigo que seja executado rapidamente. Porm, outras mtricas so possveis. Por
exemplo, se o cdigo final for executado em um dispositivo alimentado por bateria,
o compilador poderia considerar o consumo de energia tpico de cada operao.
(Operaes individuais podem consumir diferentes quantidades de energia.) Os custos
em um compilador que tenta otimizar por energia podem ser radicalmente diferentes
daqueles que uma mtrica de velocidade envolveriam. O consumo de energia do
processador depende muito de detalhes do hardware subjacente e, assim, pode mudar
de uma implementao de um processador para outra. De modo semelhante, se o espao de cdigo for crtico, o construtor de compiladores poderia atribuir custos com
base unicamente no tamanho da sequncia. Como alternativa, ele poderia simplesmente
excluir todas as sequncias de mltiplas operaes que conseguem o mesmo efeito que
uma sequncia de operao nica.
Para complicar as coisas ainda mais, algumas ISAs colocam restries adicionais
sobre operaes especficas. Uma multiplicao de inteiros poderia ter que tomar seus
operandos de uma subfaixa dos registradores; uma operao de ponto flutuante poderia
necessitar que seus operandos estivessem em registradores com nmero par; uma
operao de memria poderia ser executada apenas em uma das unidades funcionais do
processador; uma unidade de ponto flutuante poderia incluir uma operao que calcule
a sequncia (rirj)+rk mais rapidamente do que as operaes individuais de
multiplicao e adio; operaes de load mltiplo e store mltiplo poderiam exigir
registradores contguos; o sistema de memria poderia oferecer sua melhor largura de
banda e latncia para loads de palavra dupla ou qudrupla, ao invs de loads de palavra
nica. Restries como essas restringem a seleo de instrues. Ao mesmo tempo,
aumentam a importncia da descoberta de uma soluo que use a melhor operao em
cada ponto do programa de entrada.
Quando o nvel de abstrao da IR e da ISA alvo diferem significativamente, ou os modelos de computao subjacentes divergem, a seleo de instrues pode desempenhar
papel crtico para preencher essa lacuna. A extenso pela qual a seleo de instrues
pode mapear as computaes no programa em IR eficientemente para a mquina-alvo
No Captulo7, vimos que uma rotina simples de percurso em rvore poderia gerar
cdigo a partir da AST para uma expresso. O cdigo na Figura7.5 tratou dos
operadores binrios +, ,e aplicados a variveis e nmeros. Ele gerou cdigo
simples para a expresso e serviu para ilustrar uma tcnica que poderia ser usada
para gerar uma IR linear, de baixo nvel, ou cdigo assembly para uma mquina
RISC simples.
A tcnica simples de percurso em rvore gera o mesmo cdigo para cada ocorrncia de
um particular tipo de n da AST. Embora isso produza cdigo correto, nunca aproveita
a oportunidade para adaptar o cdigo a circunstncias e contexto especficos. Se um
compilador realizar otimizao significativa aps a seleo de instrues, isto pode no
ser um problema. Porm, sem a otimizao subsequente, o cdigo final provavelmente
ter ineficincias bvias.
Considere, por exemplo, o modo como a rotina simples de percurso em rvore trata de
variveis e nmeros. O cdigo para os casos relevantes :
Para variveis, ele conta com duas rotinas, base e offset, para obter o endereo de
base e o deslocamento em registradores. Depois, emite uma operao loadAO que soma
esses dois valores para produzir um endereo efetivo e recupera o contedo do local de
memria nesse endereo. Como a AST no diferencia entre as classes de armazenamento
de variveis, presume-se que base e offset consultam a tabela de smbolos para obter
as informaes adicionais de que elas precisam.
LAYOUT DE CDIGO
Antes de comear a emitir cdigo, o compilador tem oportunidade de estabelecer
adisposio dos blocos bsicos na memria. Se cada desvio na IR tiver dois destinos
de desvio explcitos, como a ILOC faz, ento o compilador pode escolher qualquer
umdos sucessores lgicos de um bloco para vir depois dele na memria. Se os desvios
tiverem apenas um destino de desvio explcito, a rearrumao dos blocos pode exigir
a reescrita dos desvios permutando os desvios tomado e fall-through.
Duas consideraes arquiteturais devem orientar esta deciso. Em alguns
processadores, tomar o desvio exige mais tempo do que a passagem direta para a
prxima operao. Em mquinas com memria cache, os blocos que so executados
juntos devem ser localizados juntos. Ambos favorecem a mesma estratgia para
olayout. Se o bloco a termina em um desvio que visa b e c, o compilador deve
colocaro destino tomado com mais frequncia depois de a na memria.
Naturalmente, se um bloco tiver mltiplos predecessores no grafo de fluxo
decontrole, apenas um deles pode preced-lo imediatamente na memria. Os outros
exigiro um desvio ou um salto para alcan-lo (ver Seo 8.6.2).
A extenso desse esquema para um conjunto mais realista de casos, incluindo variveis
que possuem representaes em diferentes tamanhos, parmetros de chamada por
valor e chamada por referncia, e variveis que residem em registradores por todo
seu tempo de vida, exigiria escrever cdigo explcito para verificar todos os casos em
cada referncia, o que tornaria o cdigo para o caso IDENT muito maior (e muito mais
lento), eliminando grande parte da atraente simplicidade do esquema de percurso em
rvore codificado mo.
O cdigo para lidar com nmeros igualmente simples. Ele considera que um nmero
deve ser carregado em um registrador em cada caso, e que val pode recuperar o valor
do nmero a partir da tabela de smbolos. Se a operao que usa o nmero (seu pai na
rvore) tiver uma forma imediata na mquina-alvo e a constante tiver um valor que se
ajusta ao campo imediato, o compilador deve usar a forma imediata, pois ela utiliza
um registrador a menos. Se o nmero for de um tipo no admitido por uma operao
imediata, o compilador deve dar um jeito de armazenar o valor na memria e gerar
uma referncia de memria apropriada para carregar o valor em um registrador. Isto,
por sua vez, pode criar oportunidades para outras melhorias, como manter a constante
em um registrador.
Considere as trs operaes de multiplicao mostradas na Figura11.2. As anotaes da
tabela de smbolos aparecem abaixo dos ns folha das rvores. Para um identificador,
isso consiste em um nome, um rtulo para o endereo de base (ou ARP para indicar
o registro de ativao atual) e um deslocamento a partir do endereo de base. Abaixo
de cada rvore existem duas sequncias de cdigo o gerado pelo avaliador de percurso em rvore simples e o que gostaramos que o compilador gerasse. No primeiro
custos. Se os custos realmente refletirem o desempenho, este tipo de seleo de instrues controlada por custo deve levar a um bom cdigo.
O construtor de compiladores precisa de ferramentas para ajudar a gerenciar a complexidade da gerao de cdigo para mquinas reais. Ao invs de escrever cdigo que
navega explicitamente pela IR e testa a aplicabilidade de cada operao, o construtor
de compiladores deve especificar regras, e as ferramentas, produzir o cdigo exigido
para que essas regras correspondam forma IR do cdigo. A duas sees seguintes
exploram duas tcnicas diferentes para gerenciar a complexidade que surge para o
conjunto de instrues de uma mquina moderna. A prxima seo explora o uso das
tcnicas de casamento de padres de rvore. Esses sistemas escondem a complexidade
do processo de construo do algoritmo de casamento (matcher), da mesma maneira
queos scanners encerram suas escolhas nas tabelas de transio de DFAs. A seo seguinte examina o uso da otimizao peephole para a seleo de instrues. Os sistemas
baseados em peephole passam a complexidade da escolha para um esquema uniforme
de simplificao de baixo nvel, seguido pelo casamento de padres para encontrar
as instrues apropriadas. Para manter baixo o custo do casamento, esses sistemas
limitam seu escopo a segmentos de cdigo curtos duas ou trs operaes por vez.
REVISO DA SEO
Se o compilador tiver que tirar proveito total das complexidades da mquina-alvo,
precisa exp-las na IR e consider-las durante a seleo de instrues. Muitos compiladores expandem sua IR para uma forma detalhada de baixo nvel antes deselecionar
instrues. Essas IRs detalhadas podem ser estruturais, como em nossa AST de baixo
nvel, ou lineares, como veremos na Seo 11.5. De qualquer forma, o seletor
deinstruo precisa combinar os detalhes da forma IR do cdigo com sequnciasdeinstrues na mquina-alvo. Esta seo mostrou que podemos expandir um
avaliador ad hoc de percurso em rvore para realizar a tarefa; e tambm exps algumas dasquestes que o seletor de instruo precisa tratar. As duas sees seguintes
mostram tcnicas mais genricas para este problema.
QUESTES DE REVISO
1. Para produzir o cdigo mostrado na coluna da direita da Figura11.2 para a expresso gh, o seletor de instruo precisa diferenciar entre o tamanho de diversas
constantes. Por exemplo, o cdigo desejado considera que @G e @H cabem
nocampo imediato da operao loadAI. Como a IR poderia representar os tamanhos dessas constantes? Como o algoritmo de percurso em rvore poderia lev-los
em considerao?
2. Muitos compiladores usam IRs com um nvel de abstrao mais alto nos primeiros
estgios da compilao e depois passam para uma IR mais detalhada no back end.
Que consideraes poderiam ser argumentadas contra a exposio dos detalhes
de baixo nvel nos primeiros estgios da compilao?
Como vimos, o compilador pode usar uma AST de baixo nvel como um modelo detalhado do cdigo que est sendo compilado, e usar rvores semelhantes para representar
as operaes disponveis no processador-alvo. Por exemplo, as operaes de adio da
ILOC poderiam ser modeladas por rvores de operao como aquelas mostradas na
margem esquerda. Pelo casamento sistemtico de rvores de operao com subrvores
de uma AST, o compilador pode descobrir todas as potenciais implementaes para
a subrvore.
Para trabalhar com padres de rvore, precisamos de uma notao mais conveniente
para descrev-los. Usando uma notao de prefixo, podemos escrever a rvore de
operaes para add como +( ri, rj) e addI como +( ri, cj). Naturalmente, +( ci,
rj) a variante comutativa de +( ri, cj). As folhas da rvore de operaes codificam
informaes sobre os tipos de armazenamento dos operandos. Por exemplo, em +( ri,
cj), o smbolo r indica um operando em um registrador, e c um operando constante
conhecido. Os subscritos so acrescentados para garantir exclusividade, como fizemos
nas regras para uma gramtica de atributo. Se reescrevermos a AST da Figura11.3 na
forma de prefixo, ela se torna:
Embora o desenho da rvore possa ser mais intuitivo, esta forma de prefixo linear
contm a mesma informao.
Dada uma AST e uma coleo de rvores de operao, o objetivo mapear a AST para
operaes construindo um ladrilhamento (tiling) da AST com rvores de operao. Um
ladrilhamento uma coleo de pares n-ast, rvore-op, onde n-ast um n na AST
e rvore-op uma rvore de operaes. A presena de um par n-ast,rvore-op no
ladrilhamento significa que a operao da mquina-alvo representada por rvore-op
poderia implementar n-ast. Naturalmente, a escolha de uma implementao para nast depende das implementaes de suas subrvores. O ladrilhamento especificar, para
cada uma das subrvores de n-ast, uma implementao que se conecta rvore-op.
O ladrilhamento implementa a AST se implementar cada operao e cada ladrilho se
conectar com seus vizinhos. Dizemos que um ladrilho n-ast,rvore-op conecta-se aos
seus vizinhos se n-ast for coberto por uma folha em outra rvore-op no ladrilhamento,
a menos que n-ast seja a raiz da AST. Quando duas dessas rvores se sobrepem
(em n-ast), deve haver concordncia entre elas sobre a classe de armazenamento de
seu n comum. Por exemplo, se ambas assumirem que o valor comum reside em um
registrador, ento as sequncias de cdigo para as duas rvores-op so compatveis.
Se uma assumir que o valor reside na memria e a outra que reside em um registrador,
as sequncias de cdigo so incompatveis, pois no transmitiro corretamente o valor
da rvore inferior para a superior.
Dado um ladrilhamento que implementa uma AST, o compilador pode facilmente gerar
cdigo assembly em um percurso de baixo para cima. Assim, a chave para tornar esta
tcnica prtica est em algoritmos que rapidamente encontram bons ladrilhamentos
para uma AST. Vrias tcnicas eficientes surgiram para casamento de padres de rvore
a ASTs de baixo nvel. Todos esses sistemas associam custos s rvores de operao
e produzem ladrilhamentos de custo mnimo. Eles diferem na tecnologia usada para
o casamento casamento de rvore, casamento de texto e sistemas de reescrita de
baixo para cima e na generalidade de seus modelos de custo custos fixos estticos
versus custos que podem variar durante o processo de casamento.
FIGURA 11.4 Regras de reescrita para ladrilhamento da rvore de baixo nvel com ILOC.
Considere a regra 16, que corresponde rvore desenhada na margem. (Seu resultado,
no n +, implicitamente um Reg.) A regra descreve uma rvore que calcula a soma de
um valor localizado em um Reg e um valor imediato em um Num. O lado esquerdo da
tabela d o padro de rvore para a regra, Reg +(Reg1, Num2). A coluna do centro
lista seu custo, 1. A coluna da direita mostra uma operao ILOC que implementa a regra,
addI r1, n2 rnew. Os operandos no padro de rvore, Reg1 e Num2, correspondem
aos operandos r1 e n2 no gabarito de cdigo. O compilador precisa reescrevero campo
rnew no gabarito de cdigo com o nome de um registrador alocado para manter o resultado
da adio. Este nome, por sua vez, torna-se uma folha na subrvore que se conecta a essa
subrvore. Observe que a regra 16 tem uma variante comutativa, a regra 17. Uma regra
explcita necessria para corresponder subrvores como aquela desenhada na margem
Para resumir tal sequncia, usaremos um desenho como aquele mostrado na margem.
As caixas tracejadas mostram os lados direitos especficos que combinaram na rvore,
com o nmero da regra registrado no canto superior esquerdo de cada caixa. A lista
de nmeros de regra abaixo do desenho indica a sequncia em que as regras foram
aplicadas. A sequncia de reescrita substitui a subrvore em caixa pelo lado esquerdo
da regra final
Observe como os no terminais garantem que as rvores de operao se conectem de
modo apropriado nos pontos onde elas se sobrepem. A regra 6 reescreve um Lab
como um Reg. A folha esquerda na regra 11 um Reg. Exibir os padres como regras
em uma gramtica encerra todas as consideraes que surgem nas fronteiras entre as
rvores de operao na rotulagem de no terminais.
Para esta subrvore trivial, as regras geram muitas sequncias de reescrita, refletindo a
ambiguidade da gramtica. A Figura11.6 mostra oito dessas sequncias. Todas as regras
em nosso esquema tm um custo de 1, exceto para as regras 1 e 7. Como nenhuma das
sequncias de reescrita utiliza essas regras, seus custos so iguais ao tamanho de sua
sequncia. As sequncias encontram-se em trs categorias por custo. O primeiro par
de sequncias, 6,11 e 8,14, tem custo dois cada um. As quatro seguintes, 6,8,10,
8,6,10, 6,16,9 e 8,19,9, tm custo trs cada. As sequncias finais, 6,8,15,9 e
8,6,15,9, possuem custo quatro cada.
FIGURA 11.8 Clculo dos conjuntos Label para ladrilhar uma AST.
11.4.3Ferramentas
Conforme vimos, uma tcnica de gerao de cdigo orientada por rvore, de baixo para
cima, pode produzir seletores de instruo eficientes. Existem vrias maneiras do construtor de compiladores implementar geradores de cdigo com base nesses princpios.
1. O construtor de compiladores pode codificar mo um algoritmo de casamento
de padres, semelhante a Tile, que verifica explicitamente as regras decasamento enquanto ladrilha a rvore. Uma implementao cuidadosa pode limitar
oconjunto de regras que devem ser examinadas para cada n, o que evita a
grande tabela esparsa e leva a um gerador de cdigo compacto.
2. Como o problema finito, o construtor de compiladores pode codific-lo
comoum autmato finito autmato de casamento de rvore e obter ocomportamento de baixo custo de um DFA. Nesse esquema, a tabela de pesquisa
codifica a funo de transio do autmato incorporando implicitamente toda a
Otimizao local
Um esquema no qual o compilador no tem melhor
alternativa, em cada ponto no cdigo, considerado
localmente timo.
QUESTES DE REVISO
1. O casamento de padres de rvore parece ser natural para uso em um compilador
com uma IR tipo rvore. Como o compartilhamento na rvore, ou seja, o uso
deum grafo acclico direcionado (DAG) ao invs de uma rvore, poderia afetar
oalgoritmo? Como voc poderia aplic-lo a uma IR linear?
2. Alguns sistemas baseados em casamento de padres de rvore exigem que
oscustos associados a um padro sejam fixos, enquanto outros permitem custos
dinmicos calculados no momento em que a combinao considerada.
Comoo compilador poderia usar os custos dinmicos?
11.5.1Otimizao peephole
A premissa bsica da otimizao peephole simples: o compilador pode eficientemente
encontrar melhorias locais examinando curtas sequncias de operaes adjacentes.
Conforme proposto originalmente, o otimizador peephole era executado aps todas
as ouras etapas na compilao; consumia e produzia cdigo assembly. O otimizador
tinha uma janela deslizante, ou peephole, que movia sobre o cdigo. A cada etapa,
examinava as operaes na janela, procurando padres especficos que pudesse melhorar. Quando reconhecia um padro, o reescrevia com uma sequncia de instrues
melhor. A combinao de um conjunto limitado de padres e uma rea limitada de
foco levava a um processamento rpido.
Um padro de exemplo clssico um store seguido por um load do mesmo local. O
load pode ser substitudo por uma cpia.
Se isto eliminar o ltimo desvio para l10, o bloco bsico comeando em l10 torna-se
inalcanvel e pode ser eliminado. Infelizmente, provar que a operao em l10 inalcanvel exige mais anlise do que normalmente est disponvel durante a otimizao
peephole (ver Seo 10.2.2).
Os primeiros otimizadores peephole usavam um conjunto limitado de padres codificados mo; utilizavam uma busca exaustiva para realizar o casamento de padres,
mas eram executados rapidamente, devido ao pequeno nmero de padres e o pequeno
tamanho de janela normalmente, duas ou trs operaes.
A otimizao peephole progrediu alm do casamento de um pequeno nmero de
padres. ISAs cada vez mais complexas levavam a tcnicas mais sistemticas. Um
otimizador peephole moderno quebra o processo em trs tarefas distintas: expanso,
simplificao e casamento. Ele substitui a otimizao controlada por padro dos primeiros sistemas por uma aplicao sistemtica de interpretao e simplificao simblicas.
avaliao de expresses com valor constante (por exemplo, 2+17 19) e eliminao
de efeitos inteis, como a criao de cdigos de condio no usados. Assim, a simplificao realiza otimizao local limitada na LLIR da janela. Isso sujeita todos os
detalhes expostos na LLIR (aritmtica de endereo, destinos de desvio, e assim por
diante) a um nvel uniforme de otimizao local.
Na ltima etapa, o casamento compara a LLIR simplificada com a biblioteca de padres,
procurando o padro que melhor capture todos os efeitos da LLIR. A sequncia de
cdigo final pode produzir efeitos alm daqueles exigidos pela sequncia LLIR; por
exemplo, poderia criar um valor de cdigo de condio novo, embora intil. Porm,
ela deve preservar os efeitos necessrios para a exatido. E no pode eliminar um valor
vivo, independentemente de se o valor est armazenado na memria, em um registrador
ou em um local definido implicitamente como o cdigo de condio.
A Figura11.9 mostra como esta tcnica poderia funcionar no exemplo da Seo 11.3.
Ela comea, no canto superior esquerdo, com as qudruplas para a AST de baixo nvel
mostradas na Figura11.3. (Lembre-se de que a AST calcula a b 2c, com a
armazenado no deslocamento 4 na AR local, b como um parmetro de chamada por
referncia cujo ponteiro est armazenado no deslocamento 16 a partir do ARP, e c
no deslocamento 12 a partir do rtulo @G.) A expanso cria a LLIR mostrada no canto
superior direito. A simplificao reduz esse cdigo para produzir o cdigo LLIR no
canto inferior direito. A partir desse fragmento de LLIR, o casamento constri o cdigo
ILOC no canto inferior esquerdo.
A chave para entender este processo est na simplificao. A Figura11.10 mostra as
sequncias sucessivas que o otimizador peephole tem em sua janela enquanto processa a
IR de baixo nvel para o exemplo. Suponha que ele tenha uma janela de trs operaes.
A sequncia 1 mostra a janela com as trs primeiras operaes. Nenhuma simplificao
possvel. O otimizador rola a primeira operao, definindo r10, para fora da janela e
traz a definio de r13. Nesta janela, ele pode substituir r12 para frente, na definio
de r13. Como isso torna r12 morto, o otimizador descarta a definio de r12 e puxa
outra operao para o final da janela, para alcanar a sequncia 3. Em seguida, coloca
r13 na referncia de memria que define r14, produzindo a sequncia 4.
Nenhuma simplificao possvel na sequncia 4, de modo que o otimizador rola a definio
de r11 para fora da janela. Ele tambm no pode simplificar a sequncia 5, de modo que
tambm rola a definio de r14 para fora da janela. Ele pode simplificar a sequncia 6
substituindo para frente, 16 na adio que define r17, ao que produz a sequncia 7. O
otimizador continua dessa maneira, simplificando o cdigo quando possvel e avanando
quando no. Quando alcana a sequncia 13, ele termina porque no pode simplificar mais
a sequncia e no possui cdigo adicional para trazer para a janela.
Voltando Figura11.9, compare o cdigo simplificado com o original. O cdigo simplificado consiste naquelas operaes que rolam para fora do topo da janela, mais aquelas
deixadas na janela quando a simplificao termina. Aps a simplificao, a computao
usa 8 operaes, ao invs de 14. E usa 7 registradores (fora rarp), ao invs de 13.
Vrias questes de projeto afetam a capacidade de um otimizador peephole de melhorar
o cdigo. A capacidade de detectar quando um valor est morto desempenha papel
crtico na simplificao. O tratamento das operaes de fluxo de controle determina o
que acontece nas fronteiras de bloco. O tamanho da janela peephole limita a capacidade
do otimizador de combinar operaes relacionadas. Por exemplo, uma janela maior
permitiria que o simplificador inclusse a constante 2 para a operao de multiplicao.
A trs subsees a seguir exploram essas questes.
O compilador pode calcular conjuntos LIVEOUT para cada bloco e depois, em uma
passada reversa pelo bloco, rastrear quais valores esto vivos em cada operao.
Como alternativa, pode usar a ideia que est por trs da forma SSA semipodada; ele
pode identificar nomes que so usados em mais de um bloco e considerar qualquer
nome deste tipo como vivo na sada de cada bloco. Essa estratgia alternativa evita
o custo da anlise viva; e identificar corretamente qualquer valor que seja estritamente local ao bloco onde definido. Na prtica, os efeitos introduzidos pela
expanso so estritamente locais, de modo que a tcnica menos dispendiosa produz
bons resultados.
Dados os conjuntos LIVEOUT ou o conjunto de nomes globais, a expanso pode marcar
os ltimos usos na LLIR. Duas observaes tornam isto possvel. Primeira, a expanso
pode processar um bloco de baixo para cima; a expanso um processo simples
controlado por gabarito. Segunda, ao percorrer o bloco de baixo para cima, a expanso
pode montar um conjunto de valores que esto vivos em cada operao, LIVENOW.
O clculo de LIVENOW simples. A expanso define o valor inicial para LIVENOW
como sendo igual o conjunto LIVEOUT para o bloco. (Na ausncia de conjuntos
LIVEOUT, ele pode definir LIVENOW para que contenha todos os nomes globais.)
Agora, ao processar uma operao ri rj op rk, o algoritmo acrescenta rj e rk a
LIVENOW e exclui ri. Esse algoritmo produz, em cada etapa, um conjunto LIVENOW
que to preciso quanto a informao inicial usada no final do bloco.
Em uma mquina que usa um cdigo de condio para controlar desvios condicionais,
muitas operaes definem o valor deste cdigo. Em um bloco tpico, muitos desses
valores de cdigo de condio esto mortos. A expanso deve inserir atribuies explcitas ao cdigo de condio. A simplificao deve entender quando o valor do cdigo
de condio est morto, pois atribuies irrelevantes ao cdigo de condio podem
impedir que o casamento gere algumas sequncias de instruo.
Por exemplo, considere o clculo rirj+rk. Se tantoquanto+definirem o cdigo
de condio, a sequncia de duas operaes poderia gerar a seguinte LLIR:
ltimo uso
Uma referncia a um nome aps a qual o valor
representado por este nome no est mais vivo.
ou em blocos diferentes. Isto exige uma anlise global para determinar quais usos cada
definio pode alcanar (ou seja, definies de alcance da Seo 9.2.4). Alm disso, o
simplificador precisa reconhecer que uma nica definio pode alcanar mltiplos usos,
e um nico uso pode se referir a valores calculados por vrias definies distintas. Assim, o simplificador no pode simplesmente combinar a operao de definio com um
uso e deixar as operaes restantes desamparadas. Ele precisa limitar sua considerao
a situaes simples, como uma nica definio e um nico uso, ou mltiplos usos com
uma nica definio, ou precisa realizar alguma anlise cuidadosa para determinar
se uma combinao segura e lucrativa. Essas complicaes sugerem aplicar uma
janela lgica dentro de um contexto local ou superlocal. Mover a janela lgica alm
de um bloco bsico estendido acrescenta complicaes significativas ao simplificador.
11.5.2Transformadores peephole
O advento de otimizadores peephole mais sistemticos, conforme descrevemos na
seo anterior, criou a necessidade de conjuntos de padres mais completos para a
linguagem assembly de uma mquina-alvo. Como o processo em trs etapas traduz
todas as operaes para LLIR e tenta simplificar todas as sequncias LLIR, o algoritmo
de casamento de padres (matcher) precisa da capacidade de traduzir sequncias LLIR
arbitrrias de volta para o cdigo assembly para a mquina-alvo. Assim, esses sistemas
peephole modernos tm bibliotecas de padres muito maiores do que os sistemas mais
antigos, parciais. medida que os computadores passaram de instrues de 16 para
de 32 bits, a exploso no nmero de operaes assembly distintas tornou problemtica
a gerao manual de padres. Para lidar com essa exploso, a maioria dos sistemas
peephole modernos inclui uma ferramenta que gera automaticamente um matcher a
partir de uma descrio do conjunto de instrues de uma mquina-alvo.
LLIR, o simplificador no precisa se preocupar com efeitos mortos; pode assumir que o
otimizador os remover com suas tcnicas mais gerais para eliminao de cdigo morto.
Este esquema tambm reduz o trabalho exigido para redirecionar um compilador.
Para mudar os processadores-alvo, o construtor de compiladores deve: (1) fornecer
uma descrio apropriada de mquina para o gerador de padres, de modo que possa
produzir um novo seletor de instruo; (2) alterar as sequncias LLIR geradas por fases
anteriores, para que se encaixem nova ISA; e (3) modificar o escalonador de instrues e alocador de registradores para refletir as caractersticas da nova ISA. Embora
isso abranja uma quantidade de trabalho significativa, a infraestrutura para descrever,
manipular e melhorar as sequncias LLIR permanece intacta. Em outras palavras, as sequncias LLIR para mquinas radicalmente diferentes devem capturar suas diferenas;
porm, a linguagem bsica em que essas sequncias so escritas permanece a mesma.
Isto permite que o construtor de compiladores construa um conjunto de ferramentas
que so teis para muitas arquiteturas e produza um compilador especfico de mquina
gerando a IR de baixo nvel apropriada para a ISA alvo e fornecendo um conjunto
apropriado de padres para o otimizador peephole.
A outra vantagem deste esquema est no simplificador. Esse transformador peephole
bsico ainda inclui um simplificador. A simplificao sistemtica de cdigo, mesmo
quando realizada em uma janela limitada, fornece uma vantagem significativa sobre um
simples passo codificado mo que percorre a IR e a reescreve em linguagem assembly.
A substituio para frente, a aplicao de identidades algbricas simples e a incluso
de constantes podem produzir sequncias de LLIR mais curtas e mais eficientes. Estas,
por sua vez, podem levar a um cdigo melhor para uma mquina-alvo.
Vrios sistemas de compilador importantes tm usado esta tcnica. O mais conhecido
pode ser o sistema de compilador Gnu (GCC). O GCC usa uma IR de baixo nvel
conhecida como linguagem de transferncia de registrador (RTL) para algumas de
suas otimizaes e gerao de cdigo. O back end usa um esquema peephole para
converter RTL em cdigo assembly para os computadores-alvo. O simplificador
implementado usando interpretao simblica sistemtica. A etapa de casamento de
padres no otimizador peephole realmente interpreta o cdigo RTL como rvores e
usa um casamento de padres de rvore simples criado a partir de uma descrio da
mquina-alvo. Outros sistemas, como o VPO de Davidson, constroem uma gramtica
a partir da descrio de mquina e geram um pequeno parser que processa a RTL em
uma forma linear para realizar a etapa de casamento de padres.
REVISO DA SEO
A tecnologia de otimizao peephole tem sido adaptada para realizar seleo
deinstrues. O seletor de instruo clssico baseado em peephole consiste em
umaexpanso baseada em gabarito que traduz a IR do compilador em uma forma
mais detalhada, com um nvel de abstrao abaixo do nvel de abstrao da ISA alvo;
uma simplificao que usa a substituio direta, simplificao algbrica, propagaode
constante e eliminao de cdigo morto dentro de um escopo de trs ou quatro
operaes; e um casamento que mapeia a IR de baixo nvel otimizada para a ISA alvo.
A fora desta tcnica est no simplificador; ele remove ineficincias de interoperao
que a expanso da IR do compilador para a IR de baixo nvel introduz. Essas oportunidades envolvem valores que so locais em escopo; elas no podem ser vistas emestgios iniciais da traduo.
As melhorias resultantes podem ser surpreendentes. A fase final de casamento de padres direta; tecnologias variando desde casamento de padres codificados mo
at parsers LR() tm sido usadas.
QUESTES DE REVISO
1. Esboce um algoritmo concreto para o simplificador que aplique substituio
direta, simplificao algbrica e propagao de constante local. Qual a complexidadedo seu algoritmo? Como o tamanho da janela peephole afeta o custo de
execuo do seu algoritmo sobre um bloco?
2. O exemplo mostrado na Figura11.10 demonstra um ponto fraco dos seletores
baseados em peephole. A atribuio de 2 a r10 est muito longe do uso de r10
para permitir que o simplificador inclua a constante e simplifique a multiplicao
(para um multI ou um add). Que tcnicas voc poderia usar para expor essa
oportunidade ao simplificador?
LLIR que no combinam com qualquer padro em sua tabela. Quando isto acontece, ele
pode invocar o simplificador simblico para procurar uma melhoria, empregando o poder
da busca somente sobre os pares LLIR para os quais no tem um padro preexistente.
Para tornar esta tcnica prtica, o simplificador simblico deve registrar os sucessos e
as falhas, assim permitindo-lhe rejeitar pares LLIR previamente vistos sem o overhead
da interpretao simblica. Quando ele tiver sucesso na melhoria de um par, deve
acrescentar o novo padro tabela de padres do otimizador, de modo que ocorrncias
futuras desse par sejam tratadas pelo mecanismo mais eficiente.
Esta tcnica de aprendizagem para gerar padres tem diversas vantagens. Ela aplica o esforo somente nos pares LLIR no vistos anteriormente; compensa os buracos na cobertura
do conjunto de treinamento da mquina alvo; fornece a eficcia do sistema mais dispendioso, embora preserve a maior parte da velocidade do sistema orientado por padres.
Porm, ao usar esta tcnica, o construtor de compiladores precisa determinar quando
o otimizador simblico deve atualizar as tabelas de padres e como acomodar essas
atualizaes. Permitir que uma compilao qualquer reescreva a tabela de padres
para todos os usurios parece desaconselhvel; questes de sincronizao e segurana
certamente surgiro. Ao invs disso, o construtor de compiladores pode optar por
atualizaes peridicas armazenando padres recm-localizados de modo que
possam ser includos na tabela como uma ao de manuteno de rotina.
interno. O uso de uma busca exaustiva para pequenos fragmentos de cdigo para
melhorar a velocidade ou o espao pode ser compensador.
De modo semelhante, a busca exaustiva tem sido aplicada como parte do processo de
redirecionar um compilador para uma nova arquitetura. Esta aplicao usa a busca
exaustiva para descobrir implementaes particularmente eficientes para sequncias IR
que o compilador gera rotineiramente. Como o custo contrado quando o compilador
transportado, o construtor de compiladores pode justificar o uso da busca amortizando
este custo pelas muitas compilaes que se espera utilizem o novo compilador.
NOTAS DO CAPTULO
A maior parte dos primeiros compiladores usava tcnicas codificadas mo, ad hoc,
para realizar a seleo de instrues [26]. Com conjuntos de instrues suficientemente
pequenos, ou equipes de construo de compiladores grandes o suficiente, isso funcionava. Por exemplo, o compilador BLISS-11 gerava excelente cdigo para o PDP-11, com
EXERCCIOS
Seo 11.2
1. O gerador de cdigo de percurso em rvore mostrado na Figura7.2 usa um loadI
para cada nmero. Reescreva o gerador de cdigo de percurso em rvore de modo
que ele use addI, subI, rsubI, multI, divI e rdivI. Explique quaisquer
rotinas adicionais ou estruturas de dados que seu gerador de cdigo necessite.
Seo 11.3
2. Usando as regras dadas na Figura11.5, gere dois ladrilhamentos para a AST
mostrada na Figura11.4.
3. Construa uma AST de baixo nvel para as expresses a seguir, usando a rvore
na Figura11.4 como um modelo:
Use as regras dadas na Figura11.5 para ladrilhar estas rvores e gerar ILOC.
4. O casamento de padres de rvore considera que sua entrada uma rvore.
a. Como voc estenderia essas ideias para lidar com DAGs, onde um n pode
ter vrios pais?
b. Como as operaes de fluxo de controle se encaixam nesse paradigma?
5. Em qualquer esquema de percurso em rvore para gerao de cdigo, o compilador precisa escolher uma ordem de avaliao para as subrvores. Ou seja,
emalgum n binrio n, ele avalia a subrvore esquerda ou a direita primeiro?
a. A escolha da ordem afeta o nmero de registradores exigidos para avaliar
asubrvore inteira?
b. Como essa escolha pode ser incorporada nos esquemas de casamento depadres de rvore de baixo para cima?
Seo 11.4
6. Um otimizador peephole real precisa lidar com operaes de fluxo de controle,
incluindo desvios condicionais, saltos e instrues rotuladas.
a. O que este otimizador deve fazer quando traz um desvio condicional
paraajanela de otimizao?
b. A situao diferente quando ele encontra um salto?
c. O que acontece com uma operao rotulada?
d. O que o otimizador pode fazer para melhorar esta situao?
7. Escreva algoritmos concretos para realizar as funes de simplificao e
casamento de um transformador peephole.
a. Qual a complexidade assinttica de cada um dos seus algoritmos?
b. Como o tempo de execuo do transformador afetado por um programa
de entrada maior, por uma janela maior e por um conjunto de padres maior
(tanto para simplificao quanto para casamento)?
8. Transformadores peephole simplificam o cdigo enquanto selecionam uma implementao concreta para ele. Suponha que este transformador seja executado
antes do escalonamento de instrues ou alocao de registradores, e que ele
pode usar um conjunto ilimitado de nomes de registrador virtual.
a. O transformador peephole pode alterar a demanda por registradores?
b. Ele pode alterar o conjunto de oportunidades que esto disponveis ao escalonador para reordenar o cdigo?
Captulo
12
Escalonamento de instrues
VISO GERAL DO CAPTULO
O tempo de execuo de um conjunto de operaes depende bastante da ordem em que
elas so apresentadas para execuo. O escalonamento de instrues tenta reordenar as
operaes em um procedimento para melhorar seu tempo de execuo; basicamente,
tenta executar o mximo possvel de operaes por ciclo.
Este captulo introduz a tcnica dominante para escalonamento em compiladores: escalonamento de lista guloso. Apresenta vrios mtodos para aplicar este escalonamento
para escopos maiores do que um nico bloco bsico.
Palavras-chave: Escalonamento de instrues, Escalonamento de lista, Escalonamento
de trao, Pipelining de software
12.1INTRODUO
Em muitos processadores, a ordem em que as operaes so apresentadas para execuo
tem um efeito significativo sobre a extenso de tempo exigida para executar uma
sequncia de instrues. Diferentes operaes exigem diferentes extenses de tempo.
Em um multiprocessador comercial tpico, a adio e subtrao de inteiros exige menos
tempo do que a diviso de inteiros; de modo semelhante, a diviso de ponto flutuante
leva mais tempo do que a adio ou subtrao de ponto flutuante. A multiplicao,
em geral, encontra-se entre as operaes correspondentes de adio e diviso. O
tempo exigido para completar um load da memria depende de onde, na hierarquia da
memria, o valor reside no momento em que o load emitido.
A tarefa de ordenar as operaes em um bloco ou um procedimento para fazer uso eficaz
dos recursos do processador chamada escalonamento de instrues. O escalonador usa
como entrada uma lista de operaes parcialmente ordenada na linguagem assembly da
mquina-alvo; e produz como sada uma verso ordenada desta lista. Ele considera que
o cdigo j foi otimizado e no tenta duplicar o trabalho do otimizador. Ao invs disso,
empacota operaes nos ciclos disponveis e slots de emisso de unidade funcional de
modo que o cdigo ser executado o mais rapidamente possvel.
Roteiro conceitual
A ordem em que o processador encontra operaes tem impacto direto sobre a velocidade da execuo do cdigo compilado. Assim, a maioria dos compiladores inclui um escalonador de instrues que reordena as operaes finais para melhorar o desempenho.
As escolhas do escalonador so restritas pelo fluxo de dados, pelos atrasos associados
s operaes individuais e pelas capacidades do processador-alvo. O escalonador
precisa considerar todos esses fatores se tiver que produzir um escalonamento correto
e eficiente para o cdigo compilado.
A tcnica dominante para o escalonamento de instrues uma heurstica gulosa,
chamada escalonamento de lista. Escalonadores de lista operam sobre cdigo em linha
reta e usam diversos esquemas de classificao de prioridade para orientar suas escolhas.
Os construtores de compilador inventaram diversos frameworks para escalonar sobre
531
Viso geral
Na maioria dos processadores modernos, a ordem em que as instrues aparecem tem
impacto sobre a velocidade com que o cdigo executado. Os processadores sobrepem
a execuo de operaes, emitindo-as sucessivamente, o mais rpido possvel, dado o
conjunto finito (e pequeno) de unidades funcionais. Em princpio, essa estratgia faz
uma boa utilizao dos recursos de hardware e diminui o tempo de execuo sobrepondo a execuo de operaes sucessivas. A dificuldade surge quando uma operao
emitida antes que seus operandos estejam prontos.
Os projetos de processador tratam desta situao de duas maneiras. O processador
pode adiar (stall) a operao prematura at que seus operandos estejam disponveis.
Em uma mquina que adia operaes prematuras, o escalonador reordena as operaes
em uma tentativa de minimizar o nmero desses adiamentos. Alternativamente, ele
pode executar a operao prematura, embora com os operandos incorretos. Essa
tcnica conta com o escalonador para manter a distncia suficiente entre a definio
de um valor e seus vrios usos para que o resultado das operaes seja correto. Se no
houver operaes teis suficientes para cobrir o atraso associado a alguma operao,
o escalonador dever inserir nops para preencher a lacuna.
Adiamento
Atraso causado por um interbloqueio de hardware
(hardware interlock) que impede que um valor seja lido
at que sua operao de definio termine.
Interbloqueio o mecanismo que detecta a emisso
prematura e cria o atraso real.
Escalonado estaticamente
Um processador que conta com a insero de NOPs
pelo compilador para garantir o resultado correto um
processador escalonado estaticamente.
Escalonado dinamicamente
Um processador que prov interbloqueios para garantir
o resultado correto um processador escalonado
dinamicamente.
Superescalar
Um processador que pode emitir operaes distintas
para mltiplas unidades funcionais distintas em um
nico ciclo considerado um processador superescalar.
Paralelismo em nvel de instruo
(ILP Instruction Level Parallelism)
Disponibilidade de operaes independentes de
poderem ser executadas simultaneamente.
unidade funcional em cada ciclo, tudo reunido em uma nica instruo de formato fixo. (O
escalonador empacota nops nos slots para unidades funcionais ociosas.) Uma mquina
VLIW empacotada evita muitos desses nops com uma instruo de tamanho varivel.
Processadores superescalares examinam uma pequena janela no fluxo de instrues,
escolhem operaes que possam ser executadas nas unidades disponveis e as atribuem
a unidades funcionais. Um processador escalonado dinamicamente considera a disponibilidade de operandos; um processador escalonado estaticamente s considera a
disponibilidade de unidades funcionais. Um processador superescalar no sequencial
(out-of-order superscalar processor) usa uma janela muito maior para varrer as operaes a serem executadas; a janela pode ter uma centena de instrues ou mais.
Essa diversidade de mecanismos de despacho de hardware torna menos ntida a distino
entre uma operao e uma instruo. Em mquinas VLIW e VLIW empacotada, uma instruo contm vrias operaes. Em mquinas superescalares, normalmente nos referimos
a uma nica operao como uma instruo e descrevemos essas mquinas como emitindo
vrias instrues por ciclo. No decorrer deste livro, usamos os termos operao para descrever um nico cdigo de operao (opcode) e seus operandos, e instruo somente para nos
referirmos a uma agregao de uma ou mais operaes que so emitidas no mesmo ciclo.
Em respeito tradio, ainda nos referimos a esse problema como escalonamento de instrues, embora pudesse ser chamado mais precisamente de escalonamento de operaes.
Em uma arquitetura VLIW ou VLIW empacotada, o escalonador empacota operaes em
instrues que so executadas em um determinado ciclo. Numa arquitetura superescalar,
seja sequencial ou no sequencial (in-order ou out-of-order), o escalonador reordena
operaes para permitir que o processador emita o mximo possvel em cada ciclo.
Este captulo examina o escalonamento e as ferramentas e tcnicas que os compiladores
utilizam para execut-lo. A Seo12.2 fornece uma introduo detalhada ao problema;
a Seo12.3 introduz o framework padro usado para escalonamento de instrues:
o algoritmo de escalonamento de lista. A Seo12.4 apresenta diversas tcnicas que
os compiladores usam para estender o intervalo de operaes sobre as quais podem
aplicar o escalonamento de lista; e a Seo Tpicos avanados apresenta uma tcnica
para escalonamento de lao.
Grafo de dependncia
Para um bloco b, seu grafo de dependncia D=(N, E)
tem um n para cada operao em b. Uma aresta em D
conecta dois ns n1 e n2 se n2 usar o resultado de n1.
tenham sido definidos. O escalonamento que viola esta regra muda o fluxo
de dados no cdigo e provavelmente produzir resultados incorretos em uma
mquina escalonada estaticamente.
3. Cada instruo no contm mais operaes de cada tipo t do que a mquina-alvo
pode emitir em um ciclo. Esta restrio fora a viabilidade, pois o escalonamento
que a infringe contm instrues que a mquina-alvo possivelmente no pode
emitir. (Em uma mquina VLIW tpica, o escalonador precisa preencher slots
no usados em uma instruo com nops.)
O compilador s deve produzir escalonamentos que atendam a todas essas trs restries.
Dado um escalonamento bem formado que seja correto e vivel, seu tamanho simplesmente o nmero de ciclo em que a ltima operao termina, supondo que a primeira instruo seja emitida no ciclo 1. O tamanho do escalonamento pode ser calculado como:
Como, ento, o compilador deve escalonar essa computao? Uma operao s pode ser
escalonada em uma instruo quando seus operandos esto disponveis. Como a, c, e e
g no possuem predecessores no grafo, so os candidatos iniciais para escalonamento.
O fato de que o n a se encontra no caminho crtico sugere fortemente que ele seja escalonado na primeira instruo. Quando a tiver sido escalonado, o caminho mais longo
restante em D cdefhi, sugerindo que c deve ser escalonado como a segunda instruo.
Com o escalonamento ac, b e e empatam como o caminho mais longo. Porm, b precisa
do resultado de a, que no estar disponvel antes do quarto ciclo. Isso torna e seguido
de b a melhor escolha. Continuar nesse padro leva ao escalonamento acebdgfhi, que
corresponde ao escalonamento mostrado na Figura12.1b.
Porm, o compilador no pode simplesmente rearrumar as instrues na ordem proposta. Lembre-se de que tanto c quanto e definem r2, e d usa o valor que c armazena em
r2. O escalonador no pode mover e para antes de d, a menos que renomeie o resultado
de e para evitar o conflito com a definio de c de r2. Essa restrio surge no do fluxo
de dados, como as dependncias modeladas pelas arestas em D. Em vez disso, ela
impede uma atribuio que mudaria o fluxo de dados. Essas restries normalmente
so chamadas antidependncias. Indicamos a antidependncia entre e e d como ed.
LIMITAES AO ESCALONAMENTO
O escalonador no pode remediar todos os problemas com a ordem das instrues.
Considere o cdigo a seguir para calcular a16
O problema est na forma do cdigo, que precisa ser resolvida mais cedo na compilao. Se o otimizador refatorar ou remodelar o cdigo para (a2)2 (a2)2, como
mostramos no lado direito da figura, o escalonador pode sobrepor algumas das
multiplicaes e alcanar um escalonamento mais curto. Se o processador s puder
emitir uma multiplicao por ciclo, o escalonamento refatorado economiza um ciclo.
Se puder emitir duas multiplicaes por ciclo, economiza dois ciclos.
Antidependncia
A operao x antidependente da operao y se x vier
antes de y e y definir um valor usado em x. A reverso
da ordem de execuo pode fazer com que x calcule um
valor diferente.
O escalonador pode produzir cdigo correto pelo menos de duas maneiras diferentes:
descobrir as antidependncias que esto presentes no cdigo de entrada e respeit-las
no escalonamento final, ou renomear valores para evit-las. O exemplo contm quatro
antidependncias, a saber, ec, ed, ge e gf. Todas envolvem a redefinio de
r2. (Tambm existem restries com base em r1, mas cada antidependncia em r1
duplica uma dependncia baseada no fluxo de valores.)
Respeitar antidependncias muda o conjunto de escalonamentos que o compilador
pode produzir. Por exemplo, ele no pode mover e para antes de c ou d, o que o fora
a produzir um escalonamento como acbdefghi, que exige 18 ciclos. Embora este seja
uma melhoria de 18% em relao ao cdigo no escalonado (abcdefghi), ele no
competitivo com a melhoria de 41% obtida pela renomeao para produzir acebdgfhi,
como vemos no lado direito da Figura12.1.
Como alternativa, o escalonador pode sistematicamente renomear os valores no bloco
para eliminar antidependncias antes de escalonar o cdigo. Esta tcnica libera o escalonador das restries impostas por antidependncias, mas cria o potencial para problemas se o cdigo escalonado exigir cdigo de derramamento. A renomeao no muda
o nmero de variveis vivas; simplesmente muda seus nomes e ajuda o escalonador a
evitar a violao de antidependncias. Porm, aumentar a sobreposio pode aumentar
ademanda por registradores e forar o alocador de registradores a derramar mais valores
acrescentando operaes de longa latncia e forando outra rodada de escalonamento.
O esquema de renomeao mais simples atribui um novo nome a cada valor medida
que produzido. No exemplo em andamento, este esquema produz o cdigo a seguir.
Esta verso do cdigo tem o mesmo padro de definies e usos.
REVISO DA SEO
Um escalonador de instrues local precisa atribuir um ciclo de execuo a cada operao. (Esses ciclos so numerados a partir da entrada para o bloco bsico.) No processo, ele deve garantir que nenhum ciclo no escalonamento tenha mais operaes
do que o hardware pode emitir em um nico ciclo. Em um processador escalonado
estaticamente, ele deve garantir que cada operao seja emitida somente depois que
seus operandos estejam disponveis; isto pode exibir que ele insira nops no escalonamento. Em um processador escalonado dinamicamente, ele deve minimizar o nmero
esperado de adiamentos que a execuo causar.
A estrutura de dados-chave para o escalonamento de instrues o grafo de dependncia para o bloco que est sendo processado, que representa o fluxo de dados no
bloco, e facilmente anotado com informaes sobre os adiamentos operao-por
-operao. O grafo de dependncia com as anotaes expe informaes importantes
sobre restries e caminhos crticos no bloco.
QUESTES DE REVISO
1. Que parmetros do processador alvo o escalonador poderia precisar? Encontre
esses parmetros para o processador do seu prprio computador.
2. bem conhecido, e bastante apreciado, que o escalonamento de instrues
interage com a alocao de registradores. Como este escalonamento interage
com a seleo de instrues? Existem modificaes no processo de seleo de instrues que poderamos fazer para simplificar o escalonamento?
12.3.1 O algoritmo
O escalonamento de lista clssico opera sobre um nico bloco bsico. Limitar nossa
considerao a sequncias de cdigo em linha reta permite-nos ignorar situaes que
possam complicar o escalonamento. Por exemplo, quando o escalonador considera mltiplos blocos, um operando pode depender de definies anteriores em diferentes blocos,
criando incerteza sobre quando o operando est disponvel para uso. A movimentao
de cdigo entre fronteiras de bloco cria outro conjunto de complicaes. Isso pode
mover uma operao para um caminho onde ela no existia antes; e tambm remover
uma operao de um caminho onde ela necessria. Restringir nossa considerao ao
caso de nico bloco evita essas complicaes. A Seo12.4 explora o escalonamento
entre blocos.
Para aplicar o escalonamento de lista a um bloco, o escalonador segue um plano em
quatro etapas.
1. Renomear para evitar antidependncias. Para reduzir o conjunto de restries
sobre o escalonador, o compilador renomeia valores. Cada definio recebe um
nome exclusivo. Esta etapa no estritamente necessria, porm, permite que
com relao ao cdigo ao seu redor, ao invs do hardware em que ele ser executado,
distribuindo oparalelismo disponvel localmente pelos loads no bloco. Essa estratgia
alivia o efeito de uma falha de cache, escalonando o mximo de atraso extra possvel para
cada load, o que no diminui a velocidade de execuo na ausncia de falhas de cache.
A Figura12.4 mostra a computao de atrasos para loads individuais em um bloco.
O algoritmo inicializa o atraso para cada load como 1. Em seguida, considera cada
operao i no grafo de dependncia D para o bloco e encontra as computaes em D
que so independentes de i, chamadas Di. Conceitualmente, esta tarefa um problema
de alcanabilidade em D. Podemos encontrar Di removendo de D cada n que seja um
predecessor, ou um sucessor, transitivo de i, junto com quaisquer arestas associadas a
esses ns.
O algoritmo, ento, encontra os componentes conectados de Di. Para cada componente C, encontra o nmero mximo N de loads em qualquer caminho isolado
atravs de C. N o nmero de loads em C que podem compartilhar o atraso da
operao i, de modo que o algoritmo acrescenta delay(i)/N ao atraso de cada
load em C. Para um determinado load l, a operao soma o compartilhamento
fracionrio do atraso de i de cada operao independente que pode ser usado para
cobrir a latncia de l. O uso desse valor como delay(l) produz um escalonamento
que compartilha o tempo vago de operaes independentes uniformemente por
todos os loads no bloco.
Por que isto acontece? O escalonador para a frente precisa colocar todas as operaes
com avaliao oito no escalonamento antes de quaisquer operaes com avaliao
sete. Embora a operao addI seja uma folha, sua avaliao mais baixa faz com que
o escalonador para a frente a adie. Quando o escalonador no tiver mais operaes
com avaliao oito, outras com avaliao sete estaro disponveis. Ao contrrio, o escalonador para trs coloca o addI antes de trs das operaes com avaliao oito um
resultado que o escalonador para a frente no poderia considerar.
E A EXECUO NO SEQUENCIAL?
Alguns processadores incluem suporte de hardware para executar instrues
deforma no-sequencial (ou fora de ordem) (OOO Out Of Order). Referimo-nos
atais processadores como mquinas escalonadas dinamicamente. Este recurso no
novo; por exemplo, ele apareceu no IBM 360/91. Para dar suporte execuo OOO,
um processador escalonado dinamicamente procura frente no fluxo de instrues,
por operaes que podem ser executadas antes do que seriam em um processador
escalonado estaticamente. Para fazer isto, o processador escalonado dinamicamente
constri e mantm uma parte do grafo de dependncia em tempo de execuo; usa
esta parte do grafo de dependncia para descobrir quando cada instruo pode ser
executada e emite cada instruo na primeira oportunidade vlida.
Quando um processador fora de ordem pode melhorar o escalonamento esttico?
Seas circunstncias em tempo de execuo forem melhores do que as suposies
feitas pelo escalonador, ento o hardware OOO pode emitir uma operao antes de sua
posio no escalonamento esttico. Isto pode acontecer em uma fronteira de bloco,
seum operando estiver disponvel antes do seu momento na pior dashipteses, o que
pode acontecer com uma operao de latncia varivel. Porconhecer os endereos de
runtime reais, um processador OOO tambm pode remover a ambiguidade dealgumas
dependncias de load-store que o escalonador no consegue.
A execuo OOO no elimina a necessidade de escalonamento de instrues. Como
a janela de antecipao finita, escalonamentos ruins podem provocar melhorias. Por
exemplo, uma janela de antecipao de 50 instrues no permitir que o processador
execute uma sequncia de 100 instrues de inteiros seguida por 100 instrues
deponto flutuante em pares intercalados de inteiro, ponto flutuante. Ele pode,
porm, intercalar sequncias mais curtas, digamos, de tamanho 30. A execuo OOO
ajuda o compilador melhorando bons, mas no timos, escalonamentos.
Um recurso relacionado do processador a renomeao dinmica de registradores.
Este esquema fornece ao processador mais registradores fsicos do que a ISA permite
que o compilador nomeie. O processador pode quebrar as antidependncias que
ocorrem dentro de sua janela de antecipao usando registradores fsicos adicionais
que esto escondidos do compilador para implementar duas referncias conectadas
por uma antidependncia.
REVISO DA SEO
O escalonamento de lista o paradigma dominante que os compiladores tm usado
para escalonar operaes h muito anos. Ele calcula, para cada operao, o ciclo em
que dever ser emitida. O algoritmo razoavelmente eficiente; sua complexidade
relaciona-se diretamente com o grafo de dependncia subjacente. Esta tcnica heurstica gulosa, em suas formas para a frente e para trs, produz resultados excelentes
para locos isolados.
Os algoritmos que realizam escalonamento sobre regies maiores no CFG usam o
escalonamento de lista para ordenar operaes. Seus pontos fortes e fracos so transferidos para esses outros domnios. Assim, quaisquer melhorias feitas ao escalonamento de lista local tambm tm potencial para melhorar os algoritmos de escalonamento
regionais.
QUESTES DE REVISO
1. Voc dever implementar um escalonador de lista para um compilador que
produzir cdigo para o seu laptop. Que mtrica voc usa como sua avaliao
principal para a lista Ready e como realiza desempates? Justifique suas escolhas.
2. Diferentes mtricas de prioridade fazem com que o escalonador considere as operaes em diferentes ordens. Voc poderia aplicar a randomizao para alcanar
efeitos semelhantes?
Para obter um contexto maior para o escalonamento de lista, o compilador pode tratar
os caminhos em um EBB, como B1, B2, B4, como se fossem blocos isolados, desde
que leve em considerao os prefixos de caminho compartilhados, como B1, que ocorre
tanto em B1, B2, B4 como em B1, B3, e as sadas prematuras, como B1 B3 e B2
B5. (Vimos, na Seo 8.5.1, este mesmo conceito no algoritmo de numerao de
valor superlocal.) Esta tcnica permite que o compilador aplique seu mecanismo de
escalonamento altamente eficaz escalonamento de lista a sequncias de operaes
maiores, cujo efeito aumentar a frao de cdigo que escalonada junta, o que deve
melhorar os tempos de execuo.
Para ver como os prefixos compartilhados e as sadas prematuras complicam o escalonamento de lista, considere as possibilidades para movimentao de cdigo no
caminho B1, B2, B4 do exemplo na margem. Essa movimentao de cdigo pode
exigir que o escalonador insira cdigo de compensao para manter o resultado
correto.
j
Cdigo de compensao
Cdigo inserido em um bloco Bi para combater
osefeitos da movimentao de cdigo entre blocos
aolongo de um caminho que no inclui Bi.
O compilador pode mover uma operao para a frente ou seja, mais adiante
no caminho. Por exemplo, ele poderia mover a operao c de B1 para B2. Embora
essa deciso possa acelerar a execuo ao longo do caminho B1, B2, B4, ela
muda a computao realizada ao longo do caminho B1, B3. Mover c paraa
frente a partir de B1 significa que o caminho B1, B3 no executa mais c. A
menos que c esteja morto ao longo de todos os caminhos comeando a partir de
B3, o escalonador deve corrigir esta situao.
Para tanto, o escalonador deve inserir uma cpia de c em B3. Se foi vlido mover c para
aps d em B1, B2, B4, deve ser legal mover c para aps d tambm em B1, B3, pois
as dependncias que poderiam impedir essa movimentao esto totalmente contidas
em B1. A nova cpia de c no estende a execuo ao longo do caminho B1, B3, mas
aumenta o tamanho global do fragmento decdigo.
j
REVISO DA SEO
Tcnicas de escalonamento regional utilizam diversos mtodos para construir segmentos maiores de cdigo em linha reta para o escalonamento de lista. A qualidade
do cdigo produzido por esses mtodos, at certo ponto, determinada pela
qualidade do escalonador subjacente. A infraestrutura do escalonamento regional
simplesmente fornece mais contexto e mais operaes ao escalonador de lista, numa
tentativa de lhe oferecer mais liberdade e oportunidades.
Todas as trs tcnicas examinadas nesta seo devem lidar com cdigo de compensao. Embora este cdigo introduza complicaes nos algoritmos e possa inserir
atrasos ao longo de alguns caminhos, a experincia sugere que os benefcios do escalonamento regional superam as complicaes.
QUESTES DE REVISO
1. No escalonamento de EBB, o compilador precisa escalonar alguns blocos comrelao aos seus prefixos j escalonados. Uma implementao simples poderia
reanalisar os blocos pr-escalonados e reconstruir seus grafos de dependncia.
Que estruturas de dados seu compilador poderia usar para evitar este trabalho
extra?
2. O escalonamento de trao e a clonagem por contexto tentam melhorar os
resultados do escalonamento de EBB. Compare essas duas tcnicas. Como voc
espera que os resultados sejam diferentes?
A Figura12.7 mostra o cdigo que um compilador poderia gerar para este lao aps
a otimizao. Neste caso, tanto a reduo de fora de operador quanto a substituio
de teste de funo linear foram aplicados (ver Seo 10.4), de modo que as expresses de endereo para x, y e z so atualizadas com operaes addI, e o teste de final
de lao foi reescrito em termos do deslocamento em x, eliminando a necessidade de
manter um valor para i.
O cdigo na Figura12.7 foi escalonado para uma mquina com uma unidade funcional,
considerando que loads e stores usam trs ciclos; multiplicaes, dois; e todas as outras
operaes, um ciclo. A primeira coluna mostra contagens de ciclo, normalizadas pela
primeira operao no lao (no label L1).
Ncleo do lao
Parte central de um lao com pipeline de software; o
kernel executa a maior parte das iteraes do lao em
um padro intercalado.
O cdigo de pr-lao inicializa um ponteiro para cada array (r@x, r@y e r@z). Ele calcula
um limite superior para o intervalo de r@x em rub; o teste de fim de lao usa rub. O
corpo do lao carrega x e y, realiza a multiplicao e armazena o resultado em z.
Oescalonador preencheu todos os slots de emisso na sombra das operaes de longa
latncia com outras operaes. Durante as latncias de load, o escalonamento atualiza
r@x e r@y. Ele realiza a comparao na sombra da multiplicao, e preenche os slots
aps o store com a atualizao de r@z e o desvio, o que produz um escalonamento
apertado para uma mquina com uma unidade funcional.
Considere o que acontece se executarmos o mesmo cdigo em um processador superescalar com duas unidades funcionais e as mesmas latncias. Suponha que loads e stores
devam ser executados na unidade 0, que as unidades funcionais so adiadas quando
uma operao emitida antes que seus operandos estejam prontos, e que o processador no possa emitir operaes para uma unidade adiada. A Figura12.8 mostra o
acompanhamento de execuo da primeira iterao do lao. O mult no ciclo 3
adiado porque nem rx nem ry esto prontos. Ele adiado no ciclo 4 esperando por
ry, comea a executar novamente no ciclo 5 e produz rz ao final do ciclo 6. Isto fora
o storeAO ser adiado at o incio do ciclo 7. Supondo que o hardware possa dizer
que r@z contm um endereo que distinto de r@x e r@y, o processador pode emitir
o primeiro loadAO para a segunda iterao no ciclo 7. Se no, o processador ser
adiado at que o store termine.
O uso de duas unidades funcionais melhorou o tempo de execuo; cortou o tempo de
pr-lao ao meio, para dois ciclos; e reduziu o tempo entre o incio de iteraes sucessivas
em um tero, para seis ciclos. O caminho crtico executado to rapidamente quanto
podemos esperar; a multiplicao emitida antes que ry esteja disponvel, e executada
o mais cedo possvel. O store prossegue assim que rz estiver disponvel. Alguns slots
de emisso so desperdiados (a unidade 0 no ciclo 6 e a unidade 1 nos ciclos 1 e 4).
A reordenao do cdigo linear pode mudar o escalonamento de execuo. Por exemplo,
mover a atualizao de r@x para a frente do load de r@y permite que o processador emita
as atualizaes de r@x e r@y nos mesmos ciclos dos loads desses registradores. Isto permite que algumas das operaes sejam emitidas anteriormente no escalonamento, mas
no faz nada para acelerar o caminho crtico. O resultado disso o mesmo um lao de
seis ciclos. O pipelining do cdigo pode reduzir o tempo necessrio para cada iterao,
como vemos na Figura12.9. Neste caso, ele reduz o nmero de ciclos por iterao de seis
para cinco. A prxima subseo apresenta o algoritmo que gerou esse escalonamento.
Recorrncia
Computao baseada em lao que cria um ciclo
nografo de dependncia.
Uma recorrncia precisa se espalhar por vrias iteraes.
Escalonamento do kernel
Para escalonar o kernel, o compilador usa o escalonamento de lista com um escalonamento de tamanho fixo de ii slots. As atualizaes no relgio de escalonamento,
Cycle na Figura12.3, so realizadas em mdulo ii (mod ii). O escalonamento de lao
introduz uma complicao que no pode surgir no cdigo em linha reta (por exemplo,
um bloco, um EBB ou um trao): ciclos no grafo de dependncia.
Escalonamento de mdulo
O escalonamento de lista com um relgio cclico
svezes chamado escalonamento de mdulo.
O escalonador deve reconhecer que as dependncias transportadas por lao, como (g,
e), no restringem a primeira iterao do lao. (As dependncias transportadas por lao
so desenhadas em cinza na Figura12.10b.) Na primeira iterao, somente as operaes
e e f dependem unicamente dos valores calculados antes do lao.
As dependncias transportadas por lao tambm expem antidependncias. No exemplo, uma antidependncia vai de e para g; o cdigo no pode atualizar r@x antes de
us-lo na operao load. Antidependncias semelhantes de f para h e de k para l. Se
assumirmos que uma operao l seus operandos no incio do ciclo, quando emitida,
e escreve seu resultado ao final do ciclo, quando a operao termina, ento o atraso
em uma antidependncia zero. Assim, o escalonamento da operao na origem da
antidependncia satisfaz a restrio da antidependncia. Veremos esse comportamento
no exemplo a seguir.
O escalonamento de mdulo do grafo de dependncia do lao para um escalonamento
de cinco ciclos e duas unidades funcionais produz o escalonamento de kernel mostrado
na Figura12.11. No ciclo 1, com uma lista Ready inicial (e, f), o escalonador escolhe
e, usando alguma forma de desempate, e escalona e na unidade 0. Este escalonamento
satisfaz a antidependncia para g. Como as nicas dependncias que entram em g de
dentro do lao so dependncias transportadas por lao, g agora est pronto e pode ser
escalonado para a unidade 1 no ciclo 1.
NOTAS DO CAPTULO
Problemas de escalonamento surgem em muitos domnios, variando desde construo,
passando por produo industrial, servios de entrega, chegando at cargas teis em
veculos espaciais. Uma literatura rica a respeito de escalonamento tem crescido,
EXERCCIOS
Seo 12.2
1. Desenvolva um algoritmo que constri o grafo de dependncia para um bloco
bsico. Suponha que o bloco seja escrito em ILOC e que quaisquer valores
definidos fora do bloco estejam prontos antes que a execuo do bloco comece.
2. Se o uso principal para um grafo de dependncia for o escalonamento de instrues, ento a modelagem exata dos atrasos reais na mquina alvo crtica.
a. Como o grafo de dependncia modela a incerteza causada por referncias
de memria ambguas?
b. Em alguns processadores em pipeline, os atrasos de escrita-aps-leitura podem
ser mais curtos do que os de leitura-aps-escrita. Por exemplo, a sequncia
leria o valor de r10 para uso no add antes de escrever o resultado do sub em
r10. Como um compilador pode representar antidependncias em um grafo
dedependncia para tal arquitetura?
c. Alguns processadores evitam (bypass) a memria para reduzir os atrasos
deleitura-aps-escrita. Nessas mquinas, uma sequncia como
Seo 12.4
7. A ordem em que as operaes ocorrem determina quando os valores so criados
e quando so usados pela ltima vez. Juntos, esses efeitos determinam o tempo
de vida do valor.
a. Como o escalonador pode reduzir a demanda por registradores? Sugira heursticas de desempate concretas que se ajustariam a um escalonador de lista.
b. Qual a interao entre esses desempatadores orientados por registrador e a
capacidade do escalonador de produzir escalonamentos curtos?
8. O pipelining de software sobrepe iteraes de lao para criar um efeito que
semelhante ao pipelining de hardware.
a. Que impacto o pipelining de software ter sobre a demanda por registradores?
b. Como o escalonador pode usar a execuo predicada para reduzir a penalidade de espao de cdigo para o pipelining de software?
Seo 12.5
9. O cdigo de exemplo na Figura12.7 gera um kernel de pipeline de software de
cinco ciclos porque contm nove operaes. Se o compilador escolhesse um esquema diferente para gerar os endereos de x, y e z, poderia reduzir ainda mais
o contador de operaes no corpo do lao.
Esse esquema usa um registrador a mais, rctr, do que a verso original. Assim,
dependendo do contexto, ele poderia ter que derramar cdigo onde o original no faria.
a. Calcule RC e DC para esta verso do lao.
b. Gere o corpo do lao com pipeline de software.
c. Gere o cdigo de prlogo e eplogo para o seu corpo de lao com pipeline.
Captulo
13
Alocao de registradores
VISO GERAL DO CAPTULO
O cdigo gerado por um compilador precisa fazer uso efetivo dos recursos limitados do
processador alvo. Entre os recursos mais restritos est o conjunto de registradores de
hardware. Assim, a maioria dos compiladores inclui um passo que tanto aloca quanto
atribui registradores de hardware aos valores do programa.
Este captulo se concentra na alocao e atribuio global de registradores por meio da
colorao de grafo; e descreve os problemas que ocorrem em escopos menores como
um meio de motivar um alocador global.
Palavras-chave: Alocao de registradores, Derramamento de registradores, Agrupamento de cpia, Alocadores de colorao de grafo
13.1INTRODUO
Registradores so os locais mais rpidos na hierarquia de memria. Normalmente, so
os nicos locais de memria que a maioria das operaes pode acessar diretamente.
A proximidade dos registradores com as unidades funcionais faz do seu bom uso um
fator crtico no desempenho de execuo. Em um compilador, a responsabilidade
por fazer bom uso do conjunto de registradores da mquina-alvo encontra-se com o
alocador de registradores.
Este alocador determina, em cada ponto no programa, quais valores residiro nos registradores e qual registrador manter cada um desses valores. Se o alocador no puder
manter um valor em um registrador por todo seu tempo de vida, o valor dever ser
armazenado na memria por parte ou todo seu tempo de vida. O alocador pode relegar
um valor memria porque o cdigo contm mais valores vivos do que o conjunto
de registradores da mquina-alvo pode manter. Alternativamente, o valor poderia ser
mantido na memria entre os usos, porque o alocador no pode provar que ele pode
residir com segurana em um registrador.
Roteiro conceitual
Conceitualmente, o alocador de registradores usa como entrada um programa que utiliza
um nmero qualquer de registradores. E produz, como sada, um programa equivalente
que cabe no conjunto de registradores finito da mquina-alvo
O alocador pode ter de inserir loads e stores para movimentar valores entre registradores
e memria. O objetivo da alocao de registradores fazer uso eficaz do conjunto de
registradores da mquina alvo e minimizar o nmero de loads e stores que o cdigo
precisa executar.
563
A alocao de registradores desempenha um papel direto na criao de cdigo executvel que executado rapidamente, pelo simples motivo de que os acessos aos registradores so mais rpidos do que os acessos memria. Ao mesmo tempo, os problemas
algortmicos, que so a base da alocao de registradores, so difceis em sua forma
geral, desafiam a soluo tima. Um bom alocador de registradores calcula uma soluo
aproximada eficaz para um problema difcil, e o faz rapidamente.
Viso geral
Para simplificar as primeiras partes do compilador, a maioria dos compiladores utiliza
uma IR em que o espao de nomes no est ligado ao espao de endereos do processador alvo ou ao seu conjunto de registradores. Para traduzir o cdigo em IR para
cdigo em assembly para a mquina-alvo, esses nomes devem ser mapeados para o
espao de nomes usado na ISA da mquina-alvo. Os valores armazenados na memria
no programa em IR devem ser transformados em coordenadas estticas que, por sua
vez, so mapeadas para endereos de runtime usando tcnicas como aquelas descritas
na Seo6.4.3. Os valores armazenados em registradores virtuais na IR devem ser
mapeados para os registradores fsicos dos processadores.
Se a IR modelar a computao com um modelo de armazenamento de memria-para-memria, ento o alocador de registradores promove valores voltados para a memria
aos registradores nas regies onde so mais utilizados. Neste modelo, a alocao de
registradores uma otimizao que melhora o desempenho do programa eliminando
as operaes de memria.
Por outro lado, se a IR modelar o cdigo com um modelo de armazenamento de registrador-para-registrador, o alocador de registradores precisa decidir, em cada ponto no
cdigo, quais registradores virtuais devero residir em registradores fsicos e quais
podem residir na memria. Ele constri um mapa dos registradores virtuais na IR para
alguma combinao de registradores fsicos e locais de memria, e reescreve o cdigo
para refletir esse mapeamento. Neste modelo, a alocao de registradores precisa criar
um programa correto na mquina alvo; ele insere loads e stores no cdigo e tenta
coloc-los onde prejudicaro menos o desempenho.
Cdigo de derramamento
Loads e stores inseridos pelo alocador de registradores.
Se houver mais registradores virtuais do que fsicos, o alocador deve reservar registradores fsicos suficientes para permitir que carregue, armazene e use os valores que
no esto mantidos em registradores. O nmero exato de registradores de que ele
precisa depende do processador. Uma mquina RISC tpica poderia precisar de dois a
quatro registradores. Vamos nos referir a este nmero especfico da mquina como F.
Se o bloco usa menos de k registradores virtuais, a alocao trivial e o compilador
pode simplesmente atribuir cada vr ao seu prprio registrador fsico. Neste caso, o
alocador no precisa separar os F registradores fsicos para derramamento de cdigo.
Se o bloco usar mais do que k registradores virtuais, o compilador aplica o seguinte
algoritmo simples:
1. Calcular uma prioridade para cada registrador virtual. Em uma passagem linear
pelas operaes no bloco, o alocador conta o nmero de vezes que cada registrador virtual aparece. Essa contagem de frequncia a prioridade do registrador
virtual.
2. Classificar os registradores virtuais por ordem de prioridade. As prioridades
variam entre dois e o tamanho do bloco, de modo que o melhor algoritmo de
classificao depende do tamanho do bloco.
3. Atribuir registradores em ordem de prioridade. Atribua os primeiros k F
registradores virtuais aos registradores fsicos.
4. Reescrever o cdigo. Em uma passagem linear pelo cdigo, o alocador reescreve
o cdigo, e substitui os nomes de registrador virtual por nomes de registrador
fsico. Qualquer referncia ao nome de um registrador virtual sem registrador
fsico alocado substituda por uma curta sequncia que usa um dos registradores reservados e realiza a operao load ou store apropriada.
A alocao local de cima para baixo mantm registradores virtuais altamente ocupados
em registradores fsicos. Seu principal ponto fraco est em sua tcnica de alocao
ela dedica um registrador fsico a um registrador virtual para o bloco bsico inteiro.
Assim, um valor que v um uso intenso na primeira metade do bloco e nenhum na
segunda metade do bloco efetivamente desperdia esse registrador por toda a segunda
metade do bloco. A prxima seo apresenta uma tcnica que ataca este problema.
Ela usa um procedimento fundamentalmente diferente para a alocao uma tcnica
incremental de baixo para cima.
em ordem, o alocador evita usar dois registradores fsicos para uma operao com um
operando repetido, como add ry, ry rz. De modo semelhante, tentar liberar rx
e ry antes de alocar rz evita derramar um registrador para manter o resultado de uma
operao quando ela realmente libera um registrador. A maior parte das complicaes
no algoritmo ocorre nas rotinas Ensure, Allocate e Free.
A rotina Ensure conceitualmente simples. Usa dois argumentos, um registrador
virtual, vr, que mantm o valor desejado, e uma representao para a classe de registrador apropriada, class. Se vr j ocupa um registrador fsico, o trabalho de Ensure
termina. Caso contrrio, ela aloca um registrador fsico para vr e emite cdigo para
mover o valor de vr para esse registrador fsico. Em qualquer caso, a rotina retorna
o registrador fsico.
Allocate e Free expem os detalhes do problema de alocao. Para entend-las,
precisamos de uma representao concreta para uma classe de registradores, mostrada no
cdigo C margem. Uma classe tem Size registradores fsicos, cada um representado
por um nome de registrador virtual (Name), um inteiro que indica a distncia at seu
prximo uso (Next) e um flag indicando se esse registrador fsico est atualmente em
uso ou no (Free). Para inicializar a estrutura class, o compilador define cada registrador
para um estado no alocado (digamos, class.Name como um nome invlido, class.
Next como e class. Free como true) e coloca cada um deles na pilha da classe.
Nesse nvel de detalhe, tanto Allocate quanto Free so simples. Cada classe tem
uma pilha de registradores fsicos livres. Allocate retorna um registrador fsico
a partir da lista livre de class, se houver algum. Caso contrrio, seleciona o valor
armazenado em class que usado mais longe, no futuro, derrama-o e realoca o registrador fsico correspondente a vr. Allocate define o campo Next como 1 para
garantir que esse registrador no ser escolhido para o outro operando na operao
atual. O alocador reinicia esse campo aps ele terminar com a operao atual. Free
simplesmente precisa empilhar o registrador liberado e reiniciar seus campos com seus
valores iniciais. A funo Dist(vr) retorna o ndice no bloco da prxima referncia
a vr. O compilador pode pr-calcular essa informao em uma passagem de trs para
a frente pelo bloco.
O alocador local de baixo para cima opera de modo intuitivo: considera que os registradores fsicos esto inicialmente vazios e coloca todos em uma lista livre; satisfaz a
demanda por registradores a partir da lista livre, at que essa lista se esgote. Depois,
satisfaz a demanda derramando algum valor para a memria e reutilizando o registrador
desse valor. Ele sempre derrama o valor cujo prximo uso est mais distante no futuro.
Intuitivamente, seleciona o registrador que, de outra maneira, seria no referenciado
pelo perodo de tempo mais longo. De certa forma, ele maximiza o benefcio obtido
para o custo do derramamento.
Na prtica, esse algoritmo produz excelentes alocaes locais. Realmente, vrios
autores tm argumentado que ele produz alocaes timas. Porm, aparecem complicaes que podem fazer com que ele produza alocaes subtimas. Em qualquer
ponto da alocao, alguns valores em registradores podem precisar ser armazenados
em um derramamento, enquanto outros no. Por exemplo, se o registrador contm um
valor constante conhecido, o store suprfluo, pois o alocador pode recriar o valor sem
ter uma cpia na memria. De modo semelhante, um valor que foi criado por um load
da memria no precisa ser armazenado. Um valor que no precisa ser armazenado
chamado limpo, enquanto um valor que precisa de um store chamado sujo.
Para produzir uma alocao local tima, o alocador precisa levar em considerao a
diferena no custo entre derramar valores limpos e derramar valores sujos. Considere,
por exemplo, a alocao em uma mquina com dois registradores, onde os valores x1
e x2 j esto nos registradores. Suponha que x1 seja limpo e x2 sujo. Se a string de
referncia para o restante do bloco for x3 x1 x2, o alocador precisa derramar um dentre
x1 e x2. Como o prximo uso de x2 encontra-se mais distante no futuro, o algoritmo
local de baixo para cima o derramaria, produzindo a sequncia de operaes de memria
mostrada abaixo, esquerda. Se, ao invs disso, ele derramar x1, produzir a sequncia
mais curta de operaes de memria, mostrada direita.
Esse cenrio sugere que o alocador deve preferencialmente derramar valores limpos
ao invs de valores sujos. A resposta no to simples.
Considere outra sequncia de referncia, x3 x1 x3 x1 x2, com as mesmas condies
iniciais. Derramar consistentemente o valor limpo produz a sequncia de quatro
A presena de valores limpos e sujos torna a alocao local tima NP-difcil. Ainda
assim, o alocador local bottom-up produz boas alocaes locais na prtica. As alocaes
costumam ser melhores do que aquelas produzidas pelo algoritmo de cima para baixo.
Faixa viva
Um conjunto fechado de definies e usos relacionados,
que serve como espao de nomes bsico para alocao
de registradores.
subsequente poderia usar v. Lembre-se de que v pode ser tanto uma varivel do programa fonte, como um nome temporrio gerado pelo compilador.
O conjunto de faixas vivas distinto do conjunto de variveis e do conjunto de valores.
Cada valor calculado no cdigo faz parte de alguma faixa viva, mesmo que no tenha
um nome no cdigo-fonte original. Assim, os resultados intermedirios produzidos
pelos clculos de endereo so faixas vivas, assim como as variveis nomeadas pelo
programador, elementos de array e endereos carregados para uso como destinos de
desvio. Uma nica varivel da linguagem-fonte pode formar vrias faixas vivas. Um
alocador que atua sobre faixas vivas pode colocar faixas vivas distintas em diferentes
registradores. Assim, uma varivel da linguagem-fonte poderia residir em diferentes
registradores em pontos distintos no programa em execuo.
No cdigo em linha reta, podemos representar uma
faixa viva como um intervalo [i, j] onde a operao i a
define e a operao j seu ltimo uso.
Para faixas vivas que se espalham por vrios blocos,
precisamos de uma notao mais complexa.
Para tornar essas ideias concretas, primeiro considere o problema de encontrar faixas
vivas em um nico bloco bsico. A Figura13.2 repete o cdigo ILOC que encontramos
inicialmente na Figura 1.3, com o acrscimo de uma operao inicial que define rarp.
A tabela no lado direito mostra as faixas vivas distintas no bloco. No cdigo em linha reta, podemos representar uma faixa viva como um intervalo. Observe que cada
operao define um valor e, assim, inicia uma faixa viva. Considere rarp. Ele definido
na operao 1. Cada outra referncia a rarp um uso. Assim, o bloco usa apenas um
valor para rarp, que vivo sobre o intervalo [1,11].
Ao contrrio, ra tem diversas faixas vivas. A operao 2 a define; a operao 7 usa
o valor da operao 2. As operaes 7, 8, 9 e 10 definem, cada uma, um novo valor
para ra; em cada caso, a operao a seguir usa o valor. Assim, o valor chamado ra no
cdigo original corresponde a cinco faixas vivas distintas: [2,7], [7,8], [8,9], [9,10] e
[10,11]. Um alocador de registradores no precisa manter essas faixas vivas distintas
no mesmo registrador fsico. Ao invs disso, ele pode tratar cada faixa viva no bloco
como um valor independente para alocao e atribuio.
Para encontrar faixas vivas em regies maiores, o alocador precisa entender quando um
valor est vivo aps o final do bloco que o define. Os conjuntos LIVEOUT, conforme
computados na Seo8.6.1, codificam exatamente esse conhecimento. Em qualquer
bem definido. Mesmo que todos os usos subsequentes de x sejam equidistantes antes
da alocao, o derramamento local em um bloco poderia aumentar as distncias em
um ou mais caminhos. Como a mtrica bsica que est por trs do mtodo local de
baixo para cima multivalorada, os efeitos do algoritmo tornam-se mais difceis de
entender e justificar
Os efeitos nas fronteiras de bloco podem ser complexos. Eles no se ajustam a um
alocador local porque lidam com fenmenos que esto totalmente fora do seu escopo.
Todos esses problemas sugerem que uma tcnica diferente necessria para ir alm
da alocao local, para alocao regional ou global. Na realidade, os algoritmos de
alocao global bem-sucedidos tm pouca semelhana com os locais.
REVISO DA SEO
A alocao de registradores local examina um nico bloco bsico. Este contexto
limitado simplifica a anlise e o algoritmo. Esta seo apresentou um algoritmo de
cima para baixo (top-down) e um algoritmo de baixo para cima (bottom-up) para
a alocao local. O algoritmo de cima para baixo prioriza valores pelo nmero de
referncias a esse valor no bloco; atribui os valores de prioridade mais alta aos registradores; e reserva um pequeno conjunto de registradores para lidar com aqueles
valores que no recebem registradores. O alocador de baixo para cima atribui valores
a registradores medida que os encontra em uma passagem para a frente pelo bloco.
Quando precisa de um registrador adicional, derrama o valor cujo prximo uso est
mais distante no futuro.
Os alocadores de cima para baixo e de baixo para cima apresentados aqui diferem no
modo como tratam valores individuais. Quando o algoritmo de cima para baixo aloca
um registrador para algum valor, ele reserva aquele registrador para o bloco inteiro.
Quando o algoritmo de baixo para cima aloca um registrador para algum valor, ele
reserva aquele registrador at que encontre uma necessidade mais imediata para
ele. A capacidade do algoritmo de baixo para cima de usar um nico registrador para
diversos valores permite-lhe produzir melhores alocaes do que o algoritmo de
cima para baixo. Os paradigmas de alocao nesses dois algoritmos comeam a falhar
quando tentamos aplic-los a regies maiores.
QUESTES DE REVISO
1. Para cada um dos dois alocadores, responda s seguintes perguntas: Que etapa no
alocador tem a pior complexidade assinttica? Como o construtor de compiladores poderia limitar seu impacto sobre o tempo de compilao?
2. O alocador de cima para baixo agrega contagens de frequncia por nomes de
registrador virtual e realiza a alocao por nomes de registrador virtual. Esboce um
algoritmo que renomeie registradores virtuais de modo que melhore os resultados
do algoritmo de cima para baixo.
COLORAO DE GRAFO
Muitos alocadores de registrador global usam a colorao de grafo como um paradigma
para modelar o problema bsico da alocao. Para um grafo arbitrrio G, a colorao
de G atribui uma cor a cada n em G, de modo que nenhum par de ns adjacentes
tenha a mesma cor. Uma colorao que usa k cores chamado de k-colorao (o grafo
chamado k-colorido), e o menor valor de k para um determinado grafo chamado
nmero cromtico do grafo. Considere os seguintes grafos:
Grafo de interferncia
Grafo onde os ns representam faixas vivas e uma
aresta (i, j) indica que LRi e LRj no podem compartilhar
um registrador.
trata cada nome SSA, ou definio, como um conjunto no algoritmo. Examina cada
funo- no programa e une os conjuntos associados a cada parmetro de funo- e
o conjunto para o resultado da funo-. Aps todas as funes- terem sido processadas, os conjuntos resultantes representam as faixas vivas no cdigo. Neste ponto, o
alocador pode reescrever o cdigo para usar nomes de faixas vivas, ou criar e manter
um mapeamento entre nomes SSA e nomes de faixas vivas.
A Figura13.3a mostra um fragmento de cdigo na forma SSA semipodada, que envolve
variveis de cdigo fonte, a, b, c e d. Para encontrar as faixas vivas, o alocador atribui
a cada nome SSA um conjunto contendo seu nome, e une os conjuntos associados
aos nomes usados na funo-, {d0} {d1} {d2}. Isto gera um conjunto final de
quatro faixas vivas: LRa que contm {a0}, LRb que contm {b0}, LRc que contm
{c0} e LRd que contm {d0,d1,d2}. A Figura13.3b mostra o cdigo reescrito para
usar nomes de faixas vivas.
deve atribuir faixa viva original um custo de derramamento infinito, garantindo que no
tentar derram-la. Em geral, uma faixa viva deve ter custo de derramamento infinito
se nenhuma outra faixa viva termina entre suas definies e seus usos. Esta condio
estipula que a disponibilidade de registradores no muda entre as definies e os usos.
Se o compilador puder colorir I com k ou menos cores, ento pode mapear as cores
diretamente para registradores fsicos, a fim de produzir uma alocao vlida. No
exemplo, LRa no pode receber a mesma cor de LRb, LRc ou LRd, pois ela interfere
com cada uma dessas. Porm, as outras trs faixas vivas podem todas compartilhar uma
nica cor, pois no interferem umas com as outras. Assim, o grafo de interferncia
2-colorido, e o cdigo pode ser reescrito para usar apenas dois registradores.
Considere o que aconteceria se outra fase do compilador reordenasse as duas operaes
no final de B1. Essa mudana torna LRb viva na definio de LRd. O alocador deve
acrescentar a aresta (LRb,LRd) a E, o que torna impossvel colorir o grafo com apenas
duas cores. (O grafo pequeno o suficiente para provar isto por enumerao.) Para lidar
Interferncia
Duas faixas vivas, LRi e LRj, interferem se uma estiver
viva na definio da outra e elas tiverem valores
diferentes.
com esse grafo, o alocador tem duas opes: usar trs registradores ou, se a mquina
alvo tiver apenas dois registradores, derramar um de LRb ou LRa antes da definio de
LRd em B1. Naturalmente, o alocador tambm poderia reordenar as duas operaes e
eliminar a interferncia entre LRb e LRd. Normalmente, os alocadores de registradores
no reordenam operaes. Ao invs disso, assumem uma ordem fixa de operaes e
deixam as questes de ordenao para o escalonador de instrues (ver Captulo 12).
Construo de um alocador
Para criar um alocador global baseado no paradigma de colorao de grafo, o construtor de compiladores precisa de dois mecanismos adicionais. Primeiro, uma tcnica
eficiente para descobrir as k-coloraes. Infelizmente, o problema de determinar se uma
k-colorao existe para um determinado grafo NP-completo. Assim, os alocadores
de registradores usam aproximaes rpidas que no tm garantias de encontrar uma
k-colorao. Segundo, de uma estratgia que trate do caso em que no reste cor alguma
para uma faixa viva especfica. A maioria dos alocadores de colorao resolve isto
reescrevendo o cdigo para mudar o problema de alocao. O alocador apanha uma
ou mais faixas vivas para modificar; e, ou derrama ou divide as faixas vivas escolhidas.
O derramamento transforma a faixa viva escolhida em conjuntos de pequenas faixas
vivas, uma para cada definio ou uso da faixa viva original. A diviso quebra a faixa
viva escolhida em partes menores, no triviais. De qualquer forma, o cdigo transformado realiza a mesma computao, mas tem um grafo de interferncia diferente.
Se as mudanas forem efetivas, o novo grafo de interferncia k-colorvel. Se no, o
alocador deve derramar ou dividir mais faixas vivas.
Tratamento de derramamentos
Quando o alocador de cima para baixo encontra uma faixa viva que no pode ser
colorida, deve derramar ou dividir algum conjunto de faixas vivas para mudar o problema. Como todas as faixas vivas coloridas anteriormente foram classificadas com
prioridade mais alta do que a faixa viva no colorida, faz sentido derramar esta ltima
ao invs de uma colorida anteriormente. O alocador pode considerar a recolorao de
uma das faixas vivas coloridas anteriormente, mas precisa ter cuidado para evitar a
generalidade total e o custo do retrocesso.
Para derramar LRi, o alocador insere um store aps cada definio de LRi e um load antes
de cada uso. Se as operaes de memria precisarem de registradores, o alocador pode
reservar registradores suficientes para cuidar delas. (Por exemplo, um registrador necessrio para manter o valor derramado quando ele for carregado antes de um uso.) O nmero
de registradores necessrios para esta finalidade uma funo da arquitetura do conjunto
de instrues da mquina-alvo. A reserva desses registradores simplifica o derramamento.
Uma alternativa reserva de registradores para o cdigo de derramamento procurar
por cores livres em cada definio e uso; se nenhuma estiver disponvel, o alocador deve
derramar retroativamente uma faixa viva que j foi colorida. Neste esquema, o alocador
inseriria o cdigo de derramamento, que remove a faixa viva original e cria uma nova
faixa viva curta, s. Recalcularia interferncias na vizinhana do local de derramamento
e contaria as cores atribudas aos vizinhos de s. Se este processo no descobrir uma cor
disponvel para s, o alocador derrama o vizinho de menor prioridade de s.
Naturalmente, este esquema tem o potencial de derramar, de forma recursiva, faixas
vivas coloridas anteriormente, recurso este que tem levado a maioria dos implementadores de alocadores de cima para baixo baseados em prioridade a reservar registradores
de derramamento em seu lugar. O paradoxo, claro, que reservar registradores de
derramamento pode, por si s, causar derramamentos, pela efetiva reduo de k.
Para escolher uma cor para o n n, o alocador conta as cores dos vizinhos de n na
aproximao atual para I e atribui a n uma cor no usada. Para escolher uma cor especfica, ele pode procurar em uma ordem consistente a cada vez, ou atribuir cores
em um padro de rodzio. (Em nossa experincia, o mecanismo usado para a escolha
de cor tem pouco impacto na prtica.) Se nenhuma cor restar para n, ele fica sem cor.
Quando a pilha estiver vazia, I ter sido recriado. Se cada n tiver uma cor, o alocador
declara sucesso e reescreve o cdigo, substituindo os nomes de faixa viva por registradores fsicos. Se algum n permanecer sem cor, o alocador derramar a faixa viva
correspondente ou a dividir em partes menores. Neste ponto, os alocadores clssicos
de baixo para cima reescrevem o cdigo para refletir os derramamentos e divises,
repetindo o processo inteiro encontrando faixas vivas, construindo I e colorindo-o.
O processo repetido at que cada n em I receba uma cor. Normalmente, o alocador
termina depois de algumas iteraes. Naturalmente, um alocador de baixo para cima
poderia reservar registradores para derramamento, como o alocador de cima para baixo
faz. Esta estratgia lhe permitiria parar aps uma nica passada.
custo
, que enfatiza o
grau 2
impacto sobre os vizinhos; custo direto, que enfatiza a velocidade em tempo de execuo; e contagem das operaes de derramamento, que diminui o tamanho do cdigo.
Os dois primeiros, custo de derramamento custo e custo , tentam balancear custo
grau
grau
grau 2
O exemplo ilustra a impreciso inerente nessa atualizao conservadora de I. A atualizao deixaria uma interferncia entre LRab e LRc quando, na realidade, essa interferncia no existe. A recriao de I a partir do cdigo transformado produz o grafo
de interferncia preciso, sem aresta entre LRab e LRc, e permite que o alocador agrupe
LRab e LRc.
Como o agrupamento de duas faixas vivas pode impedir o agrupamento subsequente
de outras faixas vivas, a ordem de agrupamento importa. Em princpio, o compilador
deve agrupar primeiro as cpias executadas com mais frequncia. Assim, o alocador
pode agrupar cpias em ordem pela profundidade de aninhamento de lao do bloco
onde as cpias so encontradas. Para implementar isto, o alocador pode considerar os
blocos bsicos em ordem de mais profundamente aninhado at o menos profundamente
aninhado.
Na prtica, o custo de construir o grafo de interferncia para a primeira rodada de
agrupamento domina o custo global do alocador de colorao de grafo. As passagens
subsequentes pelo lao de criar-agrupar processam um grafo menor e, portanto, so
executadas mais rapidamente. Para reduzir o custo do agrupamento, o compilador pode
construir um subconjunto do grafo de interferncia reduzido um que s inclua faixas
vivas envolvidas em uma operao de cpia. Essa observao aplica a ideia da forma
SSA semipodada construo do grafo de interferncia incluir apenas nomes que
importam.
alocao, como o algoritmo local de baixo para cima, que so mais simples do que
os alocadores globais. Como a alocao e a atribuio podem ser realizadas em uma
nica passagem linear pelo cdigo, esta tcnica chamada alocao de varredura
linear.
Os alocadores de varredura linear evitam a criao do grafo de interferncia global
preciso e complexo a etapa mais dispendiosa nos alocadores globais de colorao
de grafo , bem como o lao O(N2) para escolher candidatos ao derramamento.
Assim, eles usam muito menos tempo de compilao do que os alocadores de
colorao de grafo globais. Em algumas aplicaes, como em compiladores
just-in-time (JITs), a escolha entre velocidade de alocao e aumento no cdigo de
derramamento torna esses alocadores de varredura linear atraentes.
Este tipo de alocao tem toda a sutileza vista nos alocadores globais. Por exemplo,
o uso do algoritmo local de cima para baixo em um alocador de varredura linear
derrama uma faixa viva em qualquer lugar em que ocorre, enquanto o uso do
algoritmo local de baixo para cima a derrama exatamente nos pontos onde o
derramamento necessrio. A noo imprecisa de interferncia significa que esses
alocadores precisam usar outros mecanismos para agrupar cpias.
Valores de multirregistrador
Considere uma mquina alvo que exige um par alinhado de registradores adjacentes
para cada valor de ponto flutuante de preciso dupla e um programa com duas faixas
vivas de preciso simples, LRa e LRb, e uma faixa viva de preciso dupla, LRc.
Com interferncias (LRa, LRc) e (LRb, LRc), as tcnicas descritas na Seo13.4.3
produzem o grafo mostrado na margem. Trs registradores, r0, r1 e r2, com um nico
par alinhado, (r0, r1), devem ser suficientes para esse grafo. LRa e LRb podem compartilhar r2, deixando o par (r0, r1) para LRc. Infelizmente, este grafo no representa
adequadamente as restries reais sobre a alocao.
Dado k=3, o alocador de colorao de baixo para cima atribui cores em ordem
arbitrria, pois nenhum n tem grauk. Se o alocador considera LRc, primeiro ter
sucesso, pois (r0, r1) est livre para manter LRc. Se LRa ou LRb for colorido primeiro,
ele pode usar r0 ou r1, criando uma situao em que o par de registradores alinhado
no est disponvel para LRc.
Para forar a ordem desejada, o alocador pode inserir duas arestas para representar
uma interferncia com um valor que precisa de dois registradores, produzindo o grafo
esquerda. Com esse grafo e k=3, o alocador de baixo para cima precisa remover um
dentre LRa ou LRb primeiro, pois LRc tem grau 4, garantindo que dois registradores
estejam disponveis para LRc.
As arestas dobradas produzem uma alocao correta, pois correspondem ao grau dos
ns que interferem com LRc com os requisitos de recurso reais. Isto no garante que
um par adjacente est disponvel para LRc. A atribuio fraca pode deixar LRc sem
par. Por exemplo, na ordem de colorao LRa, LRc, LRb, o alocador poderia atribuir
LRa a r1. O construtor de compiladores poderia condicionar a ordem de colorao em
favor de LRc escolhendo valores de registrador nico primeiro entre os ns irrestritos (a
primeira clusula no algoritmo de reduo de grafo). Outra tcnica que o alocador pode
usar realizar a recolorao limitada entre os vizinhos de LRc se um par apropriado
no estiver disponvel quando ele tentar atribuir cores.
REVISO DA SEO
Os alocadores de registradores globais consideram as faixas vivas maiores e mais
complexas que surgem dos grafos de fluxo de controle que contm mltiplos blocos.
Por consequncia, a alocao global mais difcil que a local. A maioria dos alocadores
globais opera por analogia colorao de grafo. O alocador constri um grafo que representa as interferncias entre faixas vivas, e depois tenta encontrar uma k-colorao
para esse grafo, onde k o nmero de registradores disponveis para o alocador.
Os alocadores de colorao de grafo variam na preciso de sua definio de uma
faixa viva, na preciso com a qual medem a interferncia, no algoritmo usado para
encontrar uma k-colorao e na tcnica que usam para selecionar valores para
derramamento ou diviso. Em geral, esses alocadores produzem alocaes razoveis
com quantidades aceitveis de cdigo de derramamento. As principais oportunidades
para melhoria parecem estar nas reas de escolha de derramamento, posicionamento
de derramamento e diviso de faixa viva.
QUESTES DE REVISO
1. O alocador de registradores original, de cima para baixo, controlado por prioridade,
usava uma noo de interferncia diferente daquela apresentada na Seo13.4.3.
Ele acrescentava uma aresta (LRi, LRj) para o grafo se LRi e LRj estivessem vivos no
mesmo bloco bsico. Que impacto esta definio teria sobre o alocador? E sobre o
agrupamento de registradores?
2. O alocador global de baixo para cima escolhe valores para derramar encontrando
o valor que minimiza alguma razo, como custo de derramamento . Quando o
grau
algoritmo executado, s vezes precisa escolher vrias faixas vivas para derramar
antes de tornar qualquer outra faixa viva irrestrita. Explique como esta situao
pode acontecer. Voc pode imaginar uma mtrica de derramamento que evite
este problema?
Agrupamento conservador
Quando o alocador agrupa duas faixas vivas, LR i e LRj, a nova faixa viva, LR ij,
pode ser mais restrita do que LRi ou LRj. Se LRi e LRj tiverem vizinhos distintos,
LRij>mx(LRi, LRj). Se LRij<k, a criao de LRij estritamente benfica.
Porm, se LRi<k e LRj<k, mas LRijk, o agrupamento de LRi e LRj pode tornar
I mais difcil de colorir sem derramamento. Para evitar este problema, o construtor de
compiladores pode usar uma forma limitada de agrupamento, chamada agrupamento
conservador. Neste esquema, o alocador s combina LRi e LRj se LRij tiver menos
de k vizinhos de grau significativo ou seja, vizinhos em I que, por si ss, tm k
ou mais vizinhos. Esta restrio garante que o agrupamento de LRi e LRj no torna I
mais difcil de colorir.
Se o alocador usar o agrupamento conservador, outra melhoria possvel. Quando
o alocador alcana um ponto em que cada faixa viva restante restrita, o algoritmo
bsico seleciona um candidato a derramamento. Uma tcnica alternativa reaplicar o
agrupamento neste ponto. As faixas vivas que no foram agrupadas por causa do grau da
faixa viva resultante podem muito bem ser agrupadas no grafo reduzido. O agrupamento
neste ponto pode reduzir o grau dos ns que interferem tanto com a origem quanto com
o destino da cpia. Este estilo de agrupamento iterativo pode remover cpias adicionais
e reduzir os graus dos ns, e, ainda, criar um ou mais ns irrestritos e permitir que a
colorao prossiga. Se o agrupamento iterativo no criar quaisquer ns irrestritos, o
derramamento prossegue como antes.
A colorao induzida outra tcnica para agrupar cpias sem tornar o grafo mais
difcil de colorir. Nesta tcnica, o alocador tenta atribuir a mesma cor a faixas vivas
que so conectadas por uma cpia. Na escolha de uma cor para LRi, ele primeiro experimenta cores que foram atribudas a faixas vivas conectadas a LRj por uma operao
de cpia. Se puder atribuir a mesma cor a ambas, o alocador elimina a cpia. Com
uma implementao cuidadosa, isto acrescenta pouco ou nenhum custo ao processo
de seleo de cor.
Agrupamento conservador
Forma de agrupamento que s combina LRi e LRj se LRij
receber uma cor.
Rematerializao
Alguns valores custam menos para recalcular do que para derramar. Por exemplo,
constantes inteiras pequenas devem ser recriadas com um load imediato, ao invs de
recuperadas da memria com um load. O alocador pode reconhecer esses valores e
rematerializ-los ao invs de derram-los.
A modificao de um alocador de colorao de grafo de baixo para cima para
realizar a rematerializao requer vrias pequenas mudanas. O alocador precisa
identificar e marcar nomes SSA que podem ser rematerializados. Por exemplo,
qualquer operao cujos argumentos esto sempre disponveis uma candidata.
Ele pode propagar essas marcaes de rematerializao pelo cdigo usando o
algoritmo de propagao de constante descrito no Captulo 9. Na formao de faixas
vivas, o alocador deve apenas combinar nomes SSA que possuem marcaes de
rematerializao idnticas.
O construtor de compiladores dever fazer com que a estimativa de custo de derramamento trate das marcaes de rematerializao corretamente, de modo que esses
valores tenham estimativas de custo de derramamento precisas. O processo de insero
de cdigo de derramamento tambm deve examinar as marcaes e gerar os derramamentos leves apropriados para valores rematerializveis. Finalmente, o alocador deve
usar o agrupamento conservador para evitar a combinao prematura de faixas vivas
com marcaes de rematerializao distintas.
Valores ambguos
Em cdigo que faz uso intenso de valores ambguos, sejam derivados de ponteiros de
linguagem fonte, referncias de array ou referncias de objeto cuja classe no pode ser
determinada em tempo de compilao, a capacidade ou incapacidade do alocador de
manter esses valores em registradores uma sria questo de desempenho. Para melhorar a alocao de valores ambguos, diversos sistemas incluram transformaes que
reescrevem o cdigo para manter valores no ambguos em variveis locais escalares,
mesmo quando seu lar natural seja um elemento de array ou uma estrutura baseada
em ponteiro. A substituio escalar usa a anlise de subscrito de array para identificar
o reso de valores de elemento de array e para introduzir variveis temporrias escalares que mantm valores reutilizados. A promoo para registrador usa a anlise de
fluxo de dados de valores de ponteiro para determinar quando um valor baseado em
ponteiro pode ser seguramente mantido em um registrador atravs de aninhamento de
lao e para reescrever o cdigo de modo que o valor seja mantido em uma varivel
temporria recm-introduzida. Essas duas transformaes codificam os resultados da
anlise na forma do cdigo, tornando bvio para o alocador de registradores que esses
valores podem ser mantidos em registradores. Essas transformaes podem aumentar a
demanda por registradores. De fato, a promoo de muitos valores pode produzir cdigo
de derramamento cujo custo excede o das operaes de memria que a transformao
tem por finalidade evitar. O ideal que essas tcnicas sejam integradas ao alocador
em que estimativas realsticas da demanda por registradores possam ser usadas para
determinar quantos valores promover.
Grafo cordal
Grafo em que cada ciclo de mais de trs ns tem uma
corda uma aresta que junta dois ns que no so
adjacentes no ciclo.
NOTAS DO CAPTULO
A alocao de registradores data dos compiladores mais antigos. Backus informa que
Best inventou o algoritmo local de baixo para cima em meados da dcada de 1950,
durante o desenvolvimento do compilador FORTRAN original [26,27]. O algoritmo
de Best foi redescoberto e reutilizado em muitos contextos no decorrer dos anos
[36,117,181,246]. Sua encarnao mais conhecida como o algoritmo de substituio de pgina off-line de Belady [36]. As complicaes que surgem de se ter uma
combinao de valores limpos e sujos so descritas por Horwitz [196] e por Kennedy
[214]. Liberatore e outros sugerem o derramamento de valores limpos antes dos sujos
como um compromisso prtico [246]. O exemplo nas pginas 570 e 571 foi sugerido
por Ken Kennedy.
A conexo entre os problemas de colorao de grafo e alocao de armazenamento
foi sugerida por Lavrov [242] muitos anos antes; o projeto Alpha usava a colorao
para compactar dados na memria [140,141]. O primeiro alocador de colorao de
grafo completo a aparecer na literatura foi o construdo por Chaitin e seus colegas para
o compilador PL.8da IBM [73,74,75]. Schwartz descreve os primeiros algoritmos
de Ershov e de Cocke [310], que focam a reduo do nmero de cores e ignoram o
derramamento.
A colorao de grafo de cima para baixo comea com Chow [81,82,83]. Sua implementao funcionou a partir de um modelo de memria-para-memria, usando um
grafo de interferncia impreciso e realizando diviso de faixa viva conforme descrita
na Seo13.4.4. Ela usa um passo de otimizao separado para agrupar cpias [81].
O algoritmo de Chow foi usado em diversos compiladores proeminentes. Larus construiu um alocador de cima para baixo, baseado em prioridade, para o SPUR LISP,
que usava um grafo de interferncia preciso e operava a partir de um modelo de registrador-para-registrador [241]. A alocao de cima para baixo na Seo13.4.4 segue
aproximadamente o plano de Larus.
O alocador de baixo para cima na Seo13.4.5 segue o plano de Chaitin, com as
modificaes de Briggs [51,52,56]. As contribuies de Chaitin incluem a definio
fundamental da interferncia e os algoritmos para a construo do grafo de interferncia,
agrupamento e tratamento de derramamentos. Briggs apresentou um algoritmo baseado
em SSA para a construo de faixa viva, uma heurstica de colorao melhorada
e vrias tcnicas para a diviso de faixa viva [51]. Outras melhorias significativas
na colorao de baixo para cima incluram mtodos melhores para derramamento
[37,38], rematerializao de valores simples [55], mtodos de agrupamento mais fortes
[158,280] e para diviso de faixa viva [98,106,235]. Gupta, Soffa e Steele sugeriram
o encolhimento do grafo com cliques separadores [175], enquanto Harvey props sua
diviso por classes de registradores [101].
Chaitin, Nickerson e Briggs discutem a incluso de arestas ao grafo de interferncia para
modelar restries especficas na atribuio [54,75,275]. Smith e outros apresentam
um tratamento claro de como lidar com classes de registradores [319]. A substituio
escalar [67,70] e a promoo de registradores [250,253,306] reescrevem o cdigo
para aumentar o conjunto de valores que o alocador pode manter em registradores.
A observao de que nomes SSA formam um grafo cordal foi feita, independentemente, por vrios autores [58,177,283]. Tanto Hack quanto Bouchez se basearam
na observao original com tratamentos em profundidade da alocao global baseada
em SSA [47,176].
EXERCCIOS
Seo 13.3
1. Considere o seguinte bloco bsico ILOC. Suponha que rarp e ri estejam vivos
na entrada do bloco.
a. Mostre o resultado do uso do algoritmo local de cima para baixo sobre ele
para alocar registradores. Considere uma mquina alvo com quatro registradores.
b. Mostre o resultado do uso do algoritmo local de baixo para cima sobre ele
para alocar registradores. Considere uma mquina alvo com quatro registradores.
2. O alocador local de cima para baixo um tanto quanto inocente em seu tratamento de valores. Ele aloca um valor a um registrador pelo bloco bsico inteiro.
a. Uma verso melhorada poderia calcular faixas vivas dentro do bloco e alocar
valores aos registradores para suas faixas vivas. Que modificaes seriam
necessrias para realizar isto?
b. Outra melhoria poderia ser dividir a faixa viva quando ela no puder ser
acomodada em um nico registrador. Esboce as estruturas de dados e as
modificaes algortmicas que seriam necessrias para (1) dividir uma faixa
viva em torno de uma instruo (ou faixa de instrues) onde um registrador
no est disponvel, e (2) repriorizar as partes restantes da faixa viva.
c. Com essas melhorias, a tcnica de contagem de frequncia deveria gerar
alocaes melhores. Como voc espera que seus resultados se comparem
com o uso do algoritmo local de baixo para cima? Justifique sua resposta.
3. Considere o grafo de fluxo de controle a seguir:
Suponha que read retorne um valor do meio externo e que write transmita
um valor para o meio externo.
a. Calcule os conjuntos LIVEIN e LIVEOUT para cada bloco.
b. Aplique o algoritmo local de baixo para cima a cada bloco, A, B e C. Suponha que trs registradores estejam disponveis para a computao. Se o bloco
b definir um nome n e n LIVEOUT(b), o alocador precisa armazenar n
de volta para a memria de modo que seu valor esteja disponvel em blocos
subsequentes. De modo semelhante, se o bloco b usar o nome n antes de
qualquer definio local de n, dever carregar o valor de n da memria.
Mostre o cdigo resultante, incluindo todos os loads e stores.
alocador poderia ser capaz de recolorir um deles com a cor do outro, liberando
uma cor para LRi.
a. Esboce um algoritmo que descubra se existe uma recolorao vlida e
produtiva para LRi.
b. Qual o impacto da sua tcnica sobre a complexidade assinttica do
alocador de registradores?
c. Se o alocador no puder recolorir LRk para a mesma cor de LRj porque um
dos vizinhos de LRk tem a mesma cor de LRj, o alocador deve considerar a
recolorao recursiva dos vizinhos de LRk? Explique seu raciocnio.
7. A descrio do alocador global de baixo para cima sugere a insero de cdigo
de derramamento para cada definio e uso na faixa viva derramada. O alocador
global de cima para baixo primeiro quebra a faixa viva em partes com tamanho
de bloco, depois combina essas partes quando o resultado irrestrito e, por fim,
lhes atribui uma cor.
a. Se determinado bloco tiver um ou mais registradores livres, o derramamento
de uma faixa viva vrias vezes nesse bloco um desperdcio. Sugira uma
melhoria para o mecanismo de derramamento no alocador global de baixo
para cima que evite este problema.
b. Se um determinado bloco tiver muitas faixas vivas sobrepostas, ento a
diviso de uma faixa viva derramada faz pouco para resolver o problema nesse bloco. Sugira um mecanismo (diferente de alocao local) para melhorar o
comportamento do alocador global de cima para baixo dentro de blocos com
alta demanda por registradores.
8. Considere o derramamento no alocador global de baixo para cima. Quando o alocador tiver que derramar, escolhe o valor que minimiza a razo
custo de derramamento .
grau
Apndice
ILOC
VISO GERAL DO CAPTULO
ILOC o cdigo assembly para uma mquina abstrata simples. Foi projetado originalmente como uma IR de baixo nvel, linear, para uso em um compilador otimizador.
Ns o utilizamos no decorrer do livro como uma IR de exemplo, e tambm como uma
linguagem-alvo simplificada nos captulos que discutem a gerao de cdigo. Este
apndice serve como uma referncia ao ILOC.
Palavras-chave: Representao intermediria, Cdigo de trs endereos, ILOC
A.1INTRODUO
ILOC o cdigo assembly linear para uma mquina RISC abstrata simples. O ILOC
usado neste livro uma verso simplificada da representao intermediria que foi
usada no Massively Scalar Compiler Project da Rice University. Por exemplo, o ILOC,
conforme apresentado aqui, considera um tipo de dado genrico, um inteiro sem um
tamanho especfico; no compilador, a IR admitia uma grande variedade de tipos de
dados.
A mquina abstrata ILOC tem nmero ilimitado de registradores. Ela tem operaes de
trs endereos, de registrador-para-registrador, operaes load e store, comparaes e
desvios. Ela admite apenas alguns modos de endereamento simples direto, endereo+deslocamento (offset), endereo+imediato e imediato. Os operandos de origem
so lidos no incio do ciclo, quando a operao emitida. Os operandos de resultado
so definidos ao final do ciclo em que a operao termina.
Alm do seu conjunto de instrues, os detalhes da mquina no so especificados. A
maior parte dos exemplos considera uma mquina simples, com nica unidade funcional
que executa operaes ILOC em sua ordem de aparecimento. Quando outros modelos
so usados, discutimos sobre eles explicitamente.
Um programa ILOC consiste em uma lista sequencial de instrues. Cada instruo
pode ser precedida por um rtulo (label). Um rtulo apenas uma string de texto; ela
separada da instruo por um sinal de dois pontos. Por conveno, limitamo-nos a
rtulos no formato [az] ([az] | [09] | )*. Se alguma instruo precisar de mais de
um label, inserimos uma instruo que s contm um nop antes dela, e colocamos o
label extra no nop. Para definir um programa ILOC mais formalmente,
599
Cada instruo contm uma ou mais operaes. Uma instruo de operao nica
escrita em uma linha isolada, enquanto uma instruo com mltiplas operaes pode
se espalhar por vrias linhas. Para agrupar operaes em uma nica instruo, elas so
delimitadas por colchetes e separadas por ponto e vrgula. Mais formalmente,
Uma operao ILOC corresponde a uma instruo em nvel de mquina que poderia
ser emitida para uma nica unidade funcional em um nico ciclo. Ela tem um cdigo
de operao (opcode), uma sequncia de operandos de origem separados por vrgulas
e uma sequncia de operandos de destino tambm separados por vrgulas. As origens
so separadas dos destinos pelo smbolo , pronunciado como para.
O no terminal Opcode pode ser qualquer operao ILOC, exceto cbr, jump e
jumpI. Infelizmente, como em uma linguagem assembly real, o relacionamento entre
um cdigo de operao e o formato de seus operandos no sistemtico. O modo
mais fcil de especificar o formato dos operandos para cada cdigo de operao em
um formato tabular. As tabelas que ocorrem mais adiante neste apndice mostram o
nmero de operandos e seus tipos para cada cdigo de operao ILOC usado no livro.
Operandos podem ser um de trs tipos: register, num e label. O tipo de cada
operando determinado pelo cdigo de operao e pela posio do operando na
operao. Nos exemplos, usamos tanto nomes numricos (r 10) quanto simblicos
(ri) para os registradores. Nmeros so inteiros simples, com sinal, se necessrio.
Sempre iniciamos um rtulo com um l (de label) para tornar seu tipo bvio. Esta
uma conveno, e no uma regra. Os simuladores e ferramentas ILOC devem tratar
qualquer sequncia no formato descrito acima como um potencial rtulo.
A maioria das operaes tem um nico operando de destino; algumas das operaes
store tm vrios operandos de destino, assim como os desvios. Por exemplo, storeAI tem um nico operando de origem e dois operandos de destino. A origem precisa
ser um registrador, e os destinos, um registrador e uma constante imediata. Assim, a
operao ILOC
A primeira operao, cbr, implementa um desvio condicional. As outras duas operaes so desvios incondicionais, chamados saltos.
A.3.1Aritmtica
Para expressar aritmtica, ILOC tem operaes com trs endereos, de registrador-para-registrador.
Opcode
Fontes
Destinos
Significado
add
sub
mult
div
addI
subI
rsubI
multI
divI
rdivI
r1,
r1,
r1,
r1,
r1,
r1,
r1,
r1,
r1,
r1,
r3
r3
r3
r3
r3
r3
r3
r3
r3
r3
r1+r2 r3
r1 - r2 r3
r1 x r2 r3
r1 r2 r3
r1+c2 r3
r1 - c2 r3
c2 - r1 r3
r1 x c2 r3
r1 c2 r3
c2 r1 r3
r2
r2
r2
r2
c2
c2
c2
c2
c2
c2
Origens
Destinos
Significado
lshift
lshiftI
rshift
rshiftI
r1,
r1,
r1,
r1,
r3
r3
r3
r3
r1
r1
r1
r1
r2
c2
r2
c2
<<
<<
>>
>>
r2 r3
c2 r3
r2 r3
c2 r3
Origens
Destinos
Significado
load
r1
r2
loadAI
r1, c2
r3
loadAO
r1, r2
r3
cload
r1
r2
cloadAI
r1, c2
r3
cloadAO
r1, r2
r3
MEMRIA(r1)
r2
MEMRIA(r1+c2) r3
MEMRIA(r1+r2) r3
load
de caractere
loadAI de
caractere
loadAO de
caractere
Origens
c1
Destinos
r2
Significado
c1 r2
Uma IR completa, tipo ILOC, deve ter um load imediato para cada tipo distinto de
valor que admite.
As operaes store combinam com as operaes load. ILOC admite tanto stores
numricos quanto stores de caractere em sua forma de registrador simples, nas formas
endereo-imediato e endereo-deslocamento.
Opcode
Origens
Destinos
Significado
store
r1
r2
storeAI
r1
r2, c3
storeAO
r1
r2, r3
cstore
r1
r2
cstoreAI
r1
r2, c3
cstoreAO
r1
r2, r3
r1 MEMRIA(r2)
r1 MEMRIA(r2+c3)
r1 MEMRIA(r2+r3)
store de
caractere
storeAI de
caractere
storeAO de
caractere
Origens
Destinos
Significado
i2i
r1
r2
c2c
r1
r2
c2i
r1
r2
i2c
r1
r2
r1 r2 para
inteiros
r1 r2 para
caracteres
converte caractere em inteiro
converte inteiro
em caractere
Origens
Destinos
Significado
cmp_LT
r1, r2
r3
true r3
false r3
cmp_LE
r1, r2
r3
true r3
false r3
cmp_EQ
r1, r2
r3
true r3
false r3
cmp_GE
r1, r2
r3
true r3
false r3
cmp_GT
r1, r2
r3
true r3
false r3
cmp_NE
r1, r2
r3
true r3
false r3
cbr
r1
l2, l3
l2 PC
l3 PC
if r1<r2
caso contrrio
if r1r2
caso contrrio
if r1=r2
caso contrrio
if r1 r2
caso contrrio
if r1>r2
caso contrrio
if r1 r2
caso contrrio
if r1=true
caso contrrio
Opcode
Origens
Destinos
Significado
comp
cbr_LT
r1, r2
cc1
cc3
l2, l3
define cc3
l2 PC
l3 PC
cbr_LE
cc1
l2, l3
l2 PC
l3 PC
cbr_EQ
cc1
l2, l3
l2 PC
l3 PC
cbr_GE
cc1
l2, l3
l2 PC
l3 PC
cbr_GT
cc1
l2, l3
l2 PC
l3 PC
cbr_NE
cc1
l2, l3
l2 PC
l3 PC
if cc3=LT
caso contrrio
if cc3=LE
caso contrrio
if cc3=EQ
caso contrrio
if cc3=GE
caso contrrio
if cc3=GT
caso contrrio
if cc3=NE
caso contrrio
Aqui, o operador de comparao, comp, usa dois valores e define o cdigo de condio
de modo apropriado. Sempre designamos o destino de comp como um registrador de
cdigo de condio escrevendo-o como cci. O desvio condicional correspondente
tem seis variantes, uma para cada resultado de comparao.
A.4.2Saltos
ILOC inclui duas formas da operao de salto. A forma usada em quase todos os exemplos um salto imediato que transfere o controle para um rtulo literal. A segunda,
uma operao de salto-para-registrador, usa um nico operando de registrador. Ela
interpreta o contedo do registrador como um endereo de runtime e transfere o controle para esse endereo.
Opcode
Origens
Destinos
Significado
jumpI
jump
l1
r1
l1 PC
r1 PC
Para reduzir a perda de informaes do salto para registrador, ILOC inclui uma pseudo-operao que permite que o compilador registre o conjunto de rtulos possveis
para um salto para registrador. A operao tbl tem dois argumentos, um registrador
e um label imediato.
Opcode
tbl
Origens
r1, l2
Destinos
Significado
r1 poderia
manter l2
Uma operao tbl pode ocorrer somente aps um jump. O compilador interpreta
um conjunto de um ou mais tbls como nomeao de todos os labels possveis para o
registrador. Assim, a sequncia de cdigo a seguir declara que o salto visa um dentre
os rtulos L01, L03, L05 ou L08:
para a funo- rm (ri, rj, rk). Devido natureza da forma SSA, a operao
phi pode usar um nmero qualquer de origens. Ela sempre define um nico destino.
Resumo dos cdigos de operao ILOC
Opcode
Origens
Destinos
Meaning
nop
nenhum
nenhum
add
sub
mult
div
addI
subI
rsubI
multI
divI
rdivI
lshift
lshiftI
rshift
rshiftI
and
andI
or
orI
xor
r1,
r1,
r1,
r1,
r1,
r1,
r1,
r1,
r1,
r1,
r1,
r1,
r1,
r1,
r1,
r1,
r1,
r1,
r1,
r3
r3
r3
r3
r3
r3
r3
r3
r3
r3
r3
r3
r3
r3
r3
r3
r3
r3
r3
r2
r2
r2
r2
c2
c2
c2
c2
c2
c2
r2
c2
r2
c2
r2
c2
r2
c2
r2
Opcode
xorI
loadI
load
loadAI
loadAO
cload
cloadAI
cloadAO
store
storeAI
storeAO
cstore
cstoreAI
cstoreAO
i2i
c2c
c2i
i2c
Origens
r1, c2
c1
r1
r1, c2
r1, r2
r1
r1, c2
r1, r2
r1
r1
r1
r1
r1
r1
r1
r1
r1
r1
Destinos
r3
r2
r2
r3
r3
r2
r3
r3
r2
r2, c3
r2, r3
r2
r2, c3
r2, r3
r2
r2
r2
r2
Meaning
r1 xor c2 r3
c1 r2
MEMRIA(r1) r2
MEMRIA(r1+c2) r3
MEMRIA(r1+r2) r3
load de caractere
loadAI de caractere
loadAO de caractere
r1 MEMRIA(r2)
r1 MEMRIA(r2+c3)
r1 MEMRIA(r2+r3)
store de caractere
storeAI de caractere
storeAO de caractere
r1 r2 para inteiros
r1 r2 para caracteres
converte caractere em inteiro
converte inteiro em caractere
Origens
Destinos
Significado
jump
jumpI
cbr
r1
r1
l1
l2, l3
tbl
cmp_LT
r1, l2
r1, r2
r3
cmp_LE
r1, r2
r3
cmp_EQ
r1, r2
r3
cmp_GE
r1, r2
r3
cmp_GT
r1, r2
r3
cmp_NE
r1, r2
r3
comp
cbr_LT
r1, r2
cc1
cc3
l2, l3
cbr_LE
cc1
l2, l3
cbr_EQ
cc1
l2, l3
cbr_GE
cc1
l2, l3
cbr_GT
cc1
l2, l3
cbr_NE
cc1
l2, l3
r1 PC
l1 PC
if r1=true
l2 PC
caso contrrio
l3 PC
r1 poderia manter l2
if r1<r2
true r3
caso contrrio
false r3
true r3
if r1r2
caso contrrio
false r3
if r1=r2
true r3
caso contrrio
false r3
true r3
if r1 r2
caso contrrio
false r3
if r1>r2
true r3
caso contrrio
false r3
true r3
if r1 r2
caso contrrio
false r3
define cc3
if cc3=LT
l2 PC
caso contrrio
l3 PC
if cc3=LE
l2 PC
caso contrrio
l3 PC
if cc3=EQ
l2 PC
caso contrrio
l3 PC
if cc3=GE
l2 PC
caso contrrio
l3 PC
if cc3=GT
l2 PC
caso contrrio
l3 PC
if cc3=NE
l2 PC
caso contrrio
l3 PC
Apndice
Estruturas de dados
VISO GERAL DO CAPTULO
Compiladores so executados tantas vezes que o construtor de compiladores precisa
prestar ateno eficincia de cada passo do compilador. As complexidades assinttica e esperada importam. Este apndice apresenta material de base sobre
algoritmos e estruturas de dados usadas para resolver problemas em diferentes fases
do compilador.
Palavras-chave: Representao de conjunto, Representaes intermedirias, Tabelas
hash, Tabelas de smbolos com escopo lxico
B.1INTRODUO
A criao de um compilador bem-sucedido exige ateno a muitos detalhes. Este
apndice explora algumas das questes algortmicas que surgem no projeto e implementao de um compilador. Na maioria dos casos, esses detalhes desviariam a
discusso relevante no corpo do texto. Eles foram reunidos neste apndice, onde podem
ser considerados conforme a necessidade.
Este apndice concentra-se na infraestrutura para dar suporte compilao. Muitas
questes de engenharia surgem no projeto e implementao desta infraestrutura; a
forma como o construtor de compiladores as resolve tem um grande impacto sobre
a velocidade do compilador resultante e a facilidade de extenso e manuteno do
compilador. Como exemplo dos problemas que surgem, o compilador no pode saber
o tamanho de suas entradas at que as leia; assim, o front end precisa ser projetado para
expandir o tamanho de suas estruturas de dados de modo controlado, a fim de acomodar
arquivos de entrada grandes. Como um corolrio, porm, o compilador deve saber os
tamanhos aproximados necessrios para a maioria de suas estruturas de dados internas
quando invocar os passos que vm aps o front end. Tendo gerado um programa IR
com 10.000 nomes, o compilador no deve iniciar seu segundo passo com uma tabela
de smbolos com tamanho para 1.024 nomes. Qualquer arquivo que contmIR deve
comear com uma especificao dos tamanhos aproximados das principais estruturas
de dados.
De modo semelhante, os ltimos passos de um compilador podem considerar que o
programa IR apresentado a eles foi gerado pelo compilador. Embora eles devam realizar
um trabalho completo de deteco de erro, o implementador no precisa gastar tanto
tempo explicando erros e tentando corrigi-los, como poderia se esperar no front end.
Uma estratgia comum construir um passo de validao que realiza uma verificao
completa sobre o programa IR e pode ser inserido para fins de depurao, e para contar
com deteco e informe de erro menos rduos quando o compilador no estiver sendo
depurado. Pelo processo, porm, os construtores de compilador devem se lembrar de
que so as pessoas mais provveis para examinar o cdigo entre os passos. O esforo
gasto para tornar as formas externas da IR mais legveis normalmente recompensa as
muitas pessoas que investiram tempo e esforo nela.
609
610 APNDICE
Estruturas de dados
612 APNDICE
Estruturas de dados
para elementos individuais, como em alguns sistemas de coleta de lixo ou em um sistema baseado em arena, clear toma um tempo constante.
Uma variante desta ideia faz sentido quando o universo desconhecido e os conjuntos
podem crescer e se tornar razoavelmente grandes, como na construo do grafo
de interferncia (ver Captulo13). Fazer cada n manter um nmero fixo (maior
que1) de elementos de conjunto reduz significativamente o overhead de espao e
de tempo. Com k elementos por n, a criao de um conjunto de n elementos exige
n
n
k alocaes e k + 1 ponteiros, enquanto um conjunto com ns de nico elemento
A representao de vetor de bits sempre aloca espao suficiente para representar todos
os elementos de U; assim, essa representao s pode ser usada em uma aplicao onde
U conhecido uma aplicao off-line.
A tabela na Figura B.1 lista as complexidades assintticas das operaes comuns de
conjunto com esta representao. Embora muitas das operaes sejam O(|U|), elas ainda
podem ser eficientes se U for pequeno. Uma nica palavra mantm muitos elementos;
a representao ganha uma melhoria de fator constante sobre as representaes que
precisam de uma palavra por elemento. Assim, por exemplo, com um tamanho de
palavra de 32 bits, qualquer universo de 32 ou menos elementos tem uma representao
de uma nica palavra.
A compactao da representao transferida para a velocidade das operaes.
Com conjuntos de nica palavra, muitas das operaes de conjunto tornam-se instrues de mquina nicas; por exemplo, union torna-se uma operao lgica OR,
Observe que a representao de conjunto esparso exige espao suficiente para representar todos de U. Assim, ele s pode ser usado em situaes off-line, em que o
compilador conhece o tamanho de U.
614 APNDICE
Estruturas de dados
Representao de rvores
A representao natural para as rvores, na maioria das linguagens, como uma coleo
de ns conectados por ponteiros. Uma implementao tpica aloca os ns por demanda,
medida que o compilador constri a rvore. A rvore pode incluir ns de vrios
tamanhos por exemplo, variando o nmero de filhos do n e alguns dos campos
de dados. Alternativamente, a rvore poderia ser construda com um nico tipo de n,
alocado para se ajustar ao maior n possvel.
Outra maneira de representar a mesma rvore com um array de estruturas de n. Nesta
representao, ponteiros so substitudos por ndices inteiros, e referncias baseadas
em ponteiro tornam-se referncias padres de array e estrutura. Essa implementao
fora um n com tamanho nico para tudo, mas, fora isso, semelhante quela baseada
em ponteiro.
Existem muitas outras escolhas. Cada uma deve ser avaliada no contexto.
poderia ter um n na AST com cinco filhos, como aquele mostrado na Figura B.2.a. O
n rotulado com body representa a subrvore para o corpo do lao for.
Para algumas construes, nenhum nmero fixo de filhos funcionar. Para representar
uma chamada de procedimento, a AST deve alocar ns de modo personalizado, com
base no nmero de parmetros, ou usar um nico filho que mantenha uma lista de
parmetros. A primeira tcnica complica todo o cdigo que atravessa a AST; os ns de
tamanho varivel devem manter nmeros para indicar quantos filhos tm, e a travessia
deve conter cdigo para ler esses nmeros e modificar seu comportamento de acordo.
A segunda separa a implementao da AST de sua aderncia estrita origem, mas usa
uma construo bem compreendida, a lista, para representar aqueles lugares onde um
n com aridade fixa inapropriado.
Para simplificar a implementao de rvores, o construtor de compiladores pode levar
essa separao de forma e significado um passo adiante. Qualquer rvore arbitrria
pode ser mapeada em uma rvore binria rvore em que cada n tem exatamente
dois filhos. Nesse mapeamento, o ponteiro do filho da esquerda designado para o filho
mais esquerda, e o ponteiro do filho da direita designado para o prximo irmo no
nvel atual. A Figura B.2.b mostra o n for de cinco filhos mapeado para uma rvore
616 APNDICE
Estruturas de dados
binria. Como cada n binrio, essa rvore tem ponteiros nulos em cada n folha. E,
tambm, um ponteiro de irmo no n for; na verso da esquerda, esse ponteiro ocorre
no pai do n for. As partes c e d na figura mostram um exemplo mais complexo.
O uso de rvores binrias introduz ponteiros nulos adicionais nas rvores, como mostram os dois exemplos. Em retorno, isto simplifica a implementao de vrias maneiras.
A alocao de memria pode ser feita de modo mais simples, com um alocador baseado
em arena ou um alocador personalizado. O construtor de compiladores tambm pode
implementar a rvore como um array de estruturas. O cdigo que lida com a rvore
binria um pouco mais simples do que o exigido para uma rvore com ns de muitas
aridades diferentes.
No diagrama, os retngulos representam os ns, e os ovais as arestas. Essa representao torna fcil percorrer o grafo no sentido das arestas, mas no prov o acesso
aleatrio para qualquer um dos ns; para remediar isto, podemos acrescentar um array
de ponteiros de n, indexados pelos nomes de inteiros dos ns. Com esse pequeno
acrscimo (no mostrado), o grafo adequado para resolver problemas de fluxo de
dados para a frente (forward). Ele fornece um meio rpido de encontrar todos os
sucessores de um n.
Infelizmente, os compiladores normalmente precisam atravessar o CFG no sentido
contrrio das arestas. Isto ocorre, por exemplo, nos problemas de fluxo de dados para
trs (backward), em que o algoritmo precisa de uma operao rpida de predecessor.
Para adaptar essa estrutura de grafo para a travessia para trs, precisaramos incluir
outro ponteiro para cada n e criar um segundo conjunto de estruturas de aresta para
representar os predecessores de um n. Esta tcnica certamente funcionar, mas a estrutura de dados torna-se complicada de desenhar, implementar e depurar.
Uma alternativa, assim como com as rvores, representar o grafo como um par de
tabelas uma para os ns e outra para as arestas. A tabela de ns tem dois campos:
um para a primeira aresta para um sucessor e outro para a primeira aresta para um
predecessor. A tabela de arestas tem quatro campos: o primeiro par mantm a origem e o
destino da aresta que est sendo representada, e o outro par mantm o prximo sucessor
da origem e o prximo predecessor do destino. Usando este esquema, as tabelas para
o nosso CFG de exemplo aparecem na Figura B.4. Essa representao fornece acesso
rpido aos sucessores, predecessores e ns e arestas individuais por meio de seus nomes
(supondo que os nomes sejam representados por inteiros pequenos).
A representao tabular funciona bem para a travessia do grafo e para encontrar
predecessores e sucessores. Se a aplicao fizer muito uso de outras operaes de grafo,
representaes melhores podem ser encontradas. Por exemplo, as operaes dominantes
em um alocador de registradores de colorao de grafo so o teste de presena de uma
aresta no grafo de interferncia e a interao pelos vizinhos de um n. Para dar suporte
a essas operaes, a maioria das implementaes utiliza duas representaes de grafo
diferentes (ver Seo 13.4.3). Para responder a questes de pertinncia a aresta (i, j)
est no grafo? , essas implementaes utilizam uma matriz de bits. Como o grafo
de interferncia no orientado, uma matriz de bits diagonal inferior ser suficiente,
economizando cerca de metade do espao exigido para uma matriz de bits completa.
Para percorrer rapidamente pelos vizinhos de um n, um conjunto de vetores de
adjacncia utilizado.
618 APNDICE
Estruturas de dados
Lista de estruturas
Uma alternativa implementao de array usar uma lista de estruturas. Neste esquema, cada operao tem uma estrutura independente, alm de um ponteiro para a
prxima operao. Como as estruturas podem ser alocadas individualmente, a representao do programa expande-se facilmente para um tamanho arbitrrio. Como a ordem
imposta pelos ponteiros que vinculam operaes, operaes podem ser inseridas e
removidas com simples atribuies de ponteiros nenhum embaralhamento ou cpia
necessrio. Operaes de tamanho varivel, como a operao de chamada descrita
anteriormente, so tratadas usando estruturas variantes; de fato, operaes curtas
como loadI e jump tambm podem usar uma variante para economizar pequenas
quantidades de espao.
Naturalmente, o uso de estruturas alocadas individualmente aumenta o overhead da
alocao o array precisa de uma alocao inicial, enquanto o esquema de lista,
precisa de uma alocao por operao da IR. Os ponteiros de lista aumentam o espao
exigido. Como todos os passos do compilador que manipulam a IR precisam incluir
muitas referncias baseadas em ponteiro, o cdigo para esses passos pode ser mais lento
do que aquele que usa uma implementao de array simples, pois o cdigo baseado
em ponteiro normalmente mais difcil de analisar e otimizar do que o cdigo com
uso intenso de arrays. Finalmente, se o compilador escreve a IR para meios externos
entre os passos, ele deve atravessar a lista enquanto escreve e reconstru-la enquanto
a l. Isto atrasa a E/S.
620 APNDICE
Estruturas de dados
onde C a constante, key o inteiro que est sendo usado como chave para a tabela, e
TableSize, obviamente, o tamanho atual da tabela hash. Knuth sugere o seguinte valor
para C:
0,6180339887
5 1
2
O efeito da funo calcular C. key, tomar sua parte fracionria com a funo mod e
multiplicar o resultado pelo tamanho da tabela.
622 APNDICE
Estruturas de dados
N
). Desde que
S
h distribua nomes de modo uniforme e a razo entre nomes e baldes seja pequena, este
custo se aproxima do nosso objetivo: tempo O(1) para cada acesso.
A Figura B.5 mostra uma pequena tabela hash implementada com este esquema. Ela
considera que h(a)=h(d)=3 para criar uma coliso. Assim, a e d ocupam o mesmo
slot na tabela. A estrutura de lista os liga. Insert deve fazer incluses no incio da
lista, para ganhar eficincia.
O hashing aberto tem diversas vantagens. Como cria um novo n em uma das listas
interligadas para cada nome inserido, ele pode tratar um nmero arbitrariamente grande
de nomes sem esgotar o espao. Um nmero excessivo de entradas em um balde
no afeta o custo de acesso em outros baldes. Como a representao concreta para o
conjunto de baldes normalmente um array de ponteiros, o overhead para aumentar S
pequeno um ponteiro para cada balde acrescentado. (Isto torna menos dispendioso
manter
N
pequeno. O custo por nome constante.) A escolha de S como uma potncia
S
Uma tabela de hash aberto bem implementada fornece acesso eficiente com baixo
overhead em espao e tempo.
Para melhorar o comportamento da busca linear realizada em um nico balde, o
compilador pode reordenar a cadeia dinamicamente. Rivest e outros [302,317] descrevem duas estratgias efetivas: mover um n para cima na cadeia de uma posio
a cada pesquisa, ou mov-lo para a frente da lista a cada pesquisa. Esquemas mais
complexos para organizar cada balde tambm podem ser usados. Porm, o construtor
de compiladores deve avaliar a quantidade total de tempo perdido na travessia de um
balde antes de investir muito esforo neste problema.
624 APNDICE
Estruturas de dados
garantir que por fim retornar a h(n). Se S for um nmero primo, ento qualquer escolha de 0<g(n)<S gera uma srie de sondagens, p1, p2, ..., pS, com a propriedade
de que p1=pS=h(n) e pi h(n), i tal que 1<i<S. Ou seja, LookUp examinar
cada slot na tabela antes de retornar a h(n). Como a implementao pode precisar expandir a tabela, necessrio uma tabela de nmeros primos com tamanho apropriado.
Um pequeno conjunto de primos suficiente, devido aos limites realsticos tanto do
tamanho do programa como da memria disponvel ao compilador.
626 APNDICE
Estruturas de dados
onde representa uma chamada a InitializeScope, uma chamada a FinalizeScope, e nome, n uma chamada a Insert que acrescenta nome no nveln.
FIGURA B.9 Escopo lxico em uma tabela de hash aberto alocada em pilha.
628 APNDICE
Estruturas de dados
630 APNDICE
Estruturas de dados
NOTAS DO APNDICE
Muitos dos algoritmos em um compilador manipulam conjuntos, mapeamentos, tabelas
e grafos. As implementaes de base afetam diretamente o espao e o tempo que esses
algoritmos exigem e, por fim, a usabilidade do prprio compilador [57]. Os livros-texto
sobre algoritmos e estrutura de dados abordam muitas das questes que este apndice
rene [231,4,195,109,41].
Nossos compiladores de pesquisa tm usado quase todas as estruturas de dados descritas neste apndice. Vimos problemas de desempenho vindos do crescimento das
estruturas de dados em vrias reas.
j
Bibliografia
[1] P.S. Abrams, An APL Machine, tese de Ph.D., Stanford University, Stanford, CA, 1970. Technical
Report SLAC-R-114, Stanford Linear Accelerator Center, Stanford University, fevereiro de 1970.
[2] A.V. Aho, M. Ganapathi, S.W.K. Tjiang, Code generation using tree matching and dynamic
programming, ACM Trans. Program. Lang. Syst 11 (4) (1989), p. 491-516.
[3] A.V. Aho, J.E. Hopcroft, J.D. Ullman, On finding lowest common ancestors in trees, em: Conference Record of the Fifth Annual ACM Symposium on Theory of Computing (STOC), ACM, Nova
York, 1973, p. 253-265.
[4] A.V. Aho, J.E. Hopcroft, J.D. Ullman, The Design and Analysis of Computer Algorithms, Addison-Wesley, Reading, MA, 1974.
[5] A.V. Aho, S.C. Johnson, Optimal code generation for expression trees, J. ACM 23 (3) (1976),
p.488-501.
[6] A.V. Aho, S.C. Johnson, J.D. Ullman, Code generation for expressions with common subexpressions, em: Conference Record of the Third ACM Symposium on Principles of Programming
Languages, Atlanta, GA, ACM, Nova York, 1976, p. 19-31.
[7] A.V. Aho, R. Sethi, J.D. Ullman, Compilers: Principles, Techniques, and Tools, Addison-Wesley,
Reading, MA, 1986.
[8] A.V. Aho, J.D. Ullman, The Theory of Parsing, Translation, and Compiling, Prentice-Hall, Englewood Cliffs, NJ, 1973.
[9] P. Aigrain, S.L. Graham, R.R. Henry, M.K. McKusick, E. Pelegri-Llopart, Experience with a
Graham-Glanville style code generator, SIGPLAN Not 19 (6) (1984), p. 13-24. Proceedings of
the ACM SIGPLAN 84 Symposium on Compiler Construction.
[10] A. Aiken, A. Nicolau, Optimal loop parallelization, SIGPLAN Not 23 (7) (1988), p. 308-317.
Proceedings of the ACM SIGPLAN 88 Conference on Programming Language Design and
Implementation.
[11] F.E. Allen, Program optimization, em: M. Halpern, C. Shaw (Eds.), Annual Review in Automatic
Programming, vol. 5, Pergamon Press, Oxford, England, 1969, p. 239-307.
[12] F.E. Allen, Control flow analysis, SIGPLAN Not 5 (7) (1970), p. 1-19. Proceedings of a Symposium
on Compiler Optimization.
[13] F.E. Allen, A basis for program optimization, em: Proceedings of Information Processing 71,
North-Holland Publishing Company, Amsterdam, 1971, p. 385-390.
[14] F.E. Allen, The history of language processor technology in IBM, IBM J. Res. Dev 25 (5) (1981),
p. 535-548.
[15] F.E. Allen, Comunicao particular. Dr. Allen observou que Beatty descreveu a anlise viva em
um documento intitulado Optimization Methods for Highly Parallel, Multiregister Machines,
datado de setembro de 1968, abril de 2009.
[16] F.E. Allen, J. Cocke, A catalogue of optimizing transformations, em: R. Rustin (Ed.), em: Design
and Optimization of Compilers, Prentice-Hall, Englewood Cliffs, NJ, 1972, p. 1-30.
[17] F.E. Allen, J. Cocke, Graph-Theoretic Constructs for Program Flow Analysis, Technical Report
RC 3923 (17789), IBM Thomas J. Watson Research Center, Yorktown Heights, NY, 1972.
[18] F.E. Allen, J. Cocke, A program data flow analysis procedure, Commun. ACM 19 (3) (1976), p.
137-147.
[19] F.E. Allen, J. Cocke, K. Kennedy, Reduction of operator strength, em: S.S. Muchnick, N.D. Jones
(Eds.), em: Program Flow Analysis: Theory and Applications, Prentice-Hall, Englewood Cliffs,
NJ, 1981, p. 79-101.
[20] J.R. Allen, K. Kennedy, Optimizing Compilers for Modern Architectures, Morgan Kaufmann,
San Francisco, CA, 2001.
[21] B. Alpern, F.B. Schneider, Verifying temporal properties without temporal logic, ACM Trans.
Program. Lang. Syst 11 (1) (1989), p. 147-167.
[22] B. Alpern, M.N. Wegman, F.K. Zadeck, Detecting equality of variables in programs, em: Proceedings of the Fifteenth Annual ACM Symposium on Principles of Programming Languages, San
Diego, CA, ACM, Nova York, 1988, p. 1-11.
[23] S. Alstrup, D. Harel, P.W. Lauridsen, M. Thorup, Dominators in linear time, SIAM J. Comput 28
(6) (1999). p. 2117-2132.
[24] M.A. Auslander, M.E. Hopkins, An overview of the PL.8 compiler, SIGPLAN Not 17 (6) (1982),
p. 22-31. Proceedings of the ACM SIGPLAN 82 Symposium on Compiler Construction.
633
634 Bibliografia
[25] Ayers, R. Gottlieb, R. Schooler, Aggressive inlining, SIGPLAN Not. 32 (5) (1997), p. 134-145. Proceedings of the ACM SIGPLAN 97 Conference on Programming Language Design and Implementation.
[26] J.W. Backus, The history of FORTRAN I, II, and III, em: R.L. Wexelblat (Ed.), em: History of
Programming Languages, Academic Press, Nova York, 1981, p. 25-45.
[27] J.W. Backus, R.J. Beeber, S. Best, R. Goldberg, L.M. Haibt, H.L. Herrick, etal. The FORTRAN
automatic coding system, em: Proceedings of the Western Joint Computer Conference, Institute
of Radio Engineers, Nova York, 1957, p. 188-198.
[28] D.F. Bacon, S.L. Graham, O.J. Sharp, Compiler transformations for high-performance computing,
ACM Comput. Surv. 26 (4) (1994), p. 345-420.
[29] J.-L. Baer, D.P. Bovet, Compilation of arithmetic expressions for parallel computation, em: Proceedings of 1968 IFIP Congress, North-Holland Publishing Company, Amsterdam, 1969, p. 340-346.
[30] J.T. Bagwell Jr., Local optimizations, SIGPLAN Not. 5 (7) (1970), p. 52-66. Proceedings of a
Symposium on Compiler Optimization.
[31] J.E. Ball, Predicting the effects of optimization on a procedure body, em: SIGPLAN 79: Proceedings of the 1979 SIGPLAN Symposium on Compiler Construction, ACM, Nova York, 1979,
p. 214-220.
[32] J. Banning, An efficient way to find side effects of procedure calls and aliases of variables,
em: Conference Record of the Sixth Annual ACM Symposium on Principles of Programming
Languages, San Antonio, TX, ACM, Nova York, 1979, p. 29-41.
[33] W.A. Barrett, J.D. Couch, Compiler Construction: Theory and Practice, Science Research Associates, Inc, Chicago, IL, 1979.
[34] J.M. Barth, An interprocedural data flow analysis algorithm, em: Conference Record of the Fourth
ACM Symposium on Principles of Programming Languages, Los Angeles, CA, ACM, Nova York,
1977, p. 119-131.
[35] A.M. Bauer, H.J. Saal, Does APL really need run-time checking?, Softw. Pract. Experience 4 (2)
(1974), p. 129-138.
[36] L.A. Belady, A study of replacement algorithms for a virtual storage computer, IBM Syst. J 5 (2)
(1966), p. 78-101.
[37] P. Bergner, P. Dahl, D. Engebretsen, M.T. OKeefe, Spill code minimization via interference
region spilling, SIGPLAN Not 32 (5) (1997), p. 287-295. Proceedings of the ACM SIGPLAN
97 Conference on Programming Language Design and Implementation.
[38] D. Bernstein, D.Q. Goldin, M.C. Golumbic, H. Krawczyk, Y. Mansour, I. Nahshon, etal. Spill
code minimization techniques for optimizing compilers, SIGPLAN Not. 24 (7) (1989), p. 258263. Proceedings of the ACM SIGPLAN 89 Conference on Programming Language Design and
Implementation.
[39] D. Bernstein, M. Rodeh, Global instruction scheduling for superscalar machines, SIGPLAN Not.
26 (6) (1991), p. 241-255. Proceedings of the ACM SIGPLAN 91 Conference on Programming
Language Design and Implementation.
[40] R.L. Bernstein, Producing good code for the case statement, Softw. Pract. Experience 15 (10)
(1985), p. 1021-1024.
[41] A. Binstock, J. Rex, Practical Algorithms for Programmers, Addison-Wesley, Reading, MA, 1995.
[42] P.L. Bird, An implementation of a code generator specification language for table driven code
generators, SIGPLAN Not. 17 (6) (1982), p. 44-55. Proceedings of the ACM SIGPLAN 82
Symposium on Compiler Construction.
[43] R. Bodk, R. Gupta, M.L. Soffa, Complete removal of redundant expressions, SIGPLAN Not 33 (5)
(1998), p. 1-14. Proceedings of the ACM SIGPLAN 98 Conference on Programming Language
Design and Implementation.
[44] H.-J. Boehm, Space efficient conservative garbage collection, SIGPLAN Not. 28 (6) (1993), p.
197-206. Proceedings of the ACM SIGPLAN 93 Conference on Programming Language Design
and Implementation.
[45] H.-J. Boehm, A. Demers, Implementing Russell, SIGPLAN Not. 21 (7) (1986), p. 186-195.
Proceedings of the ACM SIGPLAN 86 Symposium on Compiler Construction.
[46] H.-J. Boehm, M. Weiser, Garbage collection in an uncooperative environment, Softw. Pract.
Experience 18 (9) (1988), p. 807-820.
[47] F. Bouchez, A Study of Spilling and Coalescing in Register Allocation As Two Separate Phases,
tese de Ph.D., cole Normale Suprieure de Lyon, Lyon, Frana, 2009.
[48] D.G. Bradlee, S.J. Eggers, R.R. Henry, Integrating register allocation and instruction scheduling for
RISCs, SIGPLAN Not. 26 (4) (1991), p. 122-131. Proceedings of the Fourth International Conference
on Architectural Support for Programming Languages and Operating Systems (ASPLOS-IV).
Bibliografia 635
[49] P. Briggs, Register Allocation via Graph Coloring, tese de Ph.D., Department of Computer Science,
Rice University, Houston, TX, 1992. Technical Report TR92-183, Computer Science Department,
Rice University, 1992.
[50] P. Briggs, K.D. Cooper, T.J. Harvey, L.T. Simpson, Practical improvements to the construction and
destruction of static single assignment form, Softw. Pract. Experience 28 (8) (1998), p. 859-881.
[51] P. Briggs, K.D. Cooper, K. Kennedy, L. Torczon, Coloring heuristics for register allocation,
SIGPLAN Not 24 (7) (1989), p. 275-284. Proceedings of the ACM SIGPLAN 89 Conference
on Programming Language Design and Implementation.
[52] P. Briggs, K.D. Cooper, K. Kennedy, L. Torczon, Digital computer register allocation and code
spilling using interference graph coloring, Patente dos Estados Unidos 5.249.295, maro de 1993.
[53] P. Briggs, K.D. Cooper, L.T. Simpson, Value numbering, Softw. Pract. Experience 27 (6) (1997),
p. 701-724.
[54] P. Briggs, K.D. Cooper, L. Torczon, Coloring register pairs, ACM Lett. Program. Lang. Syst 1 (1)
(1992), p. 3-13.
[55] P. Briggs, K.D. Cooper, L. Torczon, Rematerialization, SIGPLAN Not 27 (7) (1992), p. 311-321.
Proceedings of the ACM SIGPLAN 92 Conference on Programming Language Design and
Implementation.
[56] P. Briggs, K.D. Cooper, L. Torczon, Improvements to graph coloring register allocation, ACM
Trans. Program. Lang. Syst 16 (3) (1994), p. 428-455.
[57] P. Briggs, L. Torczon, An efficient representation for sparse sets, ACM Lett. Program. Lang. Syst
2 (1-4) (1993), p. 59-69.
[58] P. Brisk, F. Dabiri, J. Macbeth, M. Sarrafzadeh, Polynomial time graph coloring register allocation,
em: 14th International Workshop on Logic and Synthesis, Lake Arrowhead, CA, 2005, p. 447-454.
[59] K. Brouwer, W. Gellerich, E. Ploedereder, Myths and facts about the efficient implementation of
finite automata and lexical analysis, em: Proceedings of the International Conference on Compiler
Construction CC1998, vol. 1883 of LNCS, Springer-Verlag, Berlin, Heidelberg, 1998, p. 1-15.
[60] J.A. Brzozowski, Canonical regular expressions and minimal state graphs for definite events,
em: Mathematical Theory of Automata, vol. 12 da MRI Symposia Series, Polytechnic Press,
Polytechnic Institute of Brooklyn, Nova York, 1962, p. 529-561.
[61] A.L. Buchsbaum, H. Kaplan, A. Rogers, J.R. Westbrook, Linear-time pointer-machine algorithms for
least common ancestors, MST verification, and dominators, em: Proceedings of the Thirtieth Annual
ACM Symposium on Theory of Computing (STOC), Dallas, TX, ACM, Nova York, 1998, p. 279-288.
[62] M. Burke, An interval-based approach to exhaustive and incremental interprocedural data-flow
analysis, ACM Trans. Program. Lang. Syst 12 (3) (1990), p. 341-395.
[63] M. Burke, J.-D. Choi, S. Fink, D. Grove, M. Hind, V. Sarkar, etal. The Jalapeo dynamic
optimizing compiler for JavaTM, em: Proceedings of the ACM 1999 Conference on Java Grande,
San Francisco, CA, ACM, Nova York, 1999, p. 129-141.
[64] M. Burke, L. Torczon, Interprocedural optimization: eliminating unnecessary recompilation, ACM
Trans. Program. Lang. Syst 15 (3) (1993), p. 367-399.
[65] J. Cai, R. Paige, Using multiset discrimination to solve language processing problems without
hashing, Theor. Comput. Sci 145 (1-2) (1995), p. 189-228.
[66] B. Calder, D. Grunwald, Reducing branch costs via branch alignment, SIGPLAN Not 29 (11)
(1994), p. 242-251. Proceedings of the Sixth International Conference on Architectural Support
for Programming Languages and Operating Systems (ASPLOS-VI).
[67] D. Callahan, S. Carr, K. Kennedy, Improving register allocation for subscripted variables,
SIGPLAN Not 25 (6) (1990), p. 53-65. Proceedings of the ACM SIGPLAN 90 Conference on
Programming Language Design and Implementation.
[68] D. Callahan, K.D. Cooper, K. Kennedy, L. Torczon, Interprocedural constant propagation, SIGPLAN Not 21 (7) (1986), p. 152-161. Proceedings of the ACM SIGPLAN 86 Symposium on
Compiler Construction.
[69] L. Cardelli, Type systems, em: A.B. Tucker Jr. (Ed.), em: The Computer Science and Engineering
Handbook, CRC Press, Boca Raton, FL, 1996, p. 2208-2236.
[70] S. Carr, K. Kennedy, Scalar replacement in the presence of conditional control flow, Softw. Pract.
Experience 24 (1) (1994), p. 51-77.
[71] R.G.G. Cattell, Automatic derivation of code generators from machine descriptions, ACM Trans.
Program. Lang. Syst. 2 (2) (1980), p. 173-190.
[72] R.G.G. Cattell, J.M. Newcomer, B.W. Leverett, Code generation in a machine-independent
compiler, SIGPLAN Not. 14 (8) (1979), p. 65-75. Proceedings of the ACM SIGPLAN 79
Symposium on Compiler Construction.
636 Bibliografia
[73] G.J. Chaitin, Register allocation and spilling via graph coloring, SIGPLAN Not. 17 (6) (1982),
p. 98-105. Proceedings of the ACM SIGPLAN 82 Symposium on Compiler Construction.
[74] G.J. Chaitin, Register allocation and spilling via graph coloring, Patente dos Estados Unidos
4.571.678, fevereiro de 1986.
[75] G.J. Chaitin, M.A. Auslander, A.K. Chandra, J. Cocke, M.E. Hopkins, P.W. Markstein, Register
allocation via coloring, Comput. Lang. 6 (1) (1981), p. 47-57.
[76] D.R. Chase, An improvement to bottom-up tree pattern matching, em: Proceedings of the Fourteenth Annual ACM Symposium on Principles of Programming Languages, Munique, Alemanha,
ACM, Nova York, 1987, p. 168-177.
[77] D.R. Chase, M. Wegman, F.K. Zadeck, Analysis of pointers and structures, SIGPLAN Not 25
(6) (1990), p. 296-310. Proceedings of the ACM SIGPLAN 90 Conference on Programming
Language Design and Implementation.
[78] J.B. Chen, B.D.D. Leupen, Improving instruction locality with just-in-time code layout, em:
Proceedings of the First USENIX Windows NT Workshop, Seattle, WA, The USENIX Association,
Berkeley, CA, 1997, p. 25-32.
[79] C.J. Cheney, A nonrecursive list compacting algorithm, Commun. ACM 13 (11) (1970), p. 677-678.
[80] J.-D. Choi, M. Burke, P.R. Carini, Efficient flow-sensitive interprocedural computation of
pointer-induced aliases and side effects, em: Proceedings of the Twentieth Annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, Charleston, SC, ACM,
Nova York, 1993, p. 232-245.
[81] F.C. Chow, A Portable Machine-Independent Global Optimizer Design and Measurements,
tese de Ph.D., Department of Electrical Engineering, Stanford University, Stanford, CA, 1983.
Technical Report CSL-TR-83-254, Computer Systems Laboratory, Stanford University, dezembro
de 1983.
[82] F.C. Chow, J.L. Hennessy, Register allocation by priority-based coloring, SIGPLAN Not 19 (6)
(1984), p. 222-232. Proceedings of the ACM SIGPLAN 84 Symposium on Compiler Construction.
[83] F.C. Chow, J.L. Hennessy, The priority-based coloring approach to register allocation, ACM Trans.
Program. Lang. Syst 12 (4) (1990), p. 501-536.
[84] C. Click, Combining Analyses, Combining Optimizations, tese de Ph.D., Department of Computer
Science, Rice University, Houston, TX, 1995. Technical Report TR95-252, Computer Science
Department, Rice University, 1995.
[85] C. Click, Global code motion/global value numbering, SIGPLAN Not 30 (6) (1995), p. 246-257.
Proceedings of the ACM SIGPLAN 95 Conference on Programming Language Design and
Implementation.
[86] C. Click, K.D. Cooper, Combining analyses, combining optimizations, ACM Trans. Program.
Lang. Syst 17 (2) (1995), p. 181-196.
[87] J. Cocke, Global common subexpression elimination, SIGPLAN Not 5 (7) (1970), p. 20-24.
Proceedings of a Symposium on Compiler Optimization.
[88] J. Cocke, K. Kennedy, An algorithm for reduction of operator strength, Commun. ACM 20 (11)
(1977), p. 850-856.
[89] J. Cocke, P.W. Markstein, Measurement of program improvement algorithms, em: S.H. Lavington
(Ed.), em: Proceedings of IFIP Congress 80, Information Processing 80, North Holland, Amsterdam, Netherlands, 1980, p. 221-228.
[90] J. Cocke, P.W. Markstein, Strength reduction for division and modulo with application to accessing a multilevel store, IBM J. Res. Dev 24 (6) (1980), p. 692-694.
[91] J. Cocke, J.T. Schwartz, Programming Languages and Their Compilers: Preliminary Notes,
Technical Report, Courant Institute of Mathematical Sciences, New York University, Nova York,
1970.
[92] J. Cohen, Garbage collection of linked structures, ACM Comput. Surv 13 (3) (1981), p. 341-367.
[93] R. Cohn, P.G. Lowney, Hot cold optimization of large Windows/NT applications, em: Proceedings of the Twenty-Ninth IEEE/ACM Annual International Symposium on Microarchitecture
(MICRO-29), Paris, Frana, ACM, Nova York, 1996, p. 80-89.
[94] S. Coleman, K.S. McKinley, Tile size selection using cache organization and data layout, SIGPLAN Not 30 (6) (1995), p. 279-290. Proceedings of the ACM SIGPLAN 95 Conference on
Programming Language Design and Implementation.
[95] G.E. Collins, A method for overlapping and erasure of lists, Commun. ACM 3 (12) (1960),
p. 655-657.
[96] M.E. Conway, Design of a separable transition diagram compiler, Commun. ACM 6 (7) (1963),
p. 396-408.
Bibliografia 637
[97] R.W. Conway, T.R. Wilcox, Design and implementation of a diagnostic compiler for PL/I,
Commun. ACM 16 (3) (1973), p. 169-179.
[98] K.D. Cooper, J. Eckhardt, Improved passive splitting, em: Proceedings of the 2005 International
Conference on Programming Languages and Compilers, Computer Science Research, Education,
and Applications (CSREA) Press, Athens, Gergia, 2005, p. 1155-1122.
[99] K.D. Cooper, M.W. Hall, L. Torczon, An experiment with inline substitution, Softw. Pract. Experience 21 (6) (1991), p. 581-601.
[100] K.D. Cooper, T.J. Harvey, K. Kennedy, A Simple, Fast Dominance Algorithm, Technical Report
TR06-38870, Rice University Computer Science Department, Houston, TX, 2006.
[101] K.D. Cooper, T.J. Harvey, L. Torczon, How to build an interference graph, Softw. Pract. Experience 28 (4) (1998), p. 425-444.
[102] K.D. Cooper, K. Kennedy, Interprocedural side-effect analysis in linear time, SIGPLAN Not
23 (7) (1988), p. 57-66. Proceedings of the ACM SIGPLAN 88 Conference on Programming
Language Design and Implementation.
[103] K.D. Cooper, K. Kennedy, Fast interprocedural alias analysis, em: Proceedings of the Sixteenth
Annual ACM Symposium on Principles of Programming Languages, Austin, TX, ACM, Nova
York, 1989, p. 49-59.
[104] K.D. Cooper, K. Kennedy, L. Torczon, The impact of interprocedural analysis and optimization
in the Rn programming environment, ACM Trans. Program. Lang. Syst. 8 (4) (1986), p. 491-523.
[105] K.D. Cooper, P.J. Schielke, Non-local instruction scheduling with limited code growth, em:
F. Mueller, A. Bestavros (Eds.), em: Proceedings of the 1998 ACM SIGPLAN Workshop on
Languages, Compilers, and Tools for Embedded Systems (LCTES), Lecture Notes in Computer
Science 1474, Springer-Verlag, Heidelberg, Alemanha, 1998, p. 193-207.
[106] K.D. Cooper, L.T. Simpson, Live range splitting in a graph coloring register allocator, em:
Proceedings of the Seventh International Compiler Construction Conference (CC 98), Lecture
Notes in Computer Science 1383, Springer-Verlag, Heidelberg, Alemanha, 1998, p. 174-187.
[107] K.D. Cooper, L.T. Simpson, C.A. Vick, Operator strength reduction, ACM Trans. Program.
Lang. Syst 23 (5) (2001), p. 603-625.
[108] K.D. Cooper, T. Waterman, Understanding energy consumption on the C62x, em: Proceedings
of the 2002 Workshop on Compilers and Operating Systems for Low Power, Charlottesville, VA,
2002, . p. 4-1-4-8.
[109] T.H. Cormen, C.E. Leiserson, R.L. Rivest, Introduction to Algorithms, MIT Press, Cambridge,
MA, 1992.
[110] R. Cytron, J. Ferrante, B.K. Rosen, M.N. Wegman, F.K. Zadeck, Efficiently computing static
single assignment form and the control dependence graph, ACM Trans. Program. Lang. Syst 13
(4) (1991), p. 451-490.
[111] R. Cytron, A. Lowry, F.K. Zadeck, Code motion of control structures in high-level languages,
em: Conference Record of the Thirteenth Annual ACM Symposium on Principles of Programming
Languages, St. Petersburg Beach, FL, ACM, Nova York, 1986, p. 70-85.
[112] J. Daciuk, Comparison of construction algorithms for minimal, acyclic, deterministic finite-state
automata from sets of strings, em: Seventh International Conference on Implementation and
Application of Automata, CIAA 2002, vol. 2068 of LNCS, Springer-Verlag, Berlin, Heidelberg,
2003, p. 255-261.
[113] M. Das, Unification-based pointer analysis with directional assignments, SIGPLAN Not. 35 (5)
(2000), p. 35-46. Proceedings of the ACM SIGPLAN 00 Conference on Programming Language
Design and Implementation.
[114] J. Davidson, S. Jinturkar, Aggressive loop unrolling in a retargetable optimizing compiler, in:
Proceedings of the 6th International Conference on Compiler Construction (CC 96), Linkping,
Sucia, 24-26 de abril, Springer-Verlag, Londres, 1996, p. 59-73.
[115] J.W. Davidson, C.W. Fraser, The design and application of a retargetable peephole optimizer,
ACM Trans. Program. Lang. Syst. 2 (2) (1980), p. 191-202.
[116] J.W. Davidson, C.W. Fraser, Automatic generation of peephole optimizations, SIGPLAN Not .19 (6)
(1984), p. 111-116. Proceedings of the ACM SIGPLAN 84 Symposium on Compiler Construction.
[117] J.W. Davidson, C.W. Fraser, Register allocation and exhaustive peephole optimization, Softw.
Pract. Experience 14 (9) (1984), p. 857-865.
[118] J.W. Davidson, C.W. Fraser, Automatic inference and fast interpretation of peephole optimization
rules, Softw. Pract. Experience 17 (11) (1987), p. 801-812.
[119] J.W. Davidson, A.M. Holler, A study of a C function inliner, Softw. Pract. Experience 18 (8)
(1988), p. 775-790.
638 Bibliografia
[120] A.J. Demers, M. Weiser, B. Hayes, H. Boehm, D. Bobrow, S. Shenker, Combining generational
and conservative garbage collection: framework and implementations, em: Proceedings of the
Seventeenth Annual ACM Symposium on Principles of Programming Languages, San Francisco,
CA, ACM, Nova York, 1990, p. 261-269.
[121] F. DeRemer, Simple LR(k) grammars, Commun. ACM 14 (7) (1971), p. 453-460.
[122] F. DeRemer, T.J. Pennello, Efficient computation of LALR(1) look-ahead sets, SIGPLAN Not
14 (8) (1979), p. 176-187. Proceedings of the ACM SIGPLAN 79 Symposium on Compiler
Construction.
[123] A. Deutsch, Interprocedural May-Alias analysis for pointers: beyond k-limiting, SIGPLAN Not
29 (6) (1994), p. 230-241. Proceedings of the ACM SIGPLAN 94 Conference on Programming
Language Design and Implementation.
[124] L.P. Deutsch, An Interactive Program Verifier, tese de Ph.D., Computer Science Department,
University of California, Berkeley, CA, 1973. Technical Report CSL-73-1, Xerox Palo Alto
Research, maio de 1973.
[125] L.P. Deutsch, D.G. Bobrow, An efficient, incremental, automatic, garbage collector, Commun.
ACM 19 (9) (1976), p. 522-526.
[126] L.P. Deutsch, A.M. Schiffman, Efficient implementation of the Smalltalk-80 system, em:
Conference Record of the Eleventh Annual ACM Symposium on Principles of Programming
Languages, Salt Lake City, UT, ACM, Nova York, 1984, p. 297-302.
[127] D.M. Dhamdhere, On algorithms for operator strength reduction, Commun. ACM 22 (5) (1979),
p. 311-312.
[128] D.M. Dhamdhere, A fast algorithm for code movement optimisation, SIGPLAN Not 23 (10)
(1988), p. 172-180.
[129] D.M. Dhamdhere, A new algorithm for composite hoisting and strength reduction, Int. J. Comput.
Math. 27 (1) (1989), p. 1-14.
[130] D.M. Dhamdhere, Practical adaptation of the global optimization algorithm of Morel and
Renvoise, ACM Trans. Program. Lang. Syst. 13 (2) (1991), p. 291-294.
[131] D.M. Dhamdhere, J.R. Isaac, A composite algorithm for strength reduction and code movement
optimization, Int. J. Comput. Inf. Sci. 9 (3) (1980), p. 243-273.
[132] M.K. Donegan, R.E. Noonan, S. Feyock, A code generator generator language, SIGPLAN Not.
14 (8) (1979), p. 58-64. Proceedings of the ACM SIGPLAN 79 Symposium on Compiler Construction.
[133] K.-H. Drechsler, M.P. Stadel, A solution to a problem with Morel and Renvoises Global
optimization by suppression of partial redundancies, ACM Trans. Program. Lang. Syst. 10 (4)
(1988), p. 635-640.
[134] K.-H. Drechsler, M.P. Stadel, A variation of Knoop, Rthing, and Steffens lazy code motion,
SIGPLAN Not. 28 (5) (1993), p. 29-38.
[135] J. Earley, An efficient context-free parsing algorithm, Commun. ACM 13 (2) (1970), p. 94-102.
[136] K. Ebcioglu, T. Nakatani, A new compilation technique for parallelizing loops with unpredictable
branches on a VLIW architecture, em: Selected Papers of the Second Workshop on Languages
and Compilers for Parallel Computing (LCPC 89), Pitman Publishing, Londres, 1990, p. 213229.
[137] J.R. Ellis, Bulldog: A Compiler for VLIW Architectures, The MIT Press, Cambridge, MA, 1986.
[138] M. Emami, R. Ghiya, L.J. Hendren, Context-sensitive interprocedural points-to analysis in the
presence of function pointers, SIGPLAN Not 29 (6) (1994), p. 242-256. Proceedings of the ACM
SIGPLAN 94 Conference on Programming Language Design and Implementation.
[139] A.P. Ershov, On programming of arithmetic expressions, Commun. ACM 1 (8) (1958), p. 3-6.
As figuras aparecem no volume 1, n. 9, p. 16.
[140] A.P. Ershov, Reduction of the problem of memory allocation in programming to the problem
of coloring the vertices of graphs, Sov. Math 3 (1962), p. 163-165. Publicado originalmente em
Doklady Akademii Nauk S.S.S.R. 142 (4) (1962).
[141] A.P. Ershov, Alpha: an automatic programming system of high efficiency, J. ACM 13 (1) (1966),
p. 17-24.
[142] R. Farrow, Linguist-86: yet another translator writing system based on attribute grammars,
SIGPLAN Not. 17 (6) (1982), p. 160-171. Proceedings of the ACM SIGPLAN 82 Symposium
on Compiler Construction.
[143] R. Farrow, Automatic generation of fixed-point-finding evaluators for circular, but well-defined, attribute grammars, SIGPLAN Not. 21 (7) (1986), p. 85-98. Proceedings of the ACM SIGPLAN 86
Symposium on Compiler Construction.
Bibliografia 639
[144] R.R. Fenichel, J.C. Yochelson, A LISP garbage-collector for virtual-memory computer systems,
Commun. ACM 12 (11) (1969), p. 611-612.
[145] J. Ferrante, K.J. Ottenstein, J.D. Warren, The program dependence graph and its use in optimization, ACM Trans. Program. Lang. Syst. 9 (3) (1987), p. 319-349.
[146] C.N. Fischer, R.J. LeBlanc Jr., The implementation of run-time diagnostics in Pascal, IEEE
Trans. Software Eng. SE-6 (4) (1980), p. 313-319.
[147] C.N. Fischer, R.J. LeBlanc Jr., Crafting a Compiler with C, Benjamin/Cummings, Redwood
City, CA, 1991.
[148] J.A. Fisher, Trace scheduling: a technique for global microcode compaction, IEEE Trans.
Comput. C-30 (7) (1981), p. 478-490.
[149] J.A. Fisher, J.R. Ellis, J.C. Ruttenberg, A. Nicolau, Parallel processing: a smart compiler and
a dumb machine, SIGPLAN Not. 19 (6) (1984), p. 37-47. Proceedings of the ACM SIGPLAN
84 Symposium on Compiler Construction.
[150] R.W. Floyd, An algorithm for coding efficient arithmetic expressions, Commun. ACM 4 (1)
(1961), p. 42-51.
[151] J.M. Foster, A syntax improving program, Comput. J 11 (1) (1968), p. 31-34.
[152] C.W. Fraser, D.R. Hanson, T.A. Proebsting, Engineering a simple, efficient code generator
generator, ACM Lett. Program. Lang. Syst. 1 (3) (1992), p. 213-226.
[153] C.W. Fraser, R.R. Henry, Hard-coding bottom-up code generation tables to save time and space,
Softw. Pract. Experience 21 (1) (1991), p. 1-12.
[154] C.W. Fraser, A.L. Wendt, Integrating code generation and optimization, SIGPLAN Not. 21 (7)
(1986), p. 242-248. Proceedings of the ACM SIGPLAN 86 Symposium on Compiler Construction.
[155] C.W. Fraser, A.L. Wendt, Automatic generation of fast optimizing code generators, SIGPLAN
Not. 23 (7) (1988), p. 79-84. Proceedings of the ACM SIGPLAN 88 Conference on Programming Language Design and Implementation.
[156] M. Ganapathi, C.N. Fischer, Description-driven code generation using attribute grammars,
em: Conference Record of the Ninth Annual ACM Symposium on Principles of Programming
Languages, Albuquerque, NM, ACM, Nova York, 1982, p. 108-119.
[157] H. Ganzinger, R. Giegerich, U. Mncke, R. Wilhelm, A truly generative semantics-directed
compiler generator, SIGPLAN Not. 17 (6) (1982), p. 172-184. Proceedings of the ACM SIGPLAN 82 Symposium on Compiler Construction.
[158] L. George, A.W. Appel, Iterated register coalescing, em: Proceedings of the Twenty-Third ACM
SIGPLAN-SIGACT Symposium on Principles of Programming Languages, St. Petersburg Beach,
FL, ACM, Nova York, 1996, p. 208-218.
[159] P.B. Gibbons, S.S. Muchnick, Efficient instruction scheduling for a pipelined architecture,
SIGPLAN Not. 21 (7) (1986), p. 11-16. Proceedings of the ACM SIGPLAN 86 Symposium on
Compiler Construction.
[160] R.S. Glanville, S.L. Graham, A new method for compiler code generation, em: Conference
Record of the Fifth Annual ACM Symposium on Principles of Programming Languages, Tucson,
AZ, ACM, Nova York, 1978, p. 231-240.
[161] N. Gloy, M.D. Smith, Procedure placement using temporal-ordering information, ACM Trans.
Program. Lang. Syst. 21 (5) (1999), p. 977-1027.
[162] A. Goldberg, D. Robson, Smalltalk-80: The Language and Its Implementation, Addison-Wesley,
Reading, MA, 1983.
[163] J.R. Goodman, W.-C. Hsu, Code scheduling and register allocation in large basic blocks, em: Proceedings of the Second International Conference on Supercomputing, ACM, Nova York, 1988, p. 442-452.
[164] E. Goto, Monocopy and Associative Operations in Extended Lisp, Technical Report 74-03,
Universidade de Tquio, Tquio, Japo, 1974.
[165] S.L. Graham, Table-driven code generation, IEEE Comput 13 (8) (1980), p. 25-34.
[166] S.L. Graham, M.A. Harrison, W.L. Ruzzo, An improved context-free recognizer, ACM Trans.
Program. Lang. Syst. 2 (3) (1980), p. 415-462.
[167] S.L. Graham, R.R. Henry, R.A. Schulman, An experiment in table driven code generation,
SIGPLAN Not. 17 (6) (1982), p. 32-43. Proceedings of the ACM SIGPLAN 82 Symposium on
Compiler Construction.
[168] S.L. Graham, M. Wegman, A fast and usually linear algorithm for global flow analysis, em:
Conference Record of the Second ACM Symposium on Principles of Programming Languages,
Palo Alto, CA, ACM, Nova York, 1975, p. 22-34.
[169] S.L. Graham, M. Wegman, A fast and usually linear algorithm for global flow analysis, J. ACM
23 (1) (1976), p. 172-202.
640 Bibliografia
[170] T. Granlund, R. Kenner, Eliminating branches using a superoptimizer and the GNU C compiler,
SIGPLAN Not. 27 (7) (1992), p. 341-352. Proceedings of the ACM SIGPLAN 92 Conference
on Programming Language Design and Implementation.
[171] D. Gries, Compiler Construction for Digital Computers, John Wiley & Sons, Nova York, 1971.
[172] D. Grove, L. Torczon, Interprocedural constant propagation: a study of jump function implementations, em: Proceedings of the ACM SIGPLAN 93 Conference on Programming Language
Design and Implementation (PLDI), ACM, Nova York, 1993, p. 90-99. Tambm publicado como
SIGPLAN Not. 28 (6) (1993).
[173] R. Gupta, Optimizing array bound checks using flow analysis, ACM Lett. Program. Lang. Syst.
(LOPLAS) 2 (1993), p. 135-150.
[174] R. Gupta, M.L. Soffa, Region scheduling: an approach for detecting and redistributing parallelism, IEEE Trans. Software Eng. SE-16 (4) (1990), p. 421-431.
[175] R. Gupta, M.L. Soffa, T. Steele, Register allocation via clique separators, SIGPLAN Not 24
(7) (1989), p. 264-274. Proceedings of the ACM SIGPLAN 89 Conference on Programming
Language Design and Implementation.
[176] S. Hack, Register Allocation for Programs in SSA Form, tese de Ph.D., Universitt Karlsruhe,
Karlsruhe, Alemanha, 2007.
[177] S. Hack, G. Goos, Optimal register allocation for SSA-form programs in polynomial time, Inf.
Process. Lett. 98 (4) (2006), p. 150-155.
[178] M. Hailperin, Cost-optimal code motion, ACM Trans. Program. Lang. Syst. 20 (6) (1998),
p. 1297-1322.
[179] D.R. Hanson, Fast allocation and deallocation of memory based on object lifetimes, Softw. Pract.
Experience 20 (1) (1990), p. 5-12.
[180] D. Harel, A linear time algorithm for finding dominators in flow graphs and related problems,
em: Proceedings of the Seventeenth Annual ACM Symposium on Theory of Computing (STOC),
ACM, Nova York, 1985, p. 185-194.
[181] W.H. Harrison, A Class of Register Allocation Algorithms, Technical Report RC-5342, IBM
Thomas J. Watson Research Center, Yorktown Heights, NY, 1975.
[182] W.H. Harrison, A new strategy for code generation: the general purpose optimizing compiler,
IEEE Trans. Software Eng. SE-5 (4) (1979), p. 367-373.
[183] A.H. Hashemi, D.R. Kaeli, B. Calder, Efficient procedure mapping using cache line coloring,
em: Proceedings of the ACM SIGPLAN 1997 Conference on Programming Language Design
and Implementation, ACM, Nova York, 1997, p. 171-182. Tambm em SIGPLAN Not. 32 (5).
[184] P.J. Hatcher, T.W. Christopher, High-quality code generation via bottom-up tree pattern matching,
em: Conference Record of the Thirteenth Annual ACM Symposium on Principles of Programming
Languages, St. Petersburg Beach, FL, ACM, Nova York, 1986, p. 119-130.
[185] M.S. Hecht, J.D. Ullman, Characterizations of reducible flow graphs, J. ACM 21 (3) (1974), p.
367-375.
[186] M.S. Hecht, J.D. Ullman, A simple algorithm for global data flow analysis problems, SIAM J.
Comput 4 (4) (1975), p. 519-532.
[187] J. Heller, Sequencing aspects of multiprogramming, J. ACM 8 (3) (1961), p. 426-439.
[188] J.L. Hennessy, T. Gross, Postpass code optimization of pipeline constraints, ACM Trans. Program. Lang. Syst. 5 (3) (1983), p. 422-448.
[189] V.P. Heuring, The automatic generation of fast lexical analysers, Softw. Pract. Experience 16
(9) (1986), p. 801-808.
[190] M. Hind, M. Burke, P. Carini, J.-D. Choi, Interprocedural pointer alias analysis, ACM Trans.
Program. Lang. Syst. 21 (4) (1999), p. 848-894.
[191] M. Hind, A. Pioli, Which pointer analysis should I use?, ACM SIGSOFT Software Eng. Notes 25 (5)
(2000), p. 113-123. In Proceedings of the International Symposium on Software Testing and Analysis.
[192] C.M. Hoffmann, M.J. ODonnell, Pattern matching in trees, J. ACM 29 (1) (1982), p. 68-95.
[193] J.E. Hopcroft, An n log n algorithm for minimizing states in a finite automaton, em: Z. Kohavi,
A. Paz (Eds.), em: Theory of Machines and Computations: Proceedings, Academic Press, Nova
York, 1971, p. 189-196.
[194] J.E. Hopcroft, J.D. Ullman, Introduction to Automata Theory, Languages, and Computation,
Addison-Wesley, Reading, MA, 1979.
[195] E. Horowitz, S. Sahni, Fundamentals of Computer Algorithms, Computer Science Press, Inc.,
Potomac, MD, 1978.
[196] L.P. Horwitz, R.M. Karp, R.E. Miller, S. Winograd, Index register allocation, J. ACM 13 (1)
(1966), p. 43-61.
Bibliografia 641
[197] S. Horwitz, P. Pfeiffer, T. Reps, Dependence analysis for pointer variables, SIGPLAN Not. 24
(7) (1989), p. 28-40. Proceedings of the ACM SIGPLAN 89 Conference on Programming
Language Design and Implementation.
[198] S. Horwitz, T. Teitelbaum, Generating editing environments based on relations and attributes,
ACM Trans. Program. Lang. Syst. 8 (4) (1986), p. 577-608.
[199] B.L. Huber, Path-Selection Heuristics for Dominator-Path Scheduling, tese de Mestrado,
Computer Science Department, Michigan Technological University, Houghton, MI, 1995.
[200] W. Hunt, B. Maher, K. Coons, D. Burger, K.S. McKinley, Optimal Huffman tree-height reduction
for instruction level parallelism, manuscrito no publicado, cedido pelos autores, 2006.
[201] W.-M.W. Hwu, S.A. Mahlke, W.Y. Chen, P.P. Chang, N.J. Warter, R.A. Bringmann, etal. The
superblock: an effective technique for VLIW and superscalar compilation, J. Supercomputing
Special Issue on Instruction Level Parallelism 7 (1-2) (1993), p. 229-248.
[202] E.T. Irons, A syntax directed compiler for Algol 60, Commun. ACM 4 (1) (1961), p. 51-55.
[203] M. Jazayeri, K.G. Walter, Alternating semantic evaluator, em: Proceedings of the 1975 Annual
Conference of the ACM, ACM, Nova York, 1975, p. 230-234.
[204] M.S. Johnson, T.C. Miller, Effectiveness of a machine-level, global optimizer, SIGPLAN Not.
21 (7) (1986), p. 99-108. Proceedings of the ACM SIGPLAN 86 Symposium on Compiler
Construction.
[205] S.C. Johnson, Yacc: Yet Another Compiler-Compiler, Technical Report 32 (Computing Science),
AT&T Bell Laboratories, Murray Hill, NJ, 1975.
[206] S.C. Johnson, A tour through the portable C compiler, em: Unix Programmer's Manual, 7. ed.,
vol. 2b, AT&T Bell Laboratories, Murray Hill, NJ, 1979.
[207] W.L. Johnson, J.H. Porter, S.I. Ackley, D.T. Ross, Automatic generation of efficient lexical
processors using finite state techniques, Commun. ACM 11 (12) (1968), p. 805-813.
[208] D.W. Jones, How (not) to code a finite state machine, ACM SIGPLAN Not. 23 (8) (1988),
p. 19-22.
[209] S.M. Joshi, D.M. Dhamdhere, A composite hoisting-strength reduction transformation for global
program optimization, Int. J. Comput. Math 11 (1) (1982), p. 21-44. (part I); 11 (2) 111-126
(part II).
[210] J.B. Kam, J.D. Ullman, Global data flow analysis and iterative algorithms, J. ACM 23 (1) (1976),
p. 158-171.
[211] J.B. Kam, J.D. Ullman, Monotone data flow analysis frameworks, Acta Informatica 7 (1977),
p. 305-317.
[212] T. Kasami, An efficient recognition and syntax analysis algorithm for context-free languages,
Scientific Report AFCRL-65-758, Air Force Cambridge Research Laboratory, Bedford, MA, 1965.
[213] K. Kennedy, A global flow analysis algorithm, Int. J. Comput. Math. Sect. A 3 (1971), p. 5-15.
[214] K. Kennedy, Global Flow Analysis and Register Allocation for Simple Code Structures, tese de
Ph.D., Courant Institute of Mathematical Sciences, New York University, Nova York, 1971.
[215] K. Kennedy, Global dead computation elimination, SETL Newsletter 111, Courant Institute of
Mathematical Sciences, New York University, Nova York, 1973.
[216] K. Kennedy, Reduction in strength using hashed temporaries, SETL Newsletter 102, Courant
Institute of Mathematical Sciences, New York University, Nova York, 1973.
[217] K. Kennedy, Use-definition chains with applications, Comput. Lang 3 (3) (1978), p. 163-179.
[218] K. Kennedy, A survey of data flow analysis techniques, em: N.D. Jones, S.S. Muchnik (Eds.), em:
Program Flow Analysis: Theory and Applications, Prentice-Hall, Englewood Cliffs, NJ, 1981, p. 5-54.
[219] K. Kennedy, L. Zucconi, Applications of graph grammar for program control flow analysis, em:
Conference Record of the Fourth ACM Symposium on Principles of Programming Languages,
Los Angeles, CA, ACM, Nova York, 1977, p. 72-85.
[220] R. Kennedy, F.C. Chow, P. Dahl, S.-M. Liu, R. Lo, M. Streich, Strength reduction via SSAPRE, em:
Proceedings of the Seventh International Conference on Compiler Construction (CC 98), Lecture
Notes in Computer Science 1383, Springer-Verlag, Heidelberg, Alemanha, 1998, p. 144-158.
[221] D.R. Kerns, S.J. Eggers, Balanced scheduling: instruction scheduling when memory latency is
uncertain, SIGPLAN Not. 28 (6) (1993), p. 278-289. Proceedings of the ACM SIGPLAN 93
Conference on Programming Language Design and Implementation.
[222] R.R. Kessler, Peep: an architectural description driven peephole optimizer, SIGPLAN Not. 19 (6)
(1984), p. 106-110. Proceedings of the ACM SIGPLAN 84 Symposium on Compiler Construction.
[223] G.A. Kildall, A unified approach to global program optimization, em: Conference Record of the
ACM Symposium on Principles of Programming Languages, Boston, MA, ACM, Nova York,
1973, p. 194-206.
642 Bibliografia
[224] S.C. Kleene, Representation of events in nerve nets and finite automata, em: C.E. Shannon, J.
McCarthy (Eds.), Automata Studies, Annals of Mathematics Studies, vol. 34, Princeton University
Press, Princeton, NJ, 1956, p. 3-41.
[225] J. Knoop, O. Rthing, B. Steffen, Lazy code motion, SIGPLAN Not. 27 (7) (1992), p. 224-234. Proceedings of the ACM SIGPLAN 92 Conference on Programming Language Design and Implementation.
[226] J. Knoop, O. Rthing, B. Steffen, Lazy strength reduction, Int. J. Program. Lang 1 (1) (1993),
p. 71-91.
[227] D.E. Knuth, A history of writing compilers, Comput. Autom 11 (12) (1962), p. 8-18. Reimpresso
em Compiler Techniques, B.W. Pollack (Ed.), Auerbach, Princeton, NJ, 1972, p. 38-56.
[228] D.E. Knuth, On the translation of languages from left to right, Inf. Control 8 (6) (1965), p. 607-639.
[229] D.E. Knuth, Semantics of context-free languages, Math. Syst. Theory 2 (2) (1968), p. 127-145.
[230] D.E. Knuth, Semantics of context-free languages: correction, Math. Syst. Theory 5 (1) (1971),
p. 95-96.
[231] D.E. Knuth, The Art of Computer Programming, Addison-Wesley, Reading, MA, 1973.
[232] D.C. Kozen, Automata and Computability, Springer-Verlag, Nova York, 1997.
[233] G. Krasner (Ed.), em: Smalltalk-80: Bits of History, Words of Advice, Addison-Wesley, Reading,
MA, 1983.
[234] S.M. Krishnamurthy, A brief survey of papers on scheduling for pipelined processors, SIGPLAN
Not. 25 (7) (1990), p. 97-106.
[235] S.M. Kurlander, C.N. Fischer, Zero-cost range splitting, SIGPLAN Not. 29 (6) (1994), p. 257265. Proceedings of the ACM SIGPLAN 94 Conference on Programming Language Design
and Implementation.
[236] M. Lam, Software pipelining: an effective scheduling technique for VLIW machines, SIGPLAN Not. 23 (7) (1988), p. 318-328. Proceedings of the ACM SIGPLAN 88 Conference on
Programming Language Design and Implementation.
[237] D.A. Lamb, Construction of a peephole optimizer, Softw. Pract. Experience 11 (6) (1981),
p. 639-647.
[238] W. Landi, B.G. Ryder, Pointer-induced aliasing: a problem taxonomy, em: Proceedings of the
Eighteenth Annual ACM Symposium on Principles of Programming Languages, Orlando, FL,
ACM, Nova York, 1991, p. 93-103.
[239] D. Landskov, S. Davidson, B. Shriver, P.W. Mallett, Local microcode compaction techniques,
ACM Comput. Surv. 12 (3) (1980), p. 261-294.
[240] R. Landwehr, H.-S. Jansohn, G. Goos, Experience with an automatic code generator generator, SIGPLAN Not. 17 (6) (1982), p. 56-66. Proceedings of the ACM SIGPLAN 82 Symposium
on Compiler Construction.
[241] J.R. Larus, P.N. Hilfinger, Register allocation in the SPUR Lisp compiler, SIGPLAN Not. 21 (7)
(1986), p. 255-263. Proceedings of the ACM SIGPLAN 86 Symposium on Compiler Construction.
[242] S.S. Lavrov, Store economy in closed operator schemes, J. Comput. Math. Math. Phys. 1
(4) (1961), p. 687-701. Traduo em ingls em U.S.S.R. Computational Mathematics and
Mathematical Physics 3 (1962), p. 810-828.
[243] V. Lefvre, Multiplication By an Integer Constant, Technical Report 4192, INRIA, Lorraine,
Frana, 2001.
[244] T. Lengauer, R.E. Tarjan, A fast algorithm for finding dominators in a flowgraph, ACM Trans.
Program. Lang. Syst. 1 (1) (1979), p. 121-141.
[245] P.M. Lewis, R.E. Stearns, Syntax-directed transduction, J. ACM 15 (3) (1968), p. 465-488.
[246] V. Liberatore, M. Farach-Colton, U. Kremer, Evaluation of algorithms for local register allocation,
em: Proceedings of the Eighth International Conference on Compiler Construction (CC 99), Lecture
Notes in Computer Science 1575, Springer-Verlag, Heidelberg, Alemanha, 1999, p. 137-152.
[247] H. Lieberman, C. Hewitt, A real-time garbage collector based on the lifetimes of objects,
Commun. ACM 26 (6) (1983), p. 419-429.
[248] B. Liskov, R.R. Atkinson, T. Bloom, J.E.B. Moss, C. Schaffert, R. Scheifler e outros, CLU
Reference Manual, Lecture Notes in Computer Science 114, Springer-Verlag, Heidelberg,
Alemanha, 1981.
[249] J.L. Lo, S.J. Eggers, Improving balanced scheduling with compiler optimizations that increase
instruction-level parallelism, SIGPLAN Not 30 (6) (1995), p. 151-162. Proceedings of the ACM
SIGPLAN 95 Conference on Programming Language Design and Implementation.
[250] R. Lo, F. Chow, R. Kennedy, S.-M. Liu, P. Tu, Register promotion by sparse partial redundancy
elimination of loads and stores, SIGPLAN Not. 33 (5) (1998), p. 26-37. Proceedings of the ACM
SIGPLAN 98 Conference on Programming Language Design and Implementation.
Bibliografia 643
[251] P.G. Lowney, S.M. Freudenberger, T.J. Karzes, W.D. Lichtenstein, R.P. Nix, J.S. ODonnell,
etal. The multiflow trace scheduling compiler, J. Supercomputing Special Issue 7 (1-2) (1993),
p. 51-142.
[252] E.S. Lowry, C.W. Medlock, Object code optimization, Commun. ACM 12 (1) (1969), p. 13-22.
[253] J. Lu, K.D. Cooper, Register promotion in C programs, SIGPLAN Not. 32 (5) (1997), p. 308319. Proceedings of the ACM SIGPLAN 97 Conference on Programming Language Design
and Implementation.
[254] J. Lu, R. Shillner, Clean: removing useless control flow, manuscrito no publicado, Department
of Computer Science, Rice University, Houston, TX, 1994.
[255] P. Lucas, The structure of formula-translators, ALGOL Bull (Suppl. 16) (1961), p. 1-27. [Die
strukturanalyse von formelbersetzern, Elektronische Rechenanlagen 3 (4) (1961), p. 159-167.].
[256] P.W. Markstein, V. Markstein, F.K. Zadeck, Reassociation and strength reduction. Captulo de
livro no publicado.
[257] V. Markstein, J. Cocke, P. Markstein, Optimization of range checking, em: Proceedings of the
1982 SIGPLAN Symposium on Compiler Construction, ACM, Nova York, 1982, p. 114-119.
Tambm publicado como SIGPLAN Not. 17 (6) (1982).
[258] H. Massalin, Superoptimizer: a look at the smallest program, SIGPLAN Not 22 (10) (1987),
p. 122-126. Proceedings of the Second International Conference on Architectural Support for
Programming Languages and Operating Systems (ASPLOS-II).
[259] J. McCarthy, Lisp: notes on its past and future, em: Proceedings of the 1980 ACM Conference
on Lisp and Functional Programming, Stanford University, Stanford, CA, 1980. pp. vviii.
[260] W.M. McKeeman, Peephole optimization, Commun. ACM 8 (7) (1965), p. 443-444.
[261] K.S. McKinley, S. Carr, C.-W. Tseng, Improving data locality with loop transformations, ACM
Trans. Program. Lang. Syst 18 (4) (1996), p. 424-453.
[262] R. McNaughton, H. Yamada, Regular expressions and state graphs for automata, IRE Trans.
Electron. Comput. EC-9 (1) (1960), p. 39-47.
[263] R. Metzger, S. Stroud, Interprocedural constant propagation: an empirical study, ACM Lett.
Program. Lang. Syst. (LOPLAS) 2 (1-4) (1993), p. 213-232.
[264] T.C. Miller, Tentative Compilation: A Design for an APL Compiler, tese de Ph.D., Yale University, New Haven, CT, 1978. Ver tambm o artigo com o mesmo ttulo em Proceedings of the
International Conference on APL: Part 1, Nova York, 1979, p. 88-95.
[265] R. Milner, M. Tofte, R. Harper, D. MacQueen, The Definition of Standard ML Revised, MIT
Press, Cambridge, MA, 1997.
[266] J.S. Moore, The Interlisp Virtual Machine Specification, Technical Report CSL 76-5, Xerox Palo
Alto Research Center, Palo Alto, CA, 1976.
[267] E. Morel, C. Renvoise, Global optimization by suppression of partial redundancies, Commun.
ACM 22 (2) (1979), p. 96-103.
[268] R. Morgan, Building an Optimizing Compiler, Digital Press (An imprint of Butterworth-Heinemann), Boston, MA, 1998.
[269] R. Motwani, K.V. Palem, V. Sarkar, S. Reyen, Combining Register Allocation and Instruction
Scheduling, Technical Report 698, Courant Institute of Mathematical Sciences, New York
University, Nova York, 1995.
[270] S.S. Muchnick, Advanced Compiler Design & Implementation, Morgan Kaufmann, San Francisco, CA, 1997.
[271] F. Mueller, D.B. Whalley, Avoiding unconditional jumps by code replication, SIGPLAN Not 27
(7) (1992), p. 322-330. Proceedings of the ACM SIGPLAN 92 Conference on Programming
Language Design and Implementation.
[272] T.P. Murtagh, An improved storage management scheme for block structured languages, ACM
Trans. Program. Lang. Syst. 13 (3) (1991), p. 372-398.
[273] P. Naur (Ed.), J.W. Backus, F.L. Bauer, J. Green, C. Katz, J. McCarthy e outros, Revised report
on the algorithmic language Algol 60, Commun. ACM 6 (1) (1963), p. 1-17.
[274] E.K. Ngassam, B.W. Watson, D.G. Kourie, Hardcoding finite state automata processing, em:
Proceedings of SAICSIT 2003 Annual Conference of the South African Institute of Computer
Scientists and Information Technologists, Repblica da frica do Sul, 2003, p. 111-121.
[275] B.R. Nickerson, Graph coloring register allocation for processors with multiregister operands,
SIGPLAN Not. 25 (6) (1990), p. 40-52. Proceedings of the ACM SIGPLAN 90 Conference on
Programming Language Design and Implementation.
[276] C. Norris, L.L. Pollock, A scheduler-sensitive global register allocator, em: Proceedings of
Supercomputing 93, Portland, OR, ACM, Nova York, 1993, p. 804-813.
644 Bibliografia
[277] C. Norris, L.L. Pollock, An experimental study of several cooperative register allocation and
instruction scheduling strategies, em: Proceedings of the Twenty-Eighth Annual International
Symposium on Microarchitecture (MICRO-28), Ann Arbor, MI, IEEE Computer Society Press,
Los Alamitos, CA, 1995, p. 169-179.
[278] K. Nygaard, O.-J. Dahl, The development of the SIMULA languages, SIGPLAN Not. 13 (8)
(1978), p. 245-272. Proceedings of the First ACM SIGPLAN Conference on the History of
Programming Languages.
[279] M. Paleczny, C.A. Vick, C. Click, The Java HotSpot Server Compiler, em: Proceedings of
the First Java Virtual Machine Research and Technology Symposium (JVM 01), Monterey,
CA, The USENIX Association, Berkeley, CA, 2001, p. 1-12.
[280] J. Park, S.-M. Moon, Optimistic register coalescing, em: Proceedings of the 1998 International
Conference on Parallel Architecture and Compilation Techniques (PACT), IEEE Computer
Society, Washington, DC, 1998, p. 196-204.
[281] E. Pelegr-Llopart, S.L. Graham, Optimal code generation for expression trees: an application
of BURS theory, em: Proceedings of the Fifteenth Annual ACM Symposium on Principles of
Programming Languages, San Diego, CA, ACM, Nova York, 1988, p. 294-308.
[282] T.J. Pennello, Very fast LR parsing, SIGPLAN Not. 21 (7) (1986), p. 145-151. Proceedings of
the ACM SIGPLAN 86 Symposium on Compiler Construction.
[283] F.M.Q. Pereira, J. Palsberg, Register allocation via coloring of chordal graphs, em: Proceedings
of the Asian Symposium on Programming Languages and Systems (APLAS 05), Springer-Verlag,
Berlin, Heidelberg, 2005, p. 315-329.
[284] K. Pettis, R.C. Hansen, Profile guided code positioning, SIGPLAN Not. 25 (6) (1990), p. 16-27.
Proceedings of the ACM SIGPLAN 90 Conference on Programming Language Design and
Implementation.
[285] S.S. Pinter, Register allocation with instruction scheduling: a new approach, SIGPLAN Not. 28
(6) (1993), p. 248-257. Proceedings of the ACM SIGPLAN 93 Conference on Programming
Language Design and Implementation.
[286] G.D. Plotkin, Call-by-name, call-by-value and the l-calculus, Theor. Comput. Sci. 1 (2) (1975),
p. 125-159.
[287] T.A. Proebsting, Simple and efficient BURS table generation, SIGPLAN Not. 27 (7) (1992),
p. 331-340. Proceedings of the ACM SIGPLAN 92 Conference on Programming Language
Design and Implementation.
[288] T.A. Proebsting, Optimizing an ANSI C interpreter with superoperators, em: Proceedings of the
Twenty-Second ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages,
San Francisco, CA, ACM, Nova York, 1995, p. 322-332.
[289] T.A. Proebsting, C.N. Fischer, Linear-time, optimal code scheduling for delayed-load architectures, SIGPLAN Not 26 (6) (1991), p. 256-267. Proceedings of the ACM SIGPLAN 91
Conference on Programming Language Design and Implementation.
[290] R.T. Prosser, Applications of boolean matrices to the analysis of flow diagrams, em: Proceedings of
the Eastern Joint Computer Conference, Institute of Radio Engineers, Nova York, 1959, p. 133-138.
[291] P.W. Purdom Jr., E.F. Moore, Immediate predominators in a directed graph [H], Commun. ACM
15 (8) (1972), p. 777-778.
[292] M.O. Rabin, D. Scott, Finite automata and their decision problems, IBM J. Res. Dev. 3 (2) (1959),
p. 114-125.
[293] B. Randell, L.J. Russell, Algol 60 Implementation: The Translation and Use of Algol 60 Programs
on a Computer, Academic Press, London, 1964.
[294] B.R. Rau, C.D. Glaeser, Some scheduling techniques and an easily schedulable horizontal
architecture for high performance scientific computing, em: Proceedings of the Fourteenth
Annual Workshop on Microprogramming (MICRO-14), Chatham, MA, IEEE Press, Piscataway,
NJ, 1981, p. 183-198.
[295] J.H. Reif, Symbolic programming analysis in almost linear time, em: Conference Record of the
Fifth Annual ACM Symposium on Principles of Programming Languages, Tucson, AZ, ACM,
Nova York, 1978, p. 76-83.
[296] J.H. Reif, H.R. Lewis, Symbolic evaluation and the global value graph, em: Conference Record
of the Fourth ACM Symposium on Principles of Programming Languages, Los Angeles, CA,
ACM, Nova York, 1977, p. 104-118.
[297] T. Reps, Optimal-time incremental semantic analysis for syntax-directed editors, em: Conference Record of the Ninth Annual ACM Symposium on Principles of Programming Languages,
Albuquerque, NM, ACM, Nova York, 1982, p. 169-176.
Bibliografia 645
[298] T. Reps, B. Alpern, Interactive proof checking, em: Conference Record of the Eleventh Annual
ACM Symposium on Principles of Programming Languages, Salt Lake City, UT, ACM, Nova
York, 1984, p. 36-45.
[299] T. Reps, T. Teitelbaum, The Synthesizer Generator: A System for Constructing Language-Based
Editors, Springer-Verlag, Nova York, 1988.
[300] M. Richards, The portability of the BCPL compiler, Softw. Pract. Experience 1 (2) (1971),
p. 135-146.
[301] S. Richardson, M. Ganapathi, Interprocedural analysis versus procedure integration, Inf. Process.
Lett 32 (3) (1989), p. 137-142.
[302] R. Rivest, On self-organizing sequential search heuristics, Commun. ACM 19 (2) (1976),
p. 63-67.
[303] A. Rogers, K. Li, Software support for speculative loads, SIGPLAN Not. 27 (9) (1992), p. 38-50.
Proceedings of the Fifth International Conference on Architectural Support for Programming
Languages and Operating Systems (ASPLOS-V).
[304] B.K. Rosen, M.N. Wegman, F.K. Zadeck, Global value numbers and redundant computations,
em: Proceedings of the Fifteenth Annual ACM Symposium on Principles of Programming
Languages, San Diego, CA, ACM, Nova York, 1988, p. 12-27.
[305] D.J. Rosenkrantz, R.E. Stearns, Properties of deterministic top-down grammars, Inf. Control 17
(3) (1970), p. 226-256.
[306] A.V.S. Sastry, R.D.C. Ju, A new algorithm for scalar register promotion based on SSA form,
SIGPLAN Not. 33 (5) (1998), p. 15-25. Proceedings of the ACM SIGPLAN 98 Conference on
Programming Language Design and Implementation.
[307] R.G. Scarborough, H.G. Kolsky, Improved optimization of FORTRAN object programs, IBM
J. Res. Dev. 24 (6) (1980), p. 660-676.
[308] P.J. Schielke, Stochastic Instruction Scheduling, tese de Ph.D., Department of Computer Science,
Rice University, Houston, TX, 2000. Technical Report TR00-370, Computer Science Department,
Rice University, 2000.
[309] H. Schorr, W.M. Waite, An efficient machine-independent procedure for garbage collection in
various list structures, Commun. ACM 10 (8) (1967), p. 501-506.
[310] J.T. Schwartz, On Programming: An Interim Report on the SETL Project, Installment II: The
SETL Language and Examples of Its Use, Technical Report, Courant Institute of Mathematical
Sciences, New York University, Nova York, 1973.
[311] R. Sethi, J.D. Ullman, The generation of optimal code for arithmetic expressions, J. ACM 17
(4) (1970), p. 715-728.
[312] M. Shapiro, S. Horwitz, Fast and accurate flow-insensitive points-to analysis, em: Proceedings
of the Twenty-Fourth ACM SIGPLAN-SIGACT Symposium on Principles of Programming
Languages, Paris, Frana, ACM, Nova York, 1997, p. 1-14.
[313] R.M. Shapiro, H. Saint, The Representation of Algorithms, Technical Report CA-7002-1432,
Massachusetts Computer Associates, Wakefield, MA, 1970.
[314] P.B. Sheridan, The arithmetic translator-compiler of the IBM FORTRAN automatic coding
system, Commun. ACM 2 (2) (1959), p. 9-21.
[315] M. Sipser, Introduction to the Theory of Computation, PWS Publishing Co, Boston, MA,
1996.
[316] R.L. Sites, D.R. Perkins, Universal P-code Definition, Version 0.2, Technical Report 78-CS-C29,
Department of Applied Physics and Information Sciences, University of California at San Diego,
San Diego, CA, 1979.
[317] D.D. Sleator, R.E. Tarjan, Amortized efficiency of list update and paging rules, Commun. ACM
28 (2) (1985), p. 202-208.
[318] M.D. Smith, M. Horowitz, M.S. Lam, Efficient superscalar performance through boosting,
SIGPLAN Not. 27 (9) (1992), p. 248-259. Proceedings of the Fifth International Conference on
Architectural Support for Programming Languages and Operating Systems (ASPLOS-V).
[319] M.D. Smith, N. Ramsey, G. Holloway, A generalized algorithm for graph-coloring register
allocation, em: Proceedings of the ACM SIGPLAN 2004 Conference on Programming Language
Design and Implementation, ACM, Nova York, 2004, p. 277-288. Tambm em SIGPLAN Not.
39 (6).
[320] M. Smotherman, S.M. Krishnamurthy, P.S. Aravind, D. Hunnicutt, Efficient DAG construction
and heuristic calculation for instruction scheduling, em: Proceedings of the Twenty-Fourth
Annual IEEE/ACM International Symposium on Microarchitecture (MICRO-24), Albuquerque,
NM, ACM, Nova York, 1991, p. 93-102.
646 Bibliografia
[321] A. Sorkin, Some comments on A solution to a problem with Morel and Renvoises Global
optimization by suppression of partial redundancies, ACM Trans. Program. Lang. Syst 11 (4)
(1989), p. 666-668.
[322] T.C. Spillman, Exposing side-effects in a PL/1 optimizing compiler, em: C.V. Freiman, J.E.
Griffith, J.L. Rosenfeld (Eds.), em: Proceedings of IFIP Congress 71, Information Processing
71, North-Holland, Amsterd, Holanda, 1972, p. 376-381.
[323] G.L. Steele Jr., Rabbit: A Compiler for Scheme, Technical Report AI-TR-474, MIT Artificial
Intelligence Laboratory, Massachusetts Institute of Technology, Cambridge, MA, 1978.
[324] G.L. Steele Jr., R.P. Gabriel, History of Programming Languages II, The Evolution of LISP,
ACM Press, Nova York, 1996. pp. 233330.
[325] M. Stephenson, S. Amarasinghe, Predicting unroll factors using supervised classification, em:
CGO 05: Proceedings of the International Symposium on Code Generation and Optimization,
IEEE Computer Society, Washington, DC, 2005, p. 123-134.
[326] P.H. Sweany, S.J. Beaty, Post-compaction register assignment in a retargetable compiler, em:
Proceedings of the Twenty-Third Annual International Symposium and Workshop on Microprogramming and Microarchitecture (MICRO-23), Orlando, FL, IEEE Computer Society Press,
Los Alamitos, CA, 1990, p. 107-116.
[327] P.H. Sweany, S.J. Beaty, Dominator-path scheduling: a global scheduling method, ACM SIGMICRO Newsl 23 (12) (1992), p. 260-263. Proceedings of the Twenty-Fifth Annual International
Symposium on Microarchitecture (MICRO-25).
[328] D. Tabakov, M.Y. Vardi, Experimental evaluation of classical automat constructions, em: Proceedings of the 12th International Conference on Logic for Programming, Artificial Intelligence,
and Reasoning (LPAR 05), Lecture Notes in Computer Science 3835, Springer-Verlag, Berlim,
Heidelberg, 2005, p. 371-386.
[329] R.E. Tarjan, Testing flow graph reducibility, J. Comput. Syst. Sci. 9 (3) (1974), p. 355-365.
[330] R.E. Tarjan, Fast algorithms for solving path problems, J. ACM 28 (3) (1981), p. 594-614.
[331] R.E. Tarjan, A unified approach to path problems, J. ACM 28 (3) (1981), p. 577-593.
[332] R.E. Tarjan, J.H. Reif, Symbolic program analysis in almost-linear time, SIAM J. Comput 11
(1) (1982), p. 81-93.
[333] K. Thompson, Programming techniques: regular expression search algorithm, Commun. ACM
11 (6) (1968), p. 419-422.
[334] S.W.K. Tjiang, Twig Reference Manual, Technical Report CSTR 120Computing Sciences, AT&T
Bell Laboratories, Murray Hill, NJ, 1986.
[335] L. Torczon, Compilation Dependences in an Ambitious Optimizing Compiler, tese de Ph.D.,
Department of Computer Science, Rice University, Houston, TX, 1985.
[336] J.D. Ullman, Fast algorithms for the elimination of common subexpressions, Acta Informatica
2 (3) (1973), p. 191-213.
[337] D. Ungar, Generation scavenging: a non-disruptive high performance storage reclamation
algorithm, ACM SIGSOFT Software Eng. Notes 9 (3) (1984), p. 157-167. Proceedings of the
First ACM SIGSOFT/SIGPLAN Software Engineering Symposium on Practical Software
Development Environments.
[338] V. Vyssotsky, P. Wegner, A Graph Theoretical FORTRAN Source Language Analyzer, Manuscript, AT&T Bell Laboratories, Murray Hill, NJ, 1963.
[339] W. Waite, G. Goos, Compiler Construction, Springer-Verlag, Nova York, 1984.
[340] W.M. Waite, The cost of lexical analysis, Softw. Pract. Experience 16 (5) (1986), p. 473-488.
[341] D.W. Wall, Global register allocation at link time, em: Proceedings of the 1986 ACM SIGPLAN
Symposium on Compiler Construction, ACM, Nova York, 1986, p. 264-275.
[342] S.K. Warren, The Coroutine Model of Attribute Grammar Evaluation, tese de Ph.D., Department
of Mathematical Sciences, Rice University, Houston, TX, 1976.
[343] B. Watson, A fast new semi-incremental algorithm for the construction of minimal acyclic DFAs,
em: Third International Workshop on Implementing Automata, WIA 98, vol. 1660 of LNCS,
Springer-Verlag, Berlin, Heidelberg, 1999, p. 121-132.
[344] B.W. Watson, A taxonomy of deterministic finite automata minimization algorithms, Computing
Science Report 93/44, Eindhoven University of Technology, Department of Mathematics and
Computing Science, Eindhoven, Holanda, 1993.
[345] B.W. Watson, A fast and simple algorithm for constructing minimal acyclic deterministic finite
automata, J. Univers. Comput. Sci. 8 (2) (2002), p. 363-367.
[346] M.N. Wegman, F.K. Zadeck, Constant propagation with conditional branches, em: Conference
Record of the Twelfth Annual ACM Symposium on Principles of Programming Languages, New
Orleans, LA, ACM, Nova York, 1985, p. 291-299.
Bibliografia 647
[347] M.N. Wegman, F.K. Zadeck, Constant propagation with conditional branches, ACM Trans.
Program. Lang. Syst 13 (2) (1991), p. 181-210.
[348] W.E. Weihl, Interprocedural data flow analysis in the presence of pointers, procedure variables,
and label variables, em: Conference Record of the Seventh Annual ACM Symposium on Principles
of Programming Languages, Las Vegas, NV, ACM, Nova York, 1980, p. 83-94.
[349] C. Wiedmann, Steps toward an APL compiler, ACM SIGAPL APL Quote Quad 9 (4) (1979), p.
321-328. Proceedings of the International Conference on APL.
[350] P.R. Wilson, Uniprocessor garbage collection techniques, em: Proceedings of the International
Workshop on Memory Management, Lecture Notes in Computer Science 637, Springer-Verlag,
Heidelberg, Alemanha, 1992, p. 1-42.
[351] R.P. Wilson, M.S. Lam, Efficient context-sensitive pointer analysis for C programs, SIGPLAN
Not. 30 (6) (1995), p. 1-12. Proceedings of the ACM SIGPLAN 95 Conference on Programming
Language Design and Implementation.
[352] M. Wolfe, High Performance Compilers for Parallel Computing, Addison Wesley, Redwood
City, CA, 1996.
[353] D. Wood, The theory of left-factored languages, part 1, Comput. J 12 (4) (1969), p. 349-356.
[354] D. Wood, The theory of left-factored languages, part 2, Comput. J 13 (1) (1970), p. 55-62.
[355] D. Wood, A further note on top-down deterministic languages, Comput. J 14 (4) (1971), p. 396403.
[356] W. Wulf, R.K. Johnsson, C.B. Weinstock, S.O. Hobbs, C.M. Geschke, The Design of an
Optimizing Compiler, Programming Languages Series, Elsevier, Nova York, 1975.
[357] C. Young, D.S. Johnson, D.R. Karger, M.D. Smith, Near-optimal intraprocedural branch
alignment, SIGPLAN Not. 32 (5) (1997), p. 183-193. Proceedings of the ACM SIGPLAN 97
Conference on Programming Language Design and Implementation.
[358] D.H. Younger, Recognition and parsing of context-free languages in time n3, Inf. Control 10 (2)
(1967), p. 189-208.
[359] F.K. Zadeck, Incremental data flow analysis in a structured program editor, SIGPLAN Not. 19
(6) (1984), p. 132-143. Proceedings of the ACM SIGPLAN 84 Symposium on Compiler Construction.
ndice Remissivo
A
abaixamento de cdigo (code sinking), 468, 494
abstrao de chamada de procedimento, 234
accept, ao, 105, 117, 134
ao
accept, 105, 117, 134
erro, 107
gerador de scanner e, 20, 47-49, 52, 54, 56,
59, 60, 67
reduce, 119, 172, 173
shift, 102, 104, 107, 117, 119, 134, 136, 175,
176
Action, tabela, 103, 135
Ver tambm tabelas LR(1)
adiamento, 532, 533, 540, 553, 554
agrupamento
conservador, 591, 592
de cpias, 585
Ver tambm faixas vivas
algoritmo
conservador, 591, 592
de determinao e unio de conjuntos
disjuntos, 576
de insero de cpia, 429, 430
de marcao, 113, 273, 274, 494
otimistas, 433, 434, 495
pessimistas, 433, 495
preciso, 274
alias, 258
alocao
baseada em arena, 270, 277, 615, 622
de pilha, 245, 625
de registradores global, 201, 590
de registros de ativao, 245, 246, 271, 276
de varredura linear, 587, 588, 593
alocadores
de pools mltiplos, 271
registrador, 13, 14, 17, 215, 218, 281, 283,
286, 290, 293, 355, 372, 378, 387,
466, 470, 471, 479, 485, 498, 539,
563-565, 574-575, 579-581, 583,
589, 590, 593, 594, 598, 617
alternao, 27, 28, 32, 33, 36, 38
ambiente
de runtime, 235, 252, 265, 342, 353, 384
de desenvolvimento integrados, 392
ambiguidade sensvel ao contexto, 125
ampliao de unidades de compilao, 392
analisador, gerando lexemas, 58
analisadores sintticos (parsers), 4, 69, 83
coleo cannica, 109, 111, 113
conflito shift-reduce, 122
conflito reduce-reduce, 122
construo de tabela LR(1), 108
esqueleto de parser, 102
anlise
de dependncia, 12, 323
de fluxo de dados iterativa, 402
de ponteiro, 446
dinmica, 408
herdado, 160
sintetizado, 160
autmato
finito, 515
determinstico, 16, 34, 35
no determinstico, 16, 34, 35
algoritmo de minimizao, 43, 45, 47, 62,
65, 67
avaliao em curto-circuito, 302, 303, 306, 339
avaliadores baseados em regra, 162, 172
B
back end, 1, 5-7, 12, 17, 178, 210, 283, 284, 298,
343, 345, 454, 498, 499, 501, 507,
524, 533, 559
Backus-Naur, 72, 138
balanceamento de altura de rvore, 354, 360,
363, 365-367, 373, 374, 394, 395,
453, 476, 479
bloco bsico, 200-202, 204, 207, 209, 214, 215,
323, 324, 327, 329, 352-355, 359363, 365-367, 373, 374, 394, 395,
401, 406, 453, 476, 479, 500, 517,
523, 531, 535, 540, 545, 548-560,
566, 567, 572, 574, 580, 590, 593,
595, 596
estendidos (EBBs Extended Basic Blocks),
353, 354, 367, 548
booleanos, relacionais gramtica de expresso,
299
break, instruo, 330, 440
Brzozowski, algoritmo, 62, 63
bytecode, 3, 6, 18, 205, 211, 249
C
cache
de mtodo, 253, 254, 279
deslocamentos relativos e desempenho de,
289
razo de acerto, 289
caminhos crticos, 540, 553
campo de tamanho explcito, 318
caracteres, 56-60, 67, 142, 143, 149-151, 157,
260, 315, 318
casamento de padres de rvore, 497, 506-508,
513, 514, 516, 518, 524, 527-529
case, instruo, 173, 214, 322, 326, 330-333,
337, 341, 605
categorias sintticas, 9, 20, 46, 73, 74, 82, 143
CFG reverso, 404, 408, 409, 414, 443, 447, 456
chamada
de funo, 126, 128, 190, 191, 279, 297, 589
de procedimento, 10, 57, 146, 148, 190, 195,
199, 203, 206, 216, 238, 242, 243,
251, 258, 262, 268, 330, 354, 377,
384, 387, 411, 414, 434, 447, 470,
471, 498, 578, 615
avaliao de parmetro, 334
implementao, 234, 235, 267, 333, 336
mltiplos locais, 336
parmetros com valor de procedimento,
335
649
chamada (cont.)
problemas de resumo, 413
salvamento e restaurao de registrador,
335
por nome, 256, 257, 280
por referncia, 199, 218, 256-258, 264, 265,
280, 291, 295, 296, 334, 336, 341,
384, 400, 411, 413, 455, 471, 504,
506, 519
por valor, 256-259, 280, 295, 296, 312, 334,
342, 504
chamador, 234, 243, 256, 262, 282, 296, 312,
333, 384, 410, 438, 469
ciclo de construes, 38, 61
classes
de registradores, 566, 595
superclasses, 226, 247, 250-255
Clean, algoritmo, 457, 495
clonagem
de bloco, 477
de procedimento, 476, 477, 479
de superbloco, 476, 477, 479, 493, 494, 496,
551
por contexto, 551, 552
cdigo
de compensao, 549-552
de mquina de pilha, 205, 207
Ver tambm IRs lineares
de prlogo, 266, 313, 455, 470, 555, 558
de trs endereos, 206-208, 211, 216, 229,
285
Ver tambm IRs lineares
elevao, 412, 466
gabaritos, 509, 510, 512, 516
inalcanvel, 459, 480, 482-484
intil, eliminao, 455
Ver tambm eliminao de cdigo intil
morto, 3, 394, 455, 460, 485, 491, 494, 524
pipelining, 553, 555
trs endereos, 205, 206
coleo cannica de conjuntos de itens LR(1),
108, 109, 112, 117, 139
coleta de lixo, 270, 272, 273, 275, 277, 612
coletor
conservador, 274, 277
de cpia, 275
Ver tambm coleta de lixo
de tempo real, 276
em lote (batch), 273
marcar-varrer, 273-275, 455
preciso, 274
colorao
de baixo para cima, 583
Ver tambm alocao de registradores
de grafo, 17, 563, 566, 575, 576, 581, 583-586,
588, 590, 592-595, 597, 611, 617
combinao de otimizaes, 480
compilao separada, 157, 185, 203, 233, 241,
246, 353
compilador
back end, 12, 499, 501, 533
definio, 7
estrutura, 5
front end, 17
otimizao, 454
D
dados de perfil, 379, 380
declaraes
omisso, 185
processamento, 180
definies de alcance, 412
deformao de nome, 260
dependncia
de controle, 456, 459
depurao, 63, 118, 175, 271, 271, 276, 286, 345,
499, 609, 620
derivao
mais direita, 75-78, 101, 102, 104, 107
mais esquerda, 75, 83, 95, 138
derramamento
por regio de interferncia, 592
custo, 575, 578, 593
faixas vivas, 571, 572, 575
global, estimativa de custo, 577
regio de interferncia, 592
valores
limpos, 570, 571, 594
sujos, 570, 594
desdobramento de constante, 355-357, 394, 468
desenrolamento de lao (loop unrolling), 370
externo, 88, 134, 347, 349, 352, 369, 371
interno, 39, 88, 112, 346, 348-350, 352, 371,
421, 513
Ver tambm habilitando transformaes
deslocamento (shift), ao, 602
E
elaborao semntica, 10, 142, 172, 181
Ver tambm anlise sensvel ao contexto
elementos de array, 12, 92, 314, 323, 572
elevao, movimentao de cdigo, 460
eliminao
de cdigo intil, 454
de cdigo morto, 3, 394, 485, 494, 524
de fluxo de controle intil, 457
de redundncia global, 411
endereabilidade, 276
display, 266, 470
link de acesso, 261, 276
link esttico, 261
endereamento aberto, 620, 623
endereo
de base, 259, 260, 267, 292
de retorno, 237
runtime, 240, 243, 247
equivalncia
de nomes, 154
estrutural, 154
observacional, 349
erro
deteco, 107
recuperao, 123
tipo, 155
escalonamento
alocao de registradores e, 539
balanceado, 542
de instrues, 201, 531
de lao, 553
em pipeline, 553
caminho crtico, 536
de lista
desempate no algoritmo, 544
F
FA no determinstico (NFA), 35
Ver tambm autmatos finitos (FA)
faixas vivas, 571
agrupamento, 585
derramamento e, 578
diviso e, 581
em bloco bsico, 572
global, 575
fall-through, desvio, 205, 210, 324, 352, 378381, 389, 503, 522
falso zero, 307, 311, 313, 314
fatorao esquerda, 92, 93, 137
fechamento de Kleene, 27, 28, 32, 33, 61, 65
fechamento finito, 28, 33
fechamento positivo, 28, 33
fechamento
expresses regulares sem fechamento, 63
Kleene, 27, 28, 32, 33, 61
sob concatenao, 32
FIRST, conjunto, 89, 90, 92, 98
FOLLOW, conjunto, 91, 98, 136
for, lao, 326, 327
forma de atribuio nica esttica (SSA), 359,
399, 400, 401
algoritmo simples
construo, 405, 416, 417, 430, 446
diferentes tipos de, 422
dominncia, 417, 449
em IR de trs endereos, 206, 214
insero de cpia, 429, 430
insero de funes-, 416, 417, 419, 421
mxima, 416, 417, 422, 434, 448
mnima, 422
podada, 421, 422
reescrita, 577
renomeao de variveis, 422
semipodada, 417, 422, 434, 446, 586
traduo a partir da, 426, 428, 430, 434, 446
Ver tambm grafos SSA
forma de cdigo, 283, 284, 462, 472, 485, 565, 567
definio, 462
forma sentencial, definio, 73
forma SSA mxima, 416, 417, 422, 434, 448
forma SSA semipodada, 417, 422, 434, 446, 586
FORTRAN
arrays, 151
lao do, 328
regras de escopo, 241
framework de dominncia iterativo, acelerao,
443
free, rotina, 269-271
front end, definio, 7
Ver tambm back end; compiladores
fronteira de dominncia, 418, 422
reversa, 456
G
GCC, compilador, 15, 524
gerao de cdigo, 13, 499
alocao de registradores, 563
escalonamento de instruo, 506
interaes, 16
percurso em rvore, 292, 293, 295, 300, 510,
527
seleo de instrues, 13, 497, 528, 577
gerador de avaliador, 161
geradores de parser, 97, 98, 100, 102, 107, 108,
117, 118, 122
automticos, 123
rotinas de recuperao de erro, 124
geradores de scanner
ferramentas, 47
lex, 47
para construo de reconhecedor, 59
Goto, tabela, 103, 105, 107, 108
preenchimento, 117
Ver tambm tabelas LR(1)
grafo cordal, 593, 595
grafo de dependncia de atributo, 160-162, 171
grafo de intervalo, 566, 587, 593
grafo de precedncia, 535
grafos acclicos direcionados (DAGs Directed
Acyclic Graphs), 199
grafos de chamada, 203
grafo de fluxo de controle (CFG), 406, 500
arestas de volta, 458
blocos bsicos, 324
blocos de nica instruo, 201
criao, 201, 209
frequncias de execuo e, 378, 382, 388, 446
irredutveis, 443
redutveis, 442
grafo de interferncia
como base para agrupamento, 590
construo, 580
definio, 576
imprecisos, 590
tamanho/esparsidade de, 631
vetores de adjacncia e, 618
grafo irredutvel, 443
definio, 441
transformao, 443
grafo redutvel, 441, 443, 445
grafo
acclico direcionado (DAG), 199
arbitrrios, representao, 616
chamada, 203
dependncia, 535, 536, 540
grafo (cont.)
fluxo de controle, 406, 500
interferncia, 576, 579, 580
irredutvel, 443
precedncia, 535
representao tabular de, 630
gramtica de expresso clssica, 79, 86, 124,
134, 163 Ver tambm
gramticas
gramtica de expresso recursiva direita,
89
associatividade direita, 128, 187
profundidade de pilha, 127
gramtica de expresso reduzida, 134, 135
gramtica livre de contexto, 143
-produo, 187, 188
ambiguidade, 190
analisadores sintticos (parsers), 69
como sistema de reescrita, 81
definio, 158
derivao mais direita, 75, 76
derivao mais esquerda, 75
fatorao esquerda, 92, 137
forma de Backus-Naur, 72
gramtica preditiva, 89
produo, 94
smbolo no terminal, 72
smbolo terminal, 72, 85
Ver tambm gramticas
gramtica livre de retrocesso, 89
gramtica preditiva, 89
gramtica ambgua, 76
algoritmo de preenchimento de tabela, 122
conflito reduce-reduce, 122
if-then-else, 119
remoo da ambiguidade, 410
sensveis ao contexto, 183
gramtica de atributo circulares,
162 Ver tambm
gramtica de atributo
gramtica de atributo, 158, 162
avaliador desatento
avaliador dinmico
avaliadores baseados em regra
circulares, 161
grafo de dependncia, 161
mtodos de avaliao, 161
S-atribuda, 164
tratamento de informaes no locais,
170
Ver tambm anlise sensvel ao contexto
gramtica regular, 94
gramtica
expresso clssica, 79, 84, 85, 190, 197
fatorao esquerda, 92, 93, 137
livre de contexto, 71-73, 81, 83, 136, 137,
183, 188
livre de retrocesso, 89
LL(1), 81, 95, 97, 102
LR(1), 81, 102, 107
otimizao, 129
recurso esquerda, 86
regulares, 81
sensveis ao contexto, 183
guias, 209, 210
H
habilitando transformaes, 475
clonagem de procedimento, 477, 479
clonagem de superbloco, 476, 477, 479, 493,
494
desenrolamento de lao (loop unrolling)
remoo de condicionais de lao (loop
unswitching), 477, 478
hashing aberto, 622, 624-626
incluso de escopos lxicos ao, 626
hashing de balde, 620, 622
incluso de escopos lxicos, 626
hashing perfeito, 59
heap de runtime. Ver heap
heap, 268
alocao de primeiro encaixe, 269, 271
alocadores de pools mltiplos, 271
coleta de lixo, 270, 272-275, 277, 612
contagem de referncia, 272, 273, 275-277,
522
herana mltipla, 254, 255
funo trampolim, 255
Ver tambm herana
herana, 247
definio, 247
implementao, 191
mltipla, 254, 255
Ver tambm linguagens orientadas a objeto
Hopcroft, algoritmo de, 42, 47
I
identidade de valor versus identidade de nome,
472, 475
identidades algbricas, 355, 357, 358, 400, 454,
517, 524, 527
if-then-else, ambiguidade, 77, 78
if-then-else, construes, 8, 22, 25, 76, 77, 119,
201, 305, 324, 337, 477, 478
aninhadas, 22, 25
frequncia de execuo, 331
gramtica, 76, 77
ILOC, 210, 211
convenes de nomeao, 601
gerao, expresses para, 180
movimentao condicional, 305
operaes de memria, 12, 217, 545,
602
inferncia de tipos, 144
aspectos interprocedimentais da
framework ad hoc para, 156
para expresses, 156
para expresses, reviso, 157, 178
problemas mais difceis, 185
regras, 155
inferncia
declaraes e, 155
para expresses, 156
regras, 155, 156
instrues
definio
lista sequencial de, 599
por segundo, 533
programa ILOC
inteiros
atribuio, 155
J
Java, 3
traduo, 249
Java, mquina virtual (JVM Java Virtual
Machine), 3, 249
L
lao do, 326
laos em pipeline, 553
Ver tambm escalonamento de lao; laos
laos
desenrolamento (unrolling), 347
do, 326
em pipeline, 553
endereos de estrutura em, 484
escalonamento, 553, 557
for, 326, 327, 378, 615
remoo de condicionais (unswitching), 477,
478
until, 329
while, 39, 54, 329, 543
ladrilhamento, 508, 509, 512, 513, 516, 527
definio, 508
localmente timo, 515
LALR(1), 136, 174
latncias
de operao, 498, 545, 547, 559
load, 554
memria, 18, 578
layout de cdigo, 136, 379-382, 503
M
mquina virtual, 3, 13, 249, 269, 343, 437
mquina RISC, 12, 205, 218, 292, 502, 503, 523,
568, 599
memria de rascunho, 578
memria
cache, 43, 288, 289, 320, 503, 542
latncias, 350, 532
layout de vetor
layout lgico do espao de endereo, 286
operaes com mltiplos registradores, 335
registrador, 290
Mtodo
dinmico, 162, 163
mapeamento de nomes de, 247
mtrica de derramamento, 584, 585, 587
microssintaxe, 20, 29
modelo memria-para-memria, 565, 566
Ver tambm modelos de memria
modelo registrador-para-registrador, 217, 218,
286, 565 Ver tambm
modelos de memria
modelos de memria, 216
escolha de, 217, 218
memria-para-memria, 217
registrador-para-registrador, 217
movimentao condicional, 302, 303, 305
movimentao de cdigo invariante de lao, 461,
550
movimentao de cdigo pouco ativo (LCM
Lazy Code Motion), 460, 462, 472
equaes, 461
expresses antecipveis, 405, 412, 413, 461,
463
expresses disponveis, 405, 411, 412, 413,
437, 447, 460, 461, 463
forma de cdigo, 462
posicionamento mais antigo, 461
posicionamento mais recente, 461
reescrita de cdigo, 461
Ver tambm movimentao de cdigo
movimentao de cdigo, 297, 411, 412, 414,
427, 453, 460, 549, 550, 551, 619
pouco ativo, 460
N
NaN, 358
nome
coordenada esttica para, 239, 240, 244
esttico, 242
expresso, 462
gerenciar o espao de nomes, 226, 290
impacto do, 212
regras para visibilidade de, 235
reutilizao, 206, 479
SSA, 576, 577, 592, 593
totalmente qualificado, 321
O
operao destrutiva, 205
operaes de endereo-deslocamento, 603
operaes de endereo-imediato, 602
operaes de fluxo de controle, 604
predicadas, 303, 522
operaes de memria, 539, 542
atrasos, 542
execuo, 335
hierarquia, 217
sequncia, 335, 570
velocidade, 335
operaes
cdigo de trs endereos, 205-208, 211, 216
comutativas, 357
endereo-deslocamento, 603
inalcanvel, 454
LLIR, 518
longa latncia, 15, 535, 538, 544,
554
operandos, 539, 540, 542
predicadas, 303, 522
sobreposio, 535
string, 315, 318
trs endereos, 206, 292, 567, 599
operador de comparao, 305
operador de complemento, 29, 32
operador de encontro (meet), 403
operadores aritmticos, 292
operadores unrios, 124, 125, 129,
514 Ver tambm
operadores
operadores
atribuio como, 298
booleanos, 292, 298
comparao, 300, 302, 303, 305
fechamento, 27, 61
reduo de fora, 468, 471, 480, 484-488,
491, 492
relacionais, 299
sobrecarga, 145
unrios, 124, 125, 129, 514
ordem por colunas, 308-311, 314, 346, 371
ordem por linhas, 308-310, 312, 314, 346
Ver tambm arrays
OSR, algoritmo, 480, 487, 495
Ver tambm reduo de fora
otimizao de chamada de folha, 469, 470
otimizao de cdigo, 223, 343, 373, 387, 393,
451
Ver tambm otimizao
otimizao global, 353, 374, 375, 387, 392, 468
otimizao interprocedimental, 354, 384, 391,
392
otimizao intraprocedimental, 384, 203
otimizao local, 354, 366, 517, 519
definio, 515
otimizao peephole, 453, 471, 517
definio
janelas fsicas, 522
janelas lgicas, 522
para seleo de instrues, 517
tamanho de janela, 518
otimizao regional, 367
Ver tambm escopo de otimizao
otimizao, 343
avaliao em curto-circuito como, 303
balanceamento de altura de rvore, 374, 394
clonagem de superbloco, 476
combinao, 480
como engenharia de software, 454
desenrolamento de lao (loop unrolling), 347,
367, 370
elevao de cdigo, 466
eliminao de cdigo inalcanvel, 480
eliminao de cdigo intil, 459
eliminao de cdigo morto, 524
escopo de programa inteiro, 468
escopo global, 379, 383
excees em runtime, 359
interprocedimental, 203, 384
intraprocedimental, 203, 384
local, 354, 366, 517, 519
movimentao de cdigo pouco ativo, 460,
462
numerao de valor local, 355, 356, 358, 365367, 372, 373, 393
numerao de valor superlocal, 227, 367, 368,
370, 373, 394, 452, 467, 472, 630
peephole, 453, 468, 471, 494, 507, 517, 518,
523, 524, 527
posicionamento de cdigo global, 378-380,
382, 388, 453
posicionamento de procedimento, 384, 388,
389, 390, 391
procedimentos folha, 246, 264, 387, 470, 471
programa inteiro, 352, 354, 384, 392
P
padres
rvore, 497, 499, 506-508, 513, 514, 516,
518, 524, 527
otimizao peephole, 499, 507, 517
palavras-chave
expresso regular, 19, 27, 47
tratamento de, 59
palavras
categorias sintticas, 47, 48, 53, 58, 134
categorias, 53
em linguagens de programao, 20
paralelismo em nvel de instruo (ILP), 354,
361, 393, 522, 532, 542, 558
parmetro real, 156, 234, 256, 257, 258, 264,
265, 296, 313, 334, 384, 387, 438,
439, 471
parmetros com valor de array
acesso, 312
verificaes de limites, 314
parmetros com valor de procedimento, 335
parmetros
avaliao, 334
com valor de array, 312
com valor de procedimento, 335
formais, 438, 439
passagem de, 256
reais, 387, 437
valores, acesso
vinculao de chamada por nome, 256
vinculao de chamada por referncia, 256
vinculao de chamada por valor, 256
parsers de descida recursiva, 94
estratgia para construir, 95
para uma gramtica preditiva, 130
parsers LL(1) dirigidos por tabela, 95
parsers bottom-up, 100
algoritmo
definio
descida recursiva
eliminao da recurso esquerda, 85
partio de conjunto, 43
Pascal, 30, 142, 143, 151, 152, 155, 189, 210,
236, 237, 239, 245, 321
percurso em rvore, 195
gerador de cdigo, 292
293, 295, 300, 506, 510, 527
para expresses, 293, 300
para seleo de instrues, 527
percurso em pr-ordem, 422, 425, 475
pipelining de software, 553, 555, 558, 559
pipelining perfeito, 559
polimorfismo paramtrico, 153, 186
definio, 153
polimorfismo
Ponteiros de Registro de Ativao (ARPs
Activation Record Pointers)
definio, 13
ponteiros, 153
ambguos, 455
anlise esttica e, 410
atribuies, 410
e valores annimos, 319, 322
lista, 610, 619
manipulao, 153
na estrutura de indireo, 312
para inteiros, 153
recuperao de dados apontados (dereference)
registro de ativao, 13, 216, 242
segurana de tipo com, 154, 410
valores, 410
ponto de juno, 400, 416, 417, 418, 422, 461, 477
ps-dominncia, 456
ps-ordem reversa (RPO), 403, 404, 408, 414,
444, 487, 489, 544
no CFG reverso, 414
posicionamento de cdigo global, 378-380, 382,
388, 453
posicionamento de procedimento, 374, 384,
388-391
posicionamento mais antigo, 461
Ver tambm movimentao
de cdigo pouco ativo
PostScript, 2
precedncia
aritmtica, 80
fechamento, 36
preenchimento, 57, 117, 118, 122, 289, 290, 320
prettyprinter, 204
prioridade
escalonamento de lista para a frente, 545, 546
escalonamento de lista para trs, 545, 546
problema de fluxo de dados para trs, 404
procedimento chamado, 142, 217, 234-237, 242256, 257-259, 262, 264, 384-387,
397, 410, 411, 413, 414, 436, 438,
439, 469-471, 477
procedimentos de deciso para a substituio em
linha, 387
procedimentos folha, 246, 264, 387, 470, 471
procedimentos
aninhados, 236, 239, 241
argumentos implcitos, 334
argumentos, 464
chamado, 142, 217, 234-237, 410, 411, 413,
436, 439
chamador, 234, 235, 243-245, 257, 259
folha, 246, 264, 387, 470, 471
invocao, 351
ligao, 264, 268, 469, 589
posicionamento, 384, 388, 389, 390, 393,
453
sequncia de eplogo, 265, 266, 268, 469
sequncia de prlogo, 265, 267, 268, 333,
334, 469, 470
processador escalonado estaticamente, 532, 534,
540, 547
processadores superescalares, 532, 534
produes inteis, 130, 137
produes
inteis, 130
quebra, em duas partes, 175
programa no recursivo, 236
promoo de parmetro, 469-471
promoo, 471
propagao de constante condicional esparsa
(SCCP), 480
eficcia, 484
inicializao, 482
regras para multiplicaes, 483
propagao de constante interprocedimental, 354,
437, 440
propagao de constante simples esparsa (SSCP),
431, 432, 480 Ver tambm
propagao de constante
propagao de constante, 453, 468
condicional esparsa (SCCP Sparse
Conditional Constant
Propagation), 480
interprocedimental, 437, 438, 439, 440
simples esparsa (SSCP Sparse Simple
Constant Propagation), 431, 432,
437, 480
prottipo de funo, 157
R
rastreamento de load, 169, 176, 177
receptor, 247, 251, 254
recompilao, 392, 394
reconhecedores
DFA como, 46
implementao, 60, 64
recorrncia, 556
recurso direita, 86, 87, 88, 126, 128, 129, 187
associatividade, 128
profundidade de pilha, 127
recurso esquerda versus, 126
recurso esquerda, 128
associatividade esquerda, 128
associatividade, 128
eliminao, 85
profundidade de pilha, 127
recurso de cauda, 329, 330, 439, 469, 471
reduo de fora, 348, 468, 480, 484, 485, 486
definio, 346
substituio de teste de funo linear, 480,
491
reduo, 101, 119, 128, 491
redundncia parcial, 494
referncia ambgua
definio, 360
elemento de array, 593
referncias de estrutura, 319
arrays de estruturas, 320
valores annimos, 322
referncias no ambguas, 321
Ver tambm referncia ambgua
registrador fsico, 286
registrador de cdigo de condio, 303, 605
registradores de salvamentos do chamador, 266,
336, 470
registradores de uso geral
registradores
cdigo de condio, 519, 605
S
salto
destinos, 332
funes, 438
implementao de tabela, 439
scanner de correspondncia mais longa, 51
scanners controlados por tabela, 49
scanners, 19
codificados mo, 56
codificados diretamente, 53
controlados por tabela, 48, 49, 58
gramticas livres de contexto e, 73
scanning, 19, 64
segurana, 144
runtime, 144
seleo de instrues, 295
CISC e, 523
definio, 497
escalonamento e, 499
esquema de percurso em rvore, 504-506
RISC e, 523
por casamento de padres de rvore, 507
por meio de otimizao peephole, 517
semirreticulado, 431
sequncia de chamada, 224, 266-268
sequncia de classificao, 56
sequncia de escape, 29
sequncia de otimizao, 493
escolhendo, 492
sequncias de eplogo, 265, 268, 469
sequncias de ps-retorno, 265
sequncias de pr-chamada, 265
definio, 265
parmetros de chamada por valor, 296
deslocamentos (shift), operaes, 602
smbolo de incio, 73
smbolo-alvo, 72, 73, 101, 110, 117
smbolos no terminais, 72, 73, 91, 119, 171, 197
smbolos terminais, 72, 73, 82, 101
Action, tabela, 102, 104, 105, 133, 134
smbolos
entrada atual, 81
eof, 117
no terminais, 119, 171
terminais, 95, 101, 119, 159, 176
sintaxe
erros, 82, 84, 91, 92, 98
expressando, 70
sistemas de reescrita de baixo para cima, 509,
516
sistemas de tipos
definio
equivalncia de nomes, 154
equivalncia de tipo, 154
equivalncia estrutural, 154
expressividade, 145, 157
linguagem estaticamente tipada, 145, 156
linguagem fortemente tipada, 145, 148, 156, 185
linguagem fracamente tipada, 145, 156
linguagem no tipada, 145, 148, 156
linguagem tipada dinamicamente, 189
polimorfismo paramtrico, 153, 157, 186
polimorfismo, 153
regras de inferncia, 155, 156, 186
tipos bsicos, 149, 150, 157
T
tabelas de smbolos
com escopo, 223
discriminao de conjuntos mltiplos para,
621
tabelas hash bidimensionais, 629
tabelas hash, 220
bidimensionais, 629
endereamento aberto, 620, 623-625, 628
hashing aberto, 628, 629
implementao, 220, 620
tabelas com escopo, 227, 369
tabelas
hash, 9, 198, 220, 227
LR(1), 108
smbolo, 180, 183, 184, 188, 194, 219, 220
tbl, operao, 210, 606
tempo de vida, 15, 142, 180, 199, 216, 217, 221,
222, 241, 242, 244, 245, 247, 285,
291, 355, 423, 504, 539, 563, 566
Thompson, construo de, 36-38, 45, 47, 61, 62
thunks, 257
tipos bsicos, 149, 150, 157, 297, 307 Ver
tambm sistemas de tipos; tipo(s)
tipos compostos, 150
tipos construdos, 157
U
ltimo uso, 355, 491, 521, 522, 539, 544, 572
unies, 321
until, laos, 329
usos consistentes de tipo, 185
tipos de funo constante e, 185
tipos de funo desconhecidos e, 185
V
valor observvel, 362
valores annimos, 319, 322
valores mortos, 520
Ver tambm valores
valores sujos, 570
valores
acesso a parmetros, 312
ambguos, 291, 565, 593
annimos, 319, 322
atribuir registrador virtual, 291
baseados em pilha, 175
booleanos, 142, 150, 299, 305
derramamento, 533
escolha de nome, 193, 358
limpos, 570, 571
mantidos em registradores, 593
mapeamento para nomes, 211
mortos, 520
no ambguos, 459, 565
no nomeados, 285
W
while, laos, 329
Ver tambm laos