Você está na página 1de 659

Construindo Compiladores

Construindo Compiladores
Keith D. Cooper
Linda Torczon

Segunda edio
Traduo
Daniel Vieira
Reviso Tcnica
Edson Senne

Do original: Engineering a Compiler, 2nd edition


2014, Elsevier Editora Ltda.
Copyright 2012 Elsevier, Inc.
Traduo autorizada do idioma ingls da edio publicada por Morgan Kaufmann, um selo
editorial Elsevier.
Todos os direitos reservados e protegidos pela Lei n 9.610, de 19/02/1998.
Nenhuma parte deste livro, sem autorizao prvia por escrito da editora, poder ser reproduzida
ou transmitida sejam quais forem os meios empregados: eletrnicos, mecnicos, fotogrficos,
gravao ou quaisquer outros.
A imagem de capa: The Landing of the Ark, uma abbada cuja iconografia foi narrada, idealizada e desenhada por John Outram da John Outram Associates, Architects and City Planners,
Londres, Inglaterra. Para saber mais, visite www.johnoutram.com/rice.html.
Copidesque: Bel Ribeiro
Editorao Eletrnica: Thomson Digital
Reviso: Casa Editorial BBM
Elsevier Editora Ltda.
Conhecimento sem Fronteiras
Rua Sete de Setembro, 111 16 andar
20050-006 Centro Rio de Janeiro RJ Brasil
Rua Quintana, 753 8 andar
04569-011 Brooklin So Paulo SP
Servio de Atendimento ao Cliente
0800-026-5340
atendimento1@elsevier.com
ISBN: 978-85-352-5564-5
ISBN (verso digital): 978-85-352-5565-2
ISBN (edio original): 978-0-12-088478-0
Nota: Muito zelo e tcnica foram empregados na edio desta obra. No entanto, podem ocorrer
erros de digitao, impresso ou dvida conceitual. Em qualquer das hipteses, solicitamos a
comunicao ao nosso Servio de Atendimento ao Cliente, para que possamos esclarecer ou
encaminhar a questo.
Nem a editora nem o autor assumem qualquer responsabilidade por eventuais danos ou perdas
a pessoas ou bens, originados do uso desta publicao.

CIP-BRASIL. CATALOGAO NA PUBLICAO


SINDICATO NACIONAL DOS EDITORES DE LIVROS, RJ
C788c
2. ed.
Cooper, Keith D.
Construindo compiladores / Keith D. Cooper, Linda Torczon ; traduo Daniel Vieira. - 2.
ed. - Rio de Janeiro : Elsevier, 2014.
28 cm.
Traduo de: Engineering a compiler
ISBN 978-85-352-5564-5
1. Compiladores (Computadores). 2. Tecnologia. I. Torczon, Linda. II. Ttulo.
13-03454

CDD: 005.453
CDU: 004.4422

Elogios a Construindo Compiladores, Segunda Edio


Compiladores so uma rica rea de estudo, reunindo o mundo inteiro da cincia da computao
em uma construo elegante. Cooper e Torczon tiveram sucesso na criao de um guia muito
bem-vindo para esses sistemas de software, melhorando esta nova edio com lies claras e
com os detalhes que simplesmente preciso conhecer bem, tendo em vista o quadro geral o
tempo inteiro. Construindo Compiladores uma companhia valiosssima para qualquer um que
seja novo no assunto.
Michael D. Smith
Reitor da Faculdade de Artes e Cincias John H. Finley Jr., Professor de Engenharia
e Cincias Aplicadas da Universidade de Harvard

A Segunda Edio de Construindo Compiladores uma excelente introduo construo de


compiladores otimizadores modernos. Os autores a partir de uma vasta experincia na construo de compiladores, ajudam os alunos a compreender o quadro geral, enquanto, ao mesmo
tempo, os orientam por muitos detalhes importantes e sutis que precisam ser enfrentados para
a construo de um compilador otimizador eficaz. Particularmente, este livro contm a melhor
introduo ao formato de atribuio nica esttica que j vi.
Jeffery von Ronne
Professor Assistente
Departamento de Cincia da Computao
Universidade do Texas, San Antonio

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).

Dedicamos este volume a


nossos pais, que nos instilaram a sede pelo conhecimento e nos deram suporte enquanto
desenvolvamos as habilidades para seguir nossa busca pelo conhecimento;
nossos filhos, que nos mostraram novamente como pode ser maravilhoso o processo
de aprendizado e crescimento; e
nossos cnjuges, sem os quais este livro nunca teria sido escrito.

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.

Prefcio Segunda Edio


A prtica da construo de compiladores muda continuamente, em parte porque mudam os projetos
de processadores e sistemas. Por exemplo, quando comeamos a escrever Construindo Compiladores
em 1998, alguns de nossos colegas questionaram sobre a sabedoria de incluir um captulo sobre
escalonamento de instrues, pois a execuo fora de ordem ameaava tornar o escalonamento em
grande parte irrelevante. Hoje, com a segunda edio j pronta, o surgimento de processadores de
vrios ncleos (multicore) e o mpeto por mais ncleos tornou novamente atraente a execuo em
ordem em pipelines, pois seus impactos menores permitem que o projetista coloque mais ncleos
em um chip. O escalonamento de instrues continuar sendo importante para o futuro prximo.
Ao mesmo tempo, a comunidade de construo de compiladores continua a desenvolver novas
ideias e algoritmos, redescobrindo tcnicas mais antigas que eram eficazes, porm quase totalmente esquecidas. A pesquisa recente criou entusiasmo em torno do uso de grficos cordais na
alocao de registradores (ver Seo13.5.2). Este trabalho promete simplificar alguns aspectos
dos alocadores de colorao de grafo. O algoritmo de Brzozowski uma tcnica de minimizao
de autmatos finitos determinsticos (AFDs) datada do incio da dcada de 1960, mas no foi
ensinada em cursos de compiladores por muitos anos (ver Seo2.6.2). Ele oferece um caminho
fcil de uma implementao da construo de subconjuntos para outra que minimiza AFDs. Um
curso moderno sobre construo de compiladores poderia incluir essas duas ideias.
Como, ento, vamos estruturar um currculo em construo de compiladores de modo que
prepare os alunos para entrar nesse campo em constante mudana? Acreditamos que o curso
deve oferecer aos alunos o conjunto de habilidades bsicas que precisaro para construir novos
componentes de compilador e para modificar os existentes. Os alunos precisam entender tanto
os conceitos gerais, como a colaborao entre compilador, ligador (linker), carregador (loader)
e o sistema operacional incorporado em uma conveno de ligao, como os pequenos detalhes,
por exemplo, como o construtor de um compilador poderia reduzir o espao de cdigo agregado
usado pelo cdigo de salvamento de registrador a cada chamada de procedimento.

Mudanas na segunda edio


Esta segunda edio de Construindo Compiladores apresenta duas perspectivas: vises gerais dos
problemas na construo de compiladores e discusses detalhadas das alternativas algortmicas.
Na preparao desta edio, focamos na usabilidade do livro, tanto como um livro-texto quanto
como uma referncia para profissionais. Especificamente,
Melhoramos o fluxo de ideias para ajudar o aluno que l o livro de forma sequencial. As
introdues dos captulos explicam sua finalidade, estabelecem os principais conceitos e
oferecem uma viso geral de alto nvel do assunto ali tratado. Os exemplos foram modificados para oferecer continuidade entre os captulos. Alm disso, cada captulo comea
com um resumo e um conjunto de palavras-chave para auxiliar o usurio que trata este
livro como uma referncia.
Acrescentamos anlises de seo e perguntas de reviso ao final de cada seo principal.
As perguntas de reviso oferecem uma verificao rpida quanto a se o leitor entendeu os
principais pontos expostos na seo.
Movemos as definies dos principais termos para a margem adjacente ao pargrafo, onde
so definidas e discutidas pela primeira vez.
Revisamos o material sobre otimizao ao ponto de oferecer uma cobertura mais ampla
das possibilidades para um compilador otimizador.
Hoje, o desenvolvimento de compiladores se concentra na otimizao e gerao de cdigo.
muito mais provvel que um construtor de compiladores recm-contratado transfira um gerador
de cdigo para um novo processador ou modifique um passo de otimizao do que escreva um
scanner (analisador lxico) ou um parser (analisador sinttico). Um construtor de compiladores

xiv Prefcio Segunda Edio

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.

A arte e a cincia da compilao


A tradio da construo de compiladores inclui tanto incrveis histrias de sucesso sobre a aplicao
da teoria prtica e histrias deprimentes sobre os limites daquilo que podemos fazer. No lado do
sucesso, os scanners modernos so construdos pela aplicao da teoria de linguagens regulares
construo automtica de reconhecedores. Parsers LR utilizam as mesmas tcnicas para realizar
o reconhecimento de leques (handles) que controla um analisador sinttico do tipo empilha-reduz
(shift-reduce parser). A anlise de fluxo de dados aplica a teoria dos reticulados anlise de programas de maneiras inteligentes e teis. Os algoritmos de aproximao usados na gerao de cdigo
produzem boas solues para muitos exemplares de problemas verdadeiramente difceis.
Por outro lado, a construo de compiladores expe problemas complexos que desafiam as boas
solues. O back end de um compilador para um processador moderno aproxima a soluo para
dois ou mais problemas NP-completos interagentes (escalonamento de instrues, alocao de
registradores e, talvez, posicionamento de instruo e dados). Esses problemas NP-completos,
porm, parecem fceis perto de problemas como reassociao algbrica de expresses (ver,
por exemplo, a Figura7.1), que admite um nmero imenso de solues; para piorar as coisas
ainda mais, a soluo desejada depende do contexto tanto no compilador quanto no cdigo da
aplicao. Como o compilador aproxima as solues para tais problemas, enfrenta restries
no tempo de compilao e memria disponvel. Um bom compilador mistura engenhosamente
teoria, conhecimento prtico, engenharia e experincia.
Abra um compilador otimizador moderno e voc encontrar uma grande variedade de tcnicas.
Os compiladores usam buscas heursticas gulosas que exploram grandes espaos de soluo, e
autmatos finitos determinsticos que reconhecem palavras na entrada. Eles empregam algoritmos
de ponto fixo para raciocinar a respeito do comportamento do programa e provadores de teorema
simples e simplificadores algbricos para prever os valores das expresses. Compiladores tiram

Prefcio Segunda Edio xv

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.

Um aviso sobre exerccios de programao


Uma aula de construo de compiladores oferece a oportunidade de explorar as questes de
projeto de software no contexto de uma aplicao concreta uma aplicao cujas funes bsicas
so bem entendidas por qualquer aluno com a base para um curso de construo de compiladores.
Em muitas verses deste curso, os exerccios de programao desempenham um grande papel.
Ensinamos esta matria em verses nas quais os alunos constroem um compilador simples do
incio ao fim comeando com um scanner e parser gerados e terminando com um gerador de
cdigo para algum conjunto de instrues RISC simplificado. Ensinamos esta matria em verses
nas quais os alunos escrevem programas que tratam de problemas individuais bem delineados,

xvi Prefcio Segunda Edio

como alocao de registradores ou escalonamento de instrues. A escolha de exerccios de


programao depende bastante do papel que o curso desempenha no currculo ao seu redor.
Em algumas escolas, o curso de compilador serve como fechamento para veteranos, juntando
conceitos de muitos outros cursos em um projeto de concepo e implementao grande e prtico.
Os alunos nessa turma poderiam escrever um compilador completo para uma linguagem simples ou modificar um compilador de cdigo aberto para acrescentar suporte a um novo recurso
da linguagem ou um novo recurso arquitetural. Essa turma poderia estudar o material em uma
ordem linear, que segue de perto a organizao do texto.
Em outras escolas, essa experincia de fechamento ocorre em outros cursos ou de outras maneiras.
Em turmas assim, o professor poderia focar os exerccios de programao de forma mais estreita
sobre algoritmos e sua implementao, usando laboratrios como um alocador de registrador local ou
um passo de rebalanceamento de altura da rvore. Esse curso poderia fazer saltos no texto e ajustar a
ordem da apresentao para atender s necessidades dos laboratrios. Por exemplo, na Rice University,
normalmente usamos um alocador de registrador local simples como primeiro laboratrio; qualquer
aluno com experincia de programao em linguagem assembly entende os fundamentos do problema.
Esta estratgia, porm, expe os alunos ao material do Captulo13 antes que vejam o Captulo2.
Em qualquer cenrio, o curso deve utilizar material de outras matrias. Existem conexes bvias
com organizao de computadores, programao em linguagem assembly, sistemas operacionais,
arquitetura de computadores, algoritmos e linguagens formais. Embora as conexes da construo
de compiladores com outros cursos possa ser menos bvia, no so menos importantes. A cpia
de caracteres, como discutida no Captulo7, tem papel crtico no desempenho das aplicaes que
incluem protocolos de rede, servidores de arquivos e servidores Web. As tcnicas desenvolvidas no
Captulo2 para anlise lxica (scanning) possuem aplicaes que variam desde edio de textos
at filtragem de URL. O alocador ascendente (bottom-up) de registrador local no Captulo13
um primo do algoritmo timo de substituio de pgina off-line, MIN.

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

Viso geral da compilao


VISO GERAL DO CAPTULO
Compiladores so programas de computador que traduzem um programa escrito em
uma linguagem em um programa escrito em outra linguagem. Ao mesmo tempo, um
compilador um sistema de software de grande porte, com muitos componentes e
algoritmos internos e interaes complexas entre eles. Assim, o estudo da construo de
compiladores uma introduo s tcnicas para a traduo e o aperfeioamento
de programas, alm de um exerccio prtico em engenharia de software. Este captulo
fornece uma viso geral conceitual de todos os principais componentes de um compilador moderno.
Palavras-chave: Compilador, Interpretador, Traduo automtica

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.

2 CAPTULO 1 Viso geral da compilao

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.

Por que estudar construo de compilador?


Compiladores so programas grandes e complexos, e geralmente incluem centenas de
milhares, ou mesmo milhes, de linhas de cdigo, organizadas em mltiplos subsistemas e componentes. As vrias partes de um compilador interagem de maneira complexa. Decises de projeto tomadas para uma parte do compilador tm ramificaes
importantes para as outras. Assim, o projeto e a implementao de um compilador so
um exerccio substancial em engenharia de software.
Um bom compilador contm um microcosmo da cincia da computao. Faz uso
prtico de algoritmos gulosos (alocao de registradores), tcnicas de busca heurstica (agendamento de lista), algoritmos de grafos (eliminao de cdigo morto),
programao dinmica (seleo de instrues), autmatos finitos e autmatos de pilha
(anlises lxica e sinttica) e algoritmos de ponto fixo (anlise de fluxo de dados).
Lida com problemas, como alocao dinmica, sincronizao, nomeao, localidade,
gerenciamento da hierarquia de memria e escalonamento de pipeline. Poucos sistemas de software renem tantos componentes complexos e diversificados. Trabalhar
dentro de um compilador fornece experincia em engenharia de software, difcil de se
obter com sistemas menores, menos complicados.
Compiladores desempenham papel fundamental na atividade central da cincia
da computao: preparar problemas para serem solucionados por computador. A
maior parte do software compilada, e a exatido desse processo e a eficincia do
cdigo resultante tm impacto direto sobre nossa capacidade de construir sistemas
de grande porte. A maioria dos estudantes no se contenta em ler sobre essas ideias;
muitas delas devem ser implementadas para que sejam apreciadas. Assim, o estudo
da construo de compiladores componente importante de um curso de cincia da
computao.

Mquina Virtual
um simulador para um processador, ou seja, um interpretador para o conjunto de instrues da mquina.

4 CAPTULO 1 Viso geral da compilao

Compiladores demonstram a aplicao bem-sucedida da teoria para problemas


prticos. As ferramentas que automatizam a produo de analisadores lxicos (scanners) e analisadores sintticos (parsers) aplicam resultados da teoria de linguagem
formal. Estas mesmas ferramentas so utilizadas para pesquisa de texto, filtragem
de website, processamento de textos e interpretadores de linguagem de comandos. A
verificao de tipo e a anlise esttica aplicam resultados das teorias de reticulados
e dos nmeros, e outros ramos da matemtica, para compreender e melhorar programas. Geradores de cdigo utilizam algoritmos de correspondncia de padro de
rvore, parsing, programao dinmica e correspondncia de texto para automatizar
a seleo de instrues.
Ainda assim, alguns problemas que surgem na construo de compiladores so problemas abertos isto , as melhores solues atuais ainda tm espao para melhorias.
Tentativas de projeto de representaes de alto nvel, universais e intermedirias esbarram na complexidade. O mtodo dominante para escalonamento de instrues um
algoritmo guloso com vrias camadas de heurstica de desempate. Embora seja evidente
que os compiladores devem usar comutatividade e associatividade para melhorar o
cdigo, a maioria deles que tenta fazer isto, simplesmente reorganiza a expresso em
alguma ordem cannica.
Construir um compilador bem-sucedido exige conhecimentos em algoritmos, engenharia e planejamento. Bons compiladores aproximam as solues para problemas
difceis. Eles enfatizam eficincia em suas prprias implementaes e no cdigo que
geram. Tm estrutura de dados interna e representaes de conhecimento que expem
o nvel correto de detalhe suficientes para permitir otimizao forte, mas no para
forar o compilador a nadar em detalhes. A construo de compiladores rene ideias
e tcnicas de toda a extenso da cincia da computao, aplicando-as em um ambiente
restrito para resolver alguns problemas verdadeiramente difceis.

Princpios fundamentais da compilao


Compiladores so objetos grandes, complexos e cuidadosamente projetados. Embora
muitos problemas no seu projeto sejam passveis de mltiplas solues e interpretaes,
h dois princpios fundamentais que um construtor de compiladores deve ter em mente
o tempo todo. O primeiro inviolvel:
O compilador deve preservar o significado do programa a ser compilado.
Exatido uma questo fundamental na programao. O compilador deve preservar a
exatido, implementando fielmente o significado de seu programa de entrada. Este
princpio est no cerne do contrato social entre o construtor e o usurio do compilador.
Se o compilador puder ter liberdade com o significado, ento, por que simplesmente
no gerar um nop ou um return? Se uma traduo incorreta aceitvel, por que se
esforar para acert-la?
O segundo princpio a ser observado prtico:
O compilador deve melhorar o programa de entrada de alguma forma perceptvel.
Um compilador tradicional melhora o programa de entrada ao torn-lo executvel
diretamente em alguma mquina-alvo. Outros compiladores melhoram suas entradas
de diferentes maneiras. Por exemplo, tpic um programa que toma a especificao
para um desenho escrito na linguagem grfica pic e a converte para LATEX; a melhoria reside na maior disponibilidade e generalidade do LATEX. Um tradutor fonte
a fonte para C deve produzir cdigo que seja, de certa forma, melhor que o programa
de entrada; se no for, por que algum iria cham-lo?

1.2 Estrutura do compilador 5

1.2 ESTRUTURA DO COMPILADOR


Compilador um grande e complexo sistema de software. A comunidade tem construdo compiladores desde 1955, e, ao longo de todos esses anos, aprendemos muitas
lies sobre como estrutur-los. Retratamos, antes, um compilador como uma caixa
simples que traduz um programa-fonte em um programa-alvo. Na realidade, claro,
isto mais complexo que este quadro simplificado.
Como o modelo de caixa simples sugere, um compilador deve entender o programafonte que toma como entrada e mapear sua funcionalidade para a mquina-alvo. A
natureza distinta dessas duas tarefas d a entender uma diviso de trabalho e leva a um
projeto que decompe a compilao em duas partes principais: front end e back end.

O front end concentra-se na compreenso do programa na linguagem-fonte. J o back


end, no mapeamento de programas para a mquina-alvo. Essa separao de interesses
tem vrias implicaes importantes para o projeto e a implementao dos compiladores.
O front end deve codificar seu conhecimento do programa fonte em alguma estrutura
para ser usada mais tarde pelo back end. Essa representao intermediria (IR) torna-se
a representao definitiva do compilador para o cdigo que est sendo traduzido. Em
cada ponto da compilao, o compilador ter uma representao definitiva. Ele pode,
na verdade, utilizar vrias IRs diferentes medida que a compilao prossegue, mas,
em cada ponto, uma determinada representao ser a IR definitiva. Pensamos na IR
definitiva como a verso do programa passada entre fases independentes do compilador,
como a IR passada do front end para o back end no desenho anterior.
Em um compilador de duas fases, o front end deve garantir que o programa-fonte esteja
bem formado, e mapear aquele cdigo para a IR. J o back end, mapear o programa
em IR para o conjunto de instrues e os recursos finitos da mquina-alvo. Como este
ltimo s processa a IR criada pelo front end, pode assumir que a IR no contm erros
sintticos ou semnticos.

VOC PODE ESTAR ESTUDANDO EM UMA POCA INTERESSANTE


Esta uma poca interessante para projetar e implementar compiladores. Na dcada
de 1980, quase todos os compiladores eram sistemas grandes, monolticos. Usavam
como entrada um punhado de linguagens e produziam cdigo assembly gerando-o
para algum computador em particular. O cdigo assembly era colado junto com
o cdigo produzido por outras compilaes incluindo bibliotecas de sistema e
de aplicao para formar um executvel. Este era armazenado em disco e, no
momento apropriado, o cdigo final era movido do disco para a memria principal e
executado.
Hoje, a tecnologia do compilador est sendo aplicada em muitos ambientes
diferentes. medida que os computadores encontram aplicaes em diversos lugares,
os compiladores precisam lidar com novas e diferentes restries. Velocidade no
mais o nico critrio para julgar o cdigo compilado. Hoje, este cdigo pode ser
julgado com base no quanto pequeno, em quanta energia consome, quanto ele
comprime ou quantas faltas de pgina gera quando executado.

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).

6 CAPTULO 1 Viso geral da compilao

Ao mesmo tempo, as tcnicas de compilao tm fugido do sistema monoltico


dos anos 1980. E aparecendo em muitos lugares novos. Compiladores Java tomam
programas parcialmente compilados (no formato Java bytecode) e os traduzem em
cdigo nativo para a mquina-alvo. Nesse ambiente, o sucesso exige que a soma do
tempo de compilao mais o de execuo deve ser inferior ao custo da interpretao.
Tcnicas para analisar programas inteiros esto passando de tempo de compilao
para de montagem, no qual o montador pode analisar o cdigo assembly para o
aplicativo inteiro e utilizar este conhecimento para melhorar o programa. Finalmente,
compiladores esto sendo invocados em tempo de execuo para gerar cdigo
personalizado que tira proveito de fatos que no podem ser conhecidos mais cedo. Se
o tempo de compilao pode ser mantido pequeno e os benefcios so grandes, esta
estratgia tende a produzir melhorias visveis.

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.

Finalmente, a estrutura de duas fases pode simplificar o processo de retargeting do


compilador. Podemos facilmente imaginar a construo de vrios back ends para um
nico front end, a fim de produzir compiladores que aceitam a mesma linguagem, mas
visam diferentes mquinas. De modo semelhante, podemos imaginar front ends para
diferentes linguagens produzindo a mesma IR e usando um back end comum. Ambos
os cenrios consideram que uma IR pode atender a vrias combinaes de fonte e alvo;
na prtica, tanto os detalhes especficos da linguagem quanto os detalhes especficos
da mquina normalmente encontram seu lugar na IR.

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 otimizador um transformador IR-para-IR que tenta melhorar o programa em IR


de alguma maneira. (Observe que esses transformadores, por si ss, so compiladores, de acordo com nossa definio na Seo 1.1.) Ele pode fazer uma ou mais passagens pela IR, analis-la e reescrev-la. Pode, tambm, reescrever a IR de um modo
que provavelmente produza um programa-alvo mais rpido, ou menor, pelo back end.
E, ainda, ter outros objetivos, como um programa que produz menos faltas de pgina
ou usa menos energia.
Conceitualmente, a estrutura em trs fases representa o compilador otimizante
clssico. Na prtica, cada fase dividida internamente em uma srie de passos.

1.2 Estrutura do compilador 7

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.

FIGURA 1.1 Estrutura de um compilador tpico.

8 CAPTULO 1 Viso geral da compilao

1.3 VISO GERAL DA TRADUO


Para traduzir o cdigo escrito em uma linguagem de programao para cdigo adequado
execuo em alguma mquina-alvo, um compilador passa por muitas etapas. Para
tornar esse processo abstrato mais concreto, considere as etapas necessrias para gerar
um cdigo executvel para a seguinte expresso
a a 2 b c d

onde a, b, c e d so variveis, indica uma atribuio, eo operador para


multiplicao. Nas subsees seguintes, rastrearemos o caminho que um compilador
segue para transformar esta expresso simples em cdigo executvel.

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.

1.3.1 Front end


Antes que o compilador possa traduzir uma expresso para cdigo executvel da
mquina-alvo, precisa entender tanto sua forma, ou sintaxe, quanto seu significado,
ou semntica. O front end determina se o cdigo de entrada est bem formado nestes
termos. Se descobrir que o cdigo vlido, ele cria uma representao deste cdigo na
representao intermediria do compilador; se no, informa ao usurio com mensagens
de erro de diagnstico para identificar os problemas com o cdigo.

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.

1.3 Viso geral da traduo 9

Matematicamente, a linguagem de origem um conjunto, normalmente infinito, de


strings definido por algum conjunto finito de regras, chamado gramtica. Dois passos separados no front end, chamados scanner e parser, determinam se o cdigo de
entrada ou no, de fato, um membro do conjunto de programas vlidos definidos
pela gramtica.
As gramticas da linguagem de programao normalmente referem-se a palavras
com base em suas classes gramaticais, s vezes chamadas categorias sintticas.
Basear as regras gramaticais em categorias ou classes gramaticais permite que
uma nica regra descreva muitas sentenas. Por exemplo, em portugus, muitas
sentenas tm a forma:
Sentena Sujeito verbo Objeto marca de fim
onde verbo e marca de fim so classes, e Sentena, Sujeito e Objeto so variveis
sintticas. Sentena representa qualquer string com a forma descrita por esta regra. O
smbolo indica deriva e significa que uma ocorrncia do lado direito pode ser
simplificada para a varivel sinttica do lado esquerdo.
Considere uma sentena como Compiladores so objetos construdos. O primeiro
passo para entender a sintaxe desta sentena identificar palavras distintas no programa
de entrada e classificar cada palavra com uma classe gramatical. Em um compilador,
esta tarefa fica com um passo chamado scanner (ou analisador lxico). O scanner apanha um fluxo de caracteres e o converte para um fluxo de palavras classificadas ou
seja, pares na forma (c, g), onde c a classe gramatical e g sua grafia. Um scanner
converteria a sentena do exemplo no seguinte fluxo de palavras classificadas:
(substantivo,Compiladores), (verbo,so), (substantivo,objetos),
(adjetivo,construdos), (marca de fim,.)
Na prtica, a grafia real das palavras poderia ser armazenada em uma tabela hash e
representada nos pares como um ndice inteiro para simplificar testes de igualdade. O
Captulo2 explora a teoria e a prtica da construo do scanner.
No prximo passo, o compilador tenta corresponder o fluxo de palavras categorizadas
s regras que especificam a sintaxe da linguagem de entrada. Por exemplo, um conhecimento funcional do portugus poderia incluir as seguintes regras gramaticais:
1
2
3
4
5
6

Sentena
Sujeito
Sujeito
Objeto
Objeto
Modificador
.. .

Sujeito verbo Objeto marca de fim


substantivo
Modificador substantivo
substantivo
Modificador substantivo
adjetivo

Por inspeo, podemos descobrir a seguinte derivao para nossa sentena de


exemplo:
Regra

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

Scanner (analisador lxico)


O passo do compilador que converte uma string de
caracteres em um fluxo de palavras.

10 CAPTULO 1 Viso geral da compilao

A derivao comea com a varivel sinttica Sentena. A cada passo, reescreve um


termo na sentena de prottipo, substituindo-o por um lado direito que pode ser derivado
desta regra. O primeiro passo usa a Regra 1 para substituir Sentena. O segundo, a
Regra 2 para substituir Sujeito. O terceiro substitui Objeto usando a Regra 5, enquanto
o passo final reescreve Modificador com adjetivo de acordo com a Regra 6. Neste
ponto, a sentena de prottipo gerada pela derivao corresponde ao fluxo de palavras
categorizadas produzidas pelo scanner.
Parser (analisador sinttico)
O passo do compilador que determina se o fluxo de
entrada uma sentena na linguagem-fonte.

A derivao prova que a sentena Compiladores so objetos construdos. pertence


linguagem descrita pelas Regras de 1 a 6. Ela est gramaticalmente correta. O processo
de encontrar automaticamente as derivaes chamado parsing (ou anlise sinttica). O
Captulo3 apresenta as tcnicas que os compiladores usam para analisar sintaticamente
o programa de entrada.
Uma sentena gramaticalmente correta pode no ter significado. Por exemplo, Pedras so vegetais verdes. tem as mesmas classes gramaticais na mesma ordem de
Compiladores so objetos construdos., mas no tem um significado racional. Para
entender a diferena entre essas duas sentenas, preciso ter conhecimento contextual
sobre os sistemas de software, pedras e vegetais.
Os modelos semnticos que os compiladores usam para raciocinar a respeito das
linguagens de programao so mais simples do que aqueles necessrios para entender
a linguagem natural. Compiladores montam modelos matemticos que detectam tipos
especficos de inconsistncia em um programa. Compiladores verificam a consistncia
de tipo. Por exemplo, a expresso:
a a 2 b c d

Verificao de tipo
O passo do compilador que verifica a consistncia de
tipo do uso de nomes no programa de entrada.

poderia ser sintaticamente bem formada, mas se b e d forem strings de caracteres, a


sentena poderia ser invlida. Os compiladores tambm verificam a consistncia de
nmero em situaes especficas; por exemplo, uma referncia de array deve ter o
mesmo nmero de subscritos do que o nmero de dimenses (rank) declarado do array
e uma chamada de procedimento deve especificar o mesmo nmero de argumentos da
definio do procedimento. O Captulo4 explora algumas das questes que surgem na
verificao de tipo e na elaborao semntica baseadas em compilador.

Representaes intermedirias (IRs)


t0 a2
t1 t0b
t2 t1c
t3 t2d
a t3

O ltimo aspecto tratado no front end de um compilador a gerao de um formato


de IR do cdigo. Compiladores usam diversos tipos diferentes de IR, dependendo da linguagem-fonte, da linguagem-alvo e das transformaes especficas que
o compilador aplica. Algumas IRs representam o programa como um grafo, outras
assemelham-se a um programa em cdigo assembly sequencial. O cdigo ao lado
mostra como nossa expresso de exemplo ficaria em uma IR sequencial de baixo
nvel. O Captulo5 apresenta uma viso geral da variedade de tipos de IR que os
compiladores utilizam.
Para cada construo na linguagem-fonte o compilador precisa de uma estratgia para
como implement-la no formato IR do cdigo. Escolhas especficas afetam a capacidade
do compilador de transformar e melhorar o cdigo. Assim, usamos dois captulos para
analisar as questes que surgem na gerao da IR para construes do cdigo-fonte. As
ligaes de procedimento so, ao mesmo tempo, uma fonte de ineficincia no cdigo
final e a cola fundamental que junta as partes de diferentes arquivos-fonte em uma
aplicao. Assim, dedicamos o Captulo6 para as questes em torno das chamadas de
procedimento. O Captulo7 apresenta estratgias de implementao para a maioria
das outras construes de linguagem de programao.

1.3 Viso geral da traduo 11

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

FIGURA 1.2 O contexto faz diferena.

12 CAPTULO 1 Viso geral da compilao

Anlise de fluxo de dados


Forma de raciocnio em tempo de compilao sobre o
fluxo de valores em runtime.

de equaes simultneas que so derivadas da estrutura do cdigo sendo traduzido.


Anlise de dependncia usa testes da teoria dos nmeros para raciocinar sobre os
valores que podem ser assumidos por expresses de subscrito. usada para remover
a ambiguidade das referncias a elementos de array. O Captulo9 apresenta uma viso
detalhada da anlise de fluxo de dados e sua aplicao, junto com a construo do formato
de atribuio nica esttica, uma IR que codifica informaes sobre o fluxo de valores
e controle diretamente na IR.

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.

1.3.3 Back end


O back end do compilador atravessa o formato IR do cdigo e emite cdigo para
a mquina-alvo; seleciona as operaes da mquina-alvo para implementar cada
operao da IR; escolhe uma ordem em que as operaes sero executadas de modo
mais eficiente; decide quais valores residiro nos registradores e quais na memria,
inserindo cdigo para impor essas decises.

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

add r1,r2 r3// exemplo de instruo


O separador, , precede a lista de alvos. um lembrete visual de que a informao
flui da esquerda para a direita. Em particular, ele tira a ambiguidade de casos nos
quais uma pessoa, lendo o texto em nvel de assembly, pode facilmente confundir
operandos e alvos. (Ver loadAI e storeAI na tabela a seguir.)

1.3 Viso geral da traduo 13

O exemplo na Figura1.3 s utiliza quatro operaes ILOC:


Operao ILOC
loadAI
loadI
mult
storeAI

r1,c2 r3
c1
r2
r1,r2 r3
r1
r2,c3

Significado
MEMORY (r1 +c2) r3
c1 r2
r1r2 r3
r1 MEMORY (r2 +c3)

O Apndice A contm uma descrio mais detalhada da ILOC. Os exemplos utilizam


rarp consistentemente como um registrador que contm o incio do armazenamento
de dados para o procedimento atual, tambm conhecido como ponteiro de registro de
ativao.

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

FIGURA 1.3 Cdigo ILOC para a a2bcd.

Registrador virtual
Nome de registrador simblico que o compilador usa
para indicar que um valor pode ser armazenado em um
registrador.

14 CAPTULO 1 Viso geral da compilao

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

Esta sequncia usa trs registradores, em vez de seis.


Minimizar o uso de registradores pode ser contraprodutivo. Se, por exemplo, qualquer
um dos valores nomeados, a, b, c ou d, j estiverem em registradores, o cdigo dever
referenci-los diretamente. Se todos estiverem em registradores, a sequncia poderia ser
implementada de modo que no exigisse registradores adicionais. Como alternativa, se
alguma expresso prxima tambm calculasse a2, poderia ser melhor preservar este
valor em um registrador do que recalcul-lo mais tarde. Essa otimizao aumentaria
a demanda por registradores, mas eliminaria uma instruo mais tarde. O Captulo13
explora os problemas que surgem na alocao de registradores e as tcnicas que os
construtores de compilador utilizam para solucion-los.

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.

1.3 Viso geral da traduo 15

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

CONSTRUO DE COMPILADORES ENGENHARIA


Um compilador tpico tem uma srie de passos que, juntos, traduzem o cdigo de
alguma linguagem-fonte para alguma linguagem-alvo. Ao longo do caminho, ele usa
dezenas de algoritmos e estruturas de dados. O construtor de compiladores precisa
selecionar, para cada etapa no processo, uma soluo apropriada.
Um compilador bem-sucedido executado um nmero inimaginvel de vezes.
Considere o nmero total de vezes que o compilador GCC foi executado. Durante
o tempo de vida do GCC, at mesmo pequenas ineficincias acrescentam uma
quantidade de tempo significativa. As economias devidas a um bom projeto e

16 CAPTULO 1 Viso geral da compilao

implementao se acumulam com o tempo. Assim, o construtor de compiladores


precisa prestar ateno aos custos do tempo de compilao, como a complexidade
assinttica dos algoritmos, o tempo de execuo real da implementao e o espao
usado pelas estruturas de dados. E, ainda, deve ter em mente um oramento de
quanto tempo o compilador gastar em suas vrias tarefas.
Por exemplo, as anlises lxica e sinttica so dois problemas para os quais existem
muitos algoritmos eficientes. Os scanners reconhecem e classificam as palavras
em tempo proporcional ao nmero de caracteres no programa de entrada.
Para uma linguagem de programao tpica, um analisador sinttico pode criar
derivaes em tempo proporcional ao tamanho da derivao. (A estrutura restrita
das linguagens de programao possibilitam a anlise sinttica eficiente.) Como
existem tcnicas eficientes e eficazes para anlises lxica e sinttica, o construtor
de compilador deve esperar gastar apenas uma frao do tempo de compilao
nessas tarefas.
Ao contrrio, a otimizao e a gerao de cdigo contm vrios problemas que
exigem mais tempo. Muitos dos algoritmos que examinaremos para anlise e
otimizao do programa tero complexidades maiores do que O(n). Assim, a escolha
do algoritmo no otimizador e gerador de cdigo tem impacto maior no tempo de
compilao do que no front end do compilador. O construtor de compilador pode
precisar negociar preciso de anlise e eficcia de otimizao contra aumentos
no tempo de compilao. E, assim, deve tomar essas decises consciente e
cuidadosamente.

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.

Interaes entre os componentes de gerao de cdigo


A maior parte dos problemas realmente difceis que ocorrem na compilao surge
durante a gerao de cdigo. Para tornar as coisas mais complexas, esses problemas
interagem entre si. Por exemplo, o escalonamento de instrues move operaes
load para longe das operaes aritmticas que dependem delas. Isto pode aumentar o
perodo sobre o qual os valores so necessrios e, de modo correspondente, aumentar
o nmero de registradores necessrios durante esse perodo. De forma semelhante,
a atribuio de valores particulares para registradores especficos pode restringir o
escalonamento de instrues criando uma dependncia falsa entre duas operaes.
(A segunda operao no pode ser escalonada at que a primeira termine, embora
os valores no registrador comum sejam independentes. Renomear os valores pode
eliminar essa falsa dependncia, ainda que custa de usar mais registradores.)

1.4 RESUMO E PERSPECTIVA


A construo de compiladores uma tarefa complexa. Um bom compilador combina
ideias da teoria de linguagens formais, do estudo de algoritmos, da inteligncia artificial,
do projeto de sistemas, da arquitetura de computadores e da teoria de linguagens de programao, aplicando-as ao problema de traduzir um programa. Um compilador rene
algoritmos gulosos, tcnicas heursticas, algoritmos de grafo, programao dinmica,
autmatos finitos determinsticos (DFAs) e no determinsticos (NFAs), algoritmos
de ponto fixo, sincronizao e localidade, alocao e nomeao, e gerenciamento
de pipeline. Muitos dos problemas encarados pelos compiladores so muito difceis

1.4 Resumo e perspectiva 17

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

18 CAPTULO 1 Viso geral da compilao

compiladores a controlarem diretamente os pipelines e as latncias de memria. Ao


final da dcada, os compiladores enfrentavam desafios que variavam desde mltiplas
unidades funcionais at longas latncias de memria e gerao de cdigo paralelo. A
estrutura e a organizao dos compiladores RISC dos anos 1980 provaram ser flexveis
o suficiente para esses novos desafios, de modo que os pesquisadores criaram novas
passagens para inserir nos otimizadores e geradores de cdigo de seus compiladores.
Embora os sistemas Java utilizem uma mistura de compilao e interpretao [63,
279], esta no a primeira linguagem a empregar essa mistura. Sistemas Lisp h muito
tempo tm includo compiladores de cdigo nativo e esquemas de implementao de
mquina virtual [266, 324]. O sistema Smalltalk-80 usava uma distribuio de bytecode
e uma mquina virtual [233]; vrias implementaes acrescentaram compiladores JIT
(Just-In-Time) [126].

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.

2.1.1 Roteiro conceitual


Este captulo descreve as ferramentas matemticas e as tcnicas de programao
comumente utilizadas para construir scanners tanto aqueles gerados quanto os
codificados mo. O captulo comea, na Seo 2.2, introduzindo um modelo para
reconhecedores, programas que identificam palavras em um fluxo de caracteres. A
Seo 2.3 descreve as expresses regulares, uma notao formal para especificar
sintaxe. Na Seo 2.4, mostramos um conjunto de construes para converter uma
expresso regular em um reconhecedor. Finalmente, na Seo 2.5, apresentamos trs
diferentes maneiras de implementar um scanner: controlado por tabela, codificado
diretamente e codificado mo.

Reconhecedor
Programa que identifica palavras especficas
em um fluxo de caracteres.

*Analisador lxico.

19

20 CAPTULO 2 Scanners

Scanners gerados e codificados mo contam com as mesmas tcnicas bsicas. Embora


a maioria dos livros-texto e cursos defendam o uso dos primeiros, a maioria dos
compiladores comerciais e compiladores de cdigo aberto (open-source) utiliza os
segundos. Um scanner codificado mo pode ser mais rpido do que um gerado,
pois a implementao pode remover uma parte do overhead que no pode ser evitado
neste ltimo. Como os scanners so simples e mudam com pouca frequncia, muitos
construtores de compilador consideram que o ganho de desempenho de um scanner
codificado mo supera a convenincia da gerao automatizada de scanner. Exploraremos as duas alternativas.

2.1.2 Viso geral


Categoria sinttica
Classificao das palavras de acordo com seu uso
gramatical.
Microssintaxe
A estrutura lxica de uma linguagem.

O scanner de um compilador l um fluxo de entrada, que consiste em caracteres, e


produz um fluxo de sada que contm palavras, cada uma rotulada com sua categoria
sinttica equivalente uma classe gramatical da palavra na linguagem natural.
Para conseguir esta agregao e classificao, o scanner aplica um conjunto de regras
que descrevem a estrutura lxica da linguagem de programao de entrada, s vezes
chamada de sua microssintaxe, que, em uma linguagem de programao, especifica
como agrupar caracteres em palavras e, reciprocamente, como separar palavras que
estejam juntas. (No contexto da anlise lxica, consideramos os sinais de pontuao e
outros smbolos tambm como palavras.)
As linguagens ocidentais, como ingls e portugus, possuem uma microssintaxe simples. Letras alfabticas adjacentes so agrupadas, da esquerda para a direita, para formar
uma palavra. Um espao em branco termina uma palavra, assim como a maioria dos
smbolos no alfabticos. (O algoritmo de montagem de palavra pode tratar um hfen
no meio de uma palavra como se fosse um caractere alfabtico.) Quando um grupo
de caracteres tiver sido agregado para formar uma palavra em potencial, o algoritmo de
montagem de palavras pode determinar sua validade com uma pesquisa de dicionrio.
A maioria das linguagens de programao tambm possui uma microssintaxe simples. Os caracteres so agregados em palavras. Na maioria das linguagens, espaos
em branco e sinais de pontuao terminam uma palavra. Por exemplo, Algol e seus
descendentes definem um identificador como um nico caractere alfabtico seguido
por zero ou mais caracteres alfanumricos. O identificador termina com o primeiro
caractere no alfanumrico. Assim, fee e f1e so identificadores vlidos, mas 12fum
no. Observe que o conjunto de palavras vlidas especificado por regras, e no pela
enumerao em um dicionrio.

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)

2.2 Reconhecendo palavras 21

por caractere, de modo que so executados em um tempo proporcional ao nmero de


caracteres no fluxo de entrada.

2.2 RECONHECENDO PALAVRAS


A explicao mais simples de um algoritmo para reconhecer palavras normalmente
uma formulao de um caractere por vez. A estrutura do cdigo pode oferecer alguma
ideia para o problema bsico. Considere o problema de reconhecer a palavra-chave
new. Considerando a presena de uma rotina NextChar que retorna o prximo caractere, o cdigo poderia ser semelhante ao fragmento mostrado na Figura2.1. O cdigo
testa n seguido por e seguido por w. A cada passo, a falha ao reconhecer o caractere
apropriado faz que o cdigo rejeite a string e tente outra coisa. Se a nica finalidade do
programa fosse reconhecer a palavra new, ento ele deveria imprimir uma mensagem
de erro ou retornar com falha. Como os scanners raramente reconhecem apenas uma
palavra, deixaremos esse caminho de erro deliberadamente vago neste ponto.
O fragmento de cdigo realiza um teste por caractere. Podemos representar este
fragmento usando o diagrama de transio simples mostrado direita do cdigo. O
diagrama de transio representa um reconhecedor. Cada crculo representa um estado
abstrato na computao. Cada estado rotulado por convenincia.
O estado inicial, ou estado de partida, s0. Sempre rotularemos assim este estado. O
estado s3 um de aceitao; o reconhecedor alcana s3 somente quando a entrada
new. Estados de aceitao so desenhados com crculos duplos, como mostramos ao
lado. As setas representam transies de um estado para outro com base no caractere de entrada. Se o reconhecedor comea em s0 e l os caracteres n, e e w, as transies nos levam para s3. O que acontece com qualquer outra entrada, como n, o e t?

FIGURA 2.1 Fragmento de cdigo para reconhecer new.

22 CAPTULO 2 Scanners

O n leva o reconhecedor a s 1. O o no combina com a aresta saindo de s 1, de


modo que a palavra de entrada no new. No cdigo, os casos que no correspondem a new tentam outra coisa. No reconhecedor, podemos pensar nesta ao
como uma transio para um estado de erro. Quando desenhamos o diagrama de
transio de um reconhecedor, normalmente omitimos transies para o estado de
erro. Cada estado tem uma transio para o estado de erro em cada entrada no
especificada.
O uso deste mesmo mtodo para criar um reconhecedor para while produziria o
seguinte diagrama de transio:

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.

2.2 Reconhecendo palavras 23

O estado s0 tem transies para n e w. O reconhecedor tem trs estados de aceitao,


s3, s5 e s10. Se qualquer estado encontrar um caractere de entrada que no corresponde
a uma de suas transies, o reconhecedor passa para um estado de erro.

2.2.1 Formalismo para os reconhecedores


Os diagramas de transio servem como abstraes do cdigo que seria exigido
para implement-los. Tambm podem ser vistos como objetos matemticos formais,
chamados autmatos finitos, que especificam os reconhecedores. Formalmente, um
autmato finito (FA Finite Automaton) uma 5-tupla (S, O, d, s0, SA), onde
S o conjunto finito de estados no reconhecedor, juntamente com um estado de erro se.
O o alfabeto finito usado pelo reconhecedor. Normalmente, O a unio dos
rtulos das arestas no diagrama de transio.
d(s, c) a funo de transio do reconhecedor. Ela mapeia cada estado s S
e cada caractere c O a algum estado seguinte. No estado si com caractere de
entrada c, o FA faz a transio si c (si , c) .
s0 S o estado inicial designado.
SA o conjunto de estados de aceitao, SA S. Cada estado em SA aparece
como um duplo crculo no diagrama de transio.
Como exemplo, podemos converter o FA para new ou not ou while no formalismo a
seguir:

Para todas as outras combinaes do estado si e caractere de entrada c, definimos d(si,


c)=se, onde se o estado de erro designado. Esta quntupla equivalente ao diagrama
de transio; tendo um, podemos facilmente recriar o outro. O diagrama de transio
uma imagem do FA correspondente.
Um FA aceita uma string x se e somente se, comeando em s0, a sequncia de caracteres
na string lev-lo por uma srie de transies que o deixe em um estado de aceitao
quando a string inteira tiver sido consumida. Isto corresponde nossa intuio para o
diagrama de transio. Para a string new, nosso exemplo de reconhecedor passa pelas
n
w
s3 . Como s 3 S A, e no resta mais entrada
transies s0 s1 , s1 e s2 e s2
alguma, o FA aceita new. Para a string de entrada nut, o comportamento diferente.
No n, o FA segue s0 n s1 . No u, faz a transio s1 u se . Quando ele entra em se, a
permanece at esgotar o fluxo de entrada.
Mais formalmente, se a string x composta dos caracteres x1 x2 x3. .. xn, ento o FA (S,
, d, s0, SA) aceita x, se e somente se

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

Intuitivamente, esta definio corresponde a uma aplicao repetida de d a um par


composto de algum estado s S e um smbolo de entrada xi. O caso bsico, d(s0, x1),
representa a transio inicial do FA a partir do estado inicial s0 no caractere x1. O estado produzido por d(s0, x1) ento usado como entrada, junto com x2, para d produzir
o prximo estado, e assim por diante, at que toda a entrada tenha sido consumida.
O resultado da aplicao final de d , novamente, um estado. Se este for um estado
aceitvel, ento o FA aceita x1 x2 x3 xn.
Dois outros casos so possveis. O FA poderia encontrar um erro enquanto processa a
string, ou seja, algum caractere xj poderia lev-lo ao estado de erro se. Esta condio
indica um erro lxico; a string x1 x2 x3. .. xj no um prefixo vlido para qualquer
palavra na linguagem aceita pelo FA. O FA pode tambm descobrir um erro esgotando sua entrada e terminando em um estado de no aceitao diferente de se.
Neste caso, a string de entrada um prefixo apropriado de alguma palavra aceita
pelo FA. Novamente, isto indica um erro. Qualquer um desses tipos de erro deve ser
relatado ao usurio final.
De qualquer forma, observe que o FA segue uma transio para cada caractere de
entrada. Supondo que podemos implement-lo de modo eficiente, devemos esperar
que o reconhecedor seja executado em um tempo proporcional ao tamanho da string
de entrada.

2.2.2 Reconhecendo palavras mais complexas


O modelo caractere a caractere mostrado no reconhecedor original para not estende-se
facilmente para lidar com quaisquer colees de palavras totalmente especificadas.
Como podemos reconhecer um nmero com este tipo de reconhecedor? Um nmero
especfico, como 113.4, fcil.

Para ser til, porm, precisamos de um diagrama de transio (e o fragmento de cdigo


correspondente) que possa reconhecer qualquer nmero. Por questo de simplicidade,
limitaremos a discusso a inteiros sem sinal. Em geral, um inteiro pode ser zero ou uma
srie de um ou mais dgitos, na qual o primeiro dgito de um a nove, e os subsequentes
so de zero a nove. (Esta definio no aceita zeros iniciais.) Como desenharamos um
diagrama de transio para esta definio?

A transio s0 0 s1 trata do caso do zero. O outro caminho, de s0 a s2 a s3, e assim


sucessivamente, trata do caso para um inteiro maior que zero; porm, apresenta vrios
problemas. Primeiro, no termina, violando a estipulao de que S finito. Segundo,
todos os estados no caminho comeando com s2 so equivalentes, ou seja, tm os
mesmos rtulos em suas transies de sada e todos so estados de aceitao.

2.2 Reconhecendo palavras 25

FIGURA 2.2 Um reconhecedor para inteiros sem sinal.

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

Mudando a tabela, permitimos a mesma estrutura de cdigo bsica para implementar


outros reconhecedores. Observe que esta tabela tem ampla oportunidade para compresso. As colunas para os dgitos de 1 a 9 so idnticas, de modo que poderiam ser

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, }.

2.3 EXPRESSES REGULARES


O conjunto de palavras aceitas por um autmato finito, F, forma uma linguagem,
indicada por L(F). O diagrama de transio do FA especifica, em detalhes precisos,
esta linguagem. Porm, esta no uma especificao que os humanos achem intuitiva.
Para qualquer FA, tambm podemos descrever sua linguagem usando uma notao
chamada expresso regular (RE Regular Expression). A linguagem descrita por
uma RE chamada linguagem regular.
Expresses regulares so equivalentes aos FAs descritos na seo anterior. (Assim
provaremos com uma construo na Seo 2.4.) Reconhecedores simples possuem
especificaes RE simples.
A linguagem consistindo de uma nica palavra new pode ser descrita por uma
RE escrita como new. Escrever dois caracteres um ao lado do outro implica que
devero aparecer nessa ordem.

2.3 Expresses regulares 27

A linguagem consistindo de duas palavras new ou while pode ser escrita


como new ou while. Para evitar possveis erros de interpretao, escrevemos isto
usando o smbolo | para indicar ou. Assim, escrevemos a RE como new | while.
A linguagem consistindo de new ou not pode ser escrita como new | not. Outras
REs so possveis, como n(ew | ot). Ambas as REs especificam o mesmo par de
palavras. A RE n(ew | ot) sugere a estrutura do FA que desenhamos anteriormente para essas duas palavras.

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:

De modo semelhante, as palavras-chave possuem REs simples.

Para modelar construes mais complexas, como inteiros ou identificadores, precisamos


de uma notao que possa capturar a essncia da aresta cclica em um FA.
O FA para um inteiro sem sinal, mostrado ao lado, tem trs estados: um inicial s0, um de
aceitao s1 exclusivamente para o inteiro zero, e outro de aceitao s2 para todos os outros
inteiros. A chave para o poder deste FA a transio de s2 de volta para si mesmo, que ocorre
a cada dgito adicional. O estado s2 traz a especificao de volta para si mesmo, criando uma
regra para derivar um novo inteiro sem sinal a partir de um existente: acrescentar outro dgito
direita do nmero existente. Outra forma de declarar esta regra : um inteiro sem sinal
um zero ou um dgito no zero seguido por zero ou mais dgitos. Para capturar a essncia
deste FA, precisamos de uma notao para esta noo de zero ou mais ocorrncias de uma
RE. Para a RE x, escrevemos isto como x*, com o significado zero ou mais ocorrncias de
x. Chamamos o operador * de fechamento de Kleene, ou apenas fechamento, para abreviar.
Usando o operador de fechamento, podemos escrever uma RE para este FA:

2.3.1 Formalizando a notao


Para trabalhar com expresses regulares de uma forma rigorosa, precisamos defini-las mais
formalmente. Uma RE descreve um conjunto de strings sobre os caracteres contidos em
algum alfabeto, O, aumentado com um caractere que representa a string vazia. Chamamos
o conjunto de strings de linguagem. Para determinada RE, r, indicamos a linguagem que
ela especifica como L(r). Uma RE construda a partir de trs operaes bsicas:
1. Alternao A alternao, ou unio, de dois conjuntos de strings, R e S, indicada
por R | S, {x | x R ou x S}.

28 CAPTULO 2 Scanners

2. Concatenao A concatenao de dois conjuntos R e S, indicada por RS, contm


todas as strings formadas pela incluso de um elemento de R no incio de um
elemento de S, ou {x y | x R e y S}.
3. Fechamento O fechamento de Kleene de um conjunto R, indicado por R*,

U i= 0 R i

Esta apenas a unio das concatenaes de R consigo mesmo, zero ou mais


vezes.
Fechamento finito
Para qualquer inteiro i, a RE Ri designa de uma a i
ocorrncias de R.
Fechamento positivo
A RE R+ indica uma ou mais ocorrncias de R,
i
normalmente escrita como i = 0 R .

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 Expresses regulares 29

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

A correspondncia entre a RE e este FA no to bvia como nos exemplos


anteriores deste captulo. A Seo 2.4 apresenta construes que automatizam
a construo de um FA a partir de uma RE. A complexidade de ambos para
comentrios em mltiplas linhas surge do uso de delimitadores com mais de um
caractere. A transio de s2 para s3 codifica o fato de que o reconhecedor viu um
*, de modo que pode lidar com o aparecimento de uma / ou sua falta de maneira
correta. Ao contrrio, Pascal usa delimitadores de comentrio de nico caractere:
{ e }, de modo que um comentrio em Pascal apenas { ^}* }.
Tentar ser especfico com uma RE tambm pode levar a expresses complexas. Considere, por exemplo, que o especificador de registradores em uma linguagem assembly
tpica consiste na letra r seguida imediatamente por um inteiro pequeno. Em ILOC,
que admite um conjunto ilimitado de nomes de registrador, a RE poderia ser r[0.. .9]+,
com o seguinte FA:

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:

2.3 Expresses regulares 31

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

2.3.3 Propriedades de fechamento das REs


Linguagens regulares
Qualquer linguagem que pode ser especificada por
uma expresso regular chamada linguagem regular.

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.

A equivalncia entre REs e FAs tambm sugere outras propriedades de fechamento.


Por exemplo, dado um FA completo, podemos construir um FA que reconhece todas
as palavras w que no esto em L(FA), chamado complemento de L(FA). Para construir esse novo FA para o complemento, podemos alternar a designao de estados de
aceitao e de no aceitao no FA original. Este resultado sugere que REs so fechadas
sob complemento. Na verdade, muitos sistemas que usam REs incluem um operador
de complemento, como o ^ em lex.

2.4 Da expresso regular ao scanner 33

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.

Reescreva-a em termos das suas trs operaes bsicas: alternao, concatenao e


fechamento.
2. Em PL/I, o programador pode inserir aspas em uma string escrevendo duas aspas
em seguida. Assim, a string

seria escrita em um programa PL/I como

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.

2.4 DA EXPRESSO REGULAR AO SCANNER


O objetivo do nosso trabalho com autmatos finitos automatizar a derivao de scanners executveis a partir de uma coleo de REs. Esta seo desenvolve as construes
que transformam uma RE em um FA que seja adequado para implementao direta
e um algoritmo que deriva uma RE para a linguagem aceita por um FA. A Figura2.3
mostra o relacionamento entre todas essas construes.

FIGURA 2.3 O ciclo das construes.

34 CAPTULO 2 Scanners

Para apresentar essas construes, distinguimos entre FAs determinsticos, ou DFAs,


e FAs no determinsticos, ou NFAs, na Seo 2.4.1. Em seguida, apresentamos a
construo de um FA determinstico a partir de uma RE em trs etapas. A construo
de Thompson, na Seo 2.4.2, deriva um NFA a partir de uma RE. A construo
de subconjunto, na Seo 2.4.3, cria um DFA que simula um NFA. O algoritmo de
Hopcroft, na Seo 2.4.4, minimiza um DFA. Para estabelecer a equivalncia de REs
e DFAs, tambm precisamos mostrar que qualquer DFA equivalente a uma RE; a
construo de Kleene deriva uma RE a partir de um DFA. Como ela no simbolizada
diretamente na construo do scanner, deixamos esse algoritmo para a Seo 2.6.1.

2.4.1 Autmatos finitos no determinsticos


Lembre-se, da definio de uma RE, que designamos a string vazia, , como uma RE.
Nenhum dos FAs que montamos mo inclua , mas algumas das REs sim. Qual
papel tem em um FA? Podemos usar transies em para combinar FAs e formar
FAs para REs mais complexas. Por exemplo, suponha que tenhamos FAs para as REs
m e n, chamadas FAm e FAn, respectivamente.

Podemos montar um FA para mn acrescentando uma transio em a partir do estado


de aceitao de FAm para o estado inicial de FAn, renumerando os estados e usando o
estado de aceitao de FAn como estado de aceitao para o novo FA.

-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.

Podemos combin-los com uma -transio para formar um FA para a*ab.

2.4 Da expresso regular ao scanner 35

A -transio, efetivamente d ao FA duas transies distintas de s0 na letra a. Ele


pode tomar a transio s0 a s0 , ou as duas transies s0 s1 e s1 a s2 . Qual
transio est correta? Considere as strings aab e ab. O DFA deve aceitar as duas
strings. Para aab, deve mover s0 a s0 , s0 s1 , s1 a s2 , e s2 b s3 . Para ab, deve
mover s0 s1 , s1 a s2 , e s2 b s3 .
Como essas duas strings mostram, a transio correta de s0 em a depende dos caracteres
que vm aps a. A cada passo, o FA examina o caractere atual. Seu estado codifica o
contexto da esquerda, ou seja, os caracteres que ele j processou. Como o FA precisa
fazer uma transio antes de examinar o prximo caractere, um estado como s0 viola
nossa noo do comportamento de um algoritmo sequencial. O FA que inclui estados
como s0, que possuem vrias transies em um nico caractere, chamado autmato
finito no determinstico (NFA Nondeterministic Finite Automaton). Ao contrrio,
um FA com transies de caractere exclusivas em cada estado chamado autmato
finito determinstico (DFA Deterministic Finite Automaton).

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.

Equivalncia de NFAs e DFAs


Ambos so equivalentes em seu poder expressivo. Qualquer DFA um caso especial de
NFA. Assim, um NFA pelo menos to poderoso quanto um DFA. Qualquer NFA pode
ser simulado por um DFA fato estabelecido pela construo de subconjunto na Seo
2.4.3. A intuio por trs dessa ideia simples; a construo um pouco mais complexa.
Considere o estado de um NFA quando tiver alcanado algum ponto na string de
entrada. Sob o segundo modelo do comportamento do NFA, ele tem algum conjunto
finito de clones operacionais. O nmero dessas configuraes pode ser limitado; para
cada estado, a configurao inclui um ou mais clones neste estado ou no. Assim, um
NFA com n estados produz no mximo |O|n configuraes.
Para simular o comportamento do NFA, precisamos de um DFA com um estado para
cada configurao do NFA. Como resultado, o DFA pode ter exponencialmente mais

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.

2.4.2 Expresso regular para NFA: construo de Thompson


O primeiro passo ao passar de uma RE para um scanner implementado precisa derivar
um NFA a partir da RE. A construo de Thompson realiza este objetivo de forma
direta. Ela tem um template para construir o NFA que corresponde a uma RE de nica
letra, e uma transformao nos NFAs que modela o efeito de cada operador bsico
da RE: concatenao, alternao e fechamento. A Figura2.4 mostra os NFAs triviais
para as REs a e b, alm das transformaes para formar NFAs para as REs ab, a|b e
a* a partir dos NFAs para a e b. As transformaes aplicam-se a quaisquer NFAs.
A construo comea montando NFAs triviais para cada caractere na RE de entrada. Em
seguida, so aplicadas as transformaes para alternao, concatenao e fechamento
coleo de NFAs triviais na ordem ditada pela precedncia e parnteses. Para a RE
a(b|c)*, a construo primeiro montaria NFAs para a, b e c. Como os parnteses possuem precedncia mais alta, em seguida ela cria a NFA para a expresso delimitada
em parnteses, b|c. O fechamento tem precedncia mais alta do que a concatenao,

FIGURA 2.4 NFAs triviais para operadores de expresso regular.

2.4 Da expresso regular ao scanner 37

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.

FIGURA 2.5 Aplicando a construo de Thompson para a(b | c)*.

38 CAPTULO 2 Scanners

REPRESENTANDO A PRECEDNCIA DE OPERADORES


A construo de Thompson deve aplicar suas trs transformaes em uma ordem
que seja consistente com a precedncia dos operadores na expresso regular. Para
representar esta ordem, uma implementao desta construo pode montar uma
rvore que representa a expresso regular e sua precedncia interna. A RE a(b|c)*
produz a seguinte rvore:

onde+representa concatenao, | alternao e * fechamento. Os parnteses so


transformados na estrutura da rvore, e, assim, no possuem representao explcita.
A construo aplica as transformaes individuais em um percurso de ps-ordem pela
rvore. Como as transformaes correspondem s operaes, este percurso monta a
seguinte sequncia de NFAs: a, b, c, b|c, (b|c)* e, finalmente, a(b|c)*. Os Captulos3 e4
mostram como montar rvores de expresso.

2.4.3 NFA para DFA: A construo de subconjunto


A construo de Thompson produz um NFA para reconhecer a linguagem especificada por uma RE. Como a execuo do DFA muito mais fcil de simular do
que a do NFA, o prximo passo no ciclo de construes converte o NFA montado
pela construo de Thompson em um DFA que reconhece a mesma linguagem. Os
DFAs resultantes possuem um modelo de execuo simples e vrias implementaes
eficientes. O algoritmo que constri um DFA a partir de um NFA chamado construo de subconjunto.
A construo de subconjunto usa como entrada um NFA (N, O, d N, n 0, N A). Ele
produz um DFA, (D, O, d D, d 0, D A). NFA e DFA utilizam o mesmo alfabeto, O.
O estado inicial do DFA, d0, e seus estados de aceitao, DA, surgiro a partir da
construo. A parte complexa da construo a derivao do conjunto de estados
D do DFA a partir do conjunto de estados N do NFA, e a derivao da funo de
transio do DFA, dD.
Configurao vlida
Configurao de um NFA que pode ser alcanada
por alguma string de entrada.

O algoritmo, mostrado na Figura2.6, constri um conjunto Q cujos elementos, qi, so,


cada um, um subconjunto de N, ou seja, cada qi 2N. Quando o algoritmo termina, cada
qi Q corresponde a um estado, di D, no DFA. A construo monta os elementos
de Q seguindo as transies que o NFA pode fazer sobre determinada entrada. Assim,
cada qi representa uma configurao vlida do NFA.
O algoritmo comea com um conjunto inicial, q0, que contm n0 e quaisquer estados
no NFA que possam ser alcanados a partir de n0 ao longo dos caminhos que contm

2.4 Da expresso regular ao scanner 39

FIGURA 2.6 A construo de subconjunto.

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.

2.4 Da expresso regular ao scanner 41

FIGURA 2.7 Aplicando a construo de subconjunto ao NFA da Figura2.5.

Computaes de ponto fixo


A construo de subconjunto um exemplo de computao de ponto fixo, um estilo
particular que aparece regularmente na cincia da computao. Essas computaes
so realizadas pela aplicao repetida de uma funo monotnica a alguma coleo
de conjuntos retirados de um domnio cuja estrutura conhecida. Essas computaes
terminam quando alcanam um estado onde iteraes adicionais produzem a mesma
resposta um ponto fixo no espao das repeties sucessivas. As computaes de
ponto fixo desempenham papel importante e recorrente na construo de compiladores.
Argumentos de trmino para algoritmos de ponto fixo normalmente dependem de
propriedades conhecidas do domnio. Para a construo de subconjunto, o domnio D
N
N
22 , pois Q={q0, q1, q2,. . ., qk }, onde cada qi 2N. Como N finito, 2N e 22 tambm
o so. O lao while acrescenta elementos a Q; e no pode remover um elemento de Q.
Podemos ver o lao while como uma funo monotnica crescente f, que significa que,
para um conjunto x, f(x)x. (O operador de comparao .) Como Q pode ter no
mximo |2N| elementos distintos, o lao while pode se repetir no mximo |2N| vezes.
Naturalmente, ele pode alcanar um ponto fixo e parar mais rapidamente do que isso.

Calculando o -closure off-line


Uma implementao da construo de subconjunto poderia calcular -closure()
seguindo os caminhos no grafo de transio do NFA conforme a necessidade. A
Figura2.8 mostra outro mtodo: um algoritmo off-line que calcula -closure( {n})
para cada estado n no grafo de transio. O algoritmo outro exemplo de computao
de ponto fixo.

Funo monotnica
A funo f no domnio D monotnica se x , y D,
xy f (x )f (y )

42 CAPTULO 2 Scanners

Para os propsitos deste algoritmo, considere o diagrama de transio do NFA como


um grafo, com ns e arestas. O algoritmo comea criando um conjunto E para cada n
no grafo. Para um n n, E(n) ir armazenar a aproximao atual para -closure(n).
Inicialmente, o algoritmo define E(n) como {n}, para cada n n, e coloca cada n na
worklist.
Cada iterao do lao while remove um n n da worklist, encontra todas as -transies
que saem de n e acrescenta seus alvos a E(n). Se essa computao mudar a E(n), coloca

FIGURA 2.8 Um algoritmo off-line para -closure.

O uso de um conjunto de vetores de bits para a worklist


pode garantir que o algoritmo no possui cpias
duplicadas do nome de um n na worklist.
Ver Apndice B.2.

os predecessores de n junto com -transies na worklist. (Se n estiver no -closure de


seu predecessor, o acrscimo de ns a E(n) tambm precisa acrescent-los no conjunto
do predecessor.) Esse processo interrompido quando a worklist se torna vazia.
O argumento de trmino para esse algoritmo mais complexo do que para aquele da
Figura2.6. O algoritmo termina quando a worklist est vazia. Inicialmente, a worklist
contm cada n no grafo. Cada iterao remove um n da worklist, podendo tambm
acrescentar um ou mais ns worklist.
O algoritmo s acrescenta um n worklist se o conjunto E do seu sucessor mudar. Os
conjuntos E(n) aumentam monotonicamente. Para um n x, seu sucessor y ao longo de
uma -transio pode colocar x na worklist no mximo |E (y)| |N| vezes, na pior das
hipteses. Se x possui mltiplos sucessores yi ao longo de -transies, cada um deles
pode colocar x na worklist |E (yi)| |N| vezes. Para o grafo inteiro, o comportamento
de pior caso colocaria ns na worklist k |N| vezes, onde k o nmero de -transies
no grafo. Assim, a worklist, eventualmente, se torna vazia e a computao termina.

2.4.4 DFA para DFA mnimo: Algoritmo de Hopcroft


Como melhoria final na converso REDFA, podemos acrescentar um algoritmo
para minimizar o nmero de estados no DFA. O DFA que surge da construo de
subconjunto pode ter um grande conjunto de estados. Embora isso no aumente o tempo
necessrio para varrer uma string, aumenta o tamanho do reconhecedor na memria.
Em computadores modernos, a velocidade de acessos memria constantemente

2.4 Da expresso regular ao scanner 43

FIGURA 2.9 Algoritmo de minimizao de DFA.

governa a velocidade da computao. Um reconhecedor pequeno pode caber melhor


na memria cache do processador.
Para minimizar o nmero de estados em um DFA, (D, O, d, d0, DA), precisamos de uma
tcnica para detectar quando dois estados so equivalentes ou seja, quando ambos
produzem o mesmo comportamento sobre qualquer string de entrada. O algoritmo
na Figura2.9 encontra classes de equivalncia de estados do DFA com base no seu
comportamento. A partir dessas classes de equivalncia podemos construir um DFA
mnimo.
O algoritmo constri uma partio de conjunto, P={p1, p2, p3,. .. pm}, dos estados do
DFA. A partio em particular, P, que o algoritmo constri agrupa os estados do DFA
por seu comportamento. Dois estados do DFA, di, dj ps, tm o mesmo comportamento
em resposta a todos os caracteres de entrada, ou seja, se di c d x , d j c d y e di, dj
ps; ento, dx e dy precisam estar no mesmo conjunto pt. Essa propriedade mantida
para cada conjunto ps P, para cada par de estados di, dj ps e para cada caractere
de entrada, c. Assim, os estados em ps tm o mesmo comportamento com relao aos
caracteres de entrada e aos conjuntos restantes em P.
Para minimizar um DFA, cada conjunto ps P deve ser o maior possvel dentro da
restrio de equivalncia comportamental. Para construir essa partio, o algoritmo
comea com uma inicial aproximada, que obedece a todas as propriedades, exceto a
equivalncia comportamental. Depois, refina iterativamente essa partio para impor
a equivalncia comportamental. A partio inicial contm dois conjuntos, p0=DA e
p1={D DA}, separao que garante que nenhum conjunto na partio final contenha
estados de aceitao e de no aceitao, pois o algoritmo nunca combina duas parties.
O algoritmo refina a partio inicial examinando repetidamente cada ps P para
procurar estados em ps que tenham comportamento diferente para alguma string de
entrada. Logicamente, ele no pode rastrear o comportamento do DFA em cada string.
Porm, pode simular o comportamento de determinado estado em resposta a um nico
caractere de entrada. E usa uma condio simples para refinar a partio: um smbolo
c O dever produzir o mesmo comportamento para cada estado di ps. Se no, o
algoritmo divide ps devido a c.
Essa ao de diviso a chave para entender o algoritmo. Para di e dj permanecerem
juntos em ps, ambos devem tomar transies equivalentes em cada caractere c O.
Ou seja, c O, di c d x e d j c d y , onde dx, dy pt. Qualquer estado dk ps,
onde d k c d z , dz pt, no pode permanecer na mesma partio de di e dj. De modo

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

FIGURA 2.10 Dividindo uma partio devido a a.

semelhante, se di e dj tiverem transies em c, e dk no, este no pode permanecer na


mesma partio que di e dj.
A Figura2.10 torna isso concreto. Os estados em p1={di, dj, dk} so equivalentes
se e somente se suas transies, c O, os levarem a estados que, por si ss, esto
em uma classe de equivalncia. Como vemos, cada estado tem uma transio em a:
di a d x , d j a d y e d k a d z . Se dx, dy e dz estiverem todos no mesmo conjunto na
partio atual, como pode ser visto esquerda, ento di, dj e dk devero permanecer
juntos, e a no divide p1.
Por outro lado, se dx, dy e dz estiverem em dois ou mais conjuntos diferentes, ento a
divide p1. Como vemos no desenho do centro da Figura2.10, dx p2 enquanto dy e
dz p3, de modo que o algoritmo precisa dividir p1 e construir dois novos conjuntos,
P4={di} e p5={dj , dk}, para refletir o potencial para diferentes resultados com strings
que comeam com o smbolo a. O resultado aparece no lado direito da mesma figura.
A mesma diviso resultaria se o estado di no tivesse transio em a.
Para refinar uma partio P, o algoritmo examina cada p P e cada c O. Se c divide
p, o algoritmo constri dois novos conjuntos a partir de p e os acrescenta a T. (Ele
poderia dividir p em mais de dois conjuntos, todos tendo comportamentos internamente
consistentes em c. No entanto, criar um estado consistente e agrupar o restante de p em
outro estado ser suficiente. Se o ltimo estado for inconsistente em seu comportamento
sobre c, o algoritmo o dividir em uma iterao posterior.) O algoritmo repete esse
processo at que encontre uma partio em que no possa mais dividir conjuntos.
Para construir o novo DFA a partir da partio final p, podemos criar um nico estado
para representar cada conjunto p P e acrescentar transies apropriadas entre esses
novos estados representativos. Para o estado representando pl, acrescentamos uma
transio para o estado representando pm sobre c se algum dj pl tiver uma transio
em c para algum dk pm. Pela construo, sabemos que, se dj tiver tal transio, o
mesmo ocorre com cada outro em pl; no fosse assim, o algoritmo teria dividido pl
devido a c. O DFA resultante mnimo; a prova est fora do nosso escopo.

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

2.4 Da expresso regular ao scanner 45

FIGURA 2.11 Aplicando o algoritmo de minimizao de DFA.

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.

FIGURA 2.12 DFA para a(b|c)*.

2.4.5 Usando DFA como reconhecedor


At aqui, desenvolvemos os mecanismos para construir uma implementao de DFA
a partir de uma nica RE. Para ser til, o scanner de um compilador deve reconhecer
todas as categorias sintticas que aparecem na gramtica para a linguagem-fonte. O
que precisamos, ento, de um reconhecedor que possa lidar com todas as REs para
a microssintaxe da linguagem. Dadas as REs para diversas categorias sintticas, r1,
r2, r3,, rk, podemos construir uma nica RE para a coleo inteira, formando (r1 | r2
| r3 | | rk).
Se voc fizer esta RE percorrer o processo inteiro, montando um NFA, construindo um
DFA para simular o NFA, minimizando-o e transformando esse DFA mnimo em cdigo
executvel, o scanner resultante reconhece a prxima palavra que corresponde a um dos
ris. Ou seja, quando o compilador o chama em alguma entrada, o scanner examinar os
caracteres, um por vez, e aceitar a string se estiver em um estado de aceitao quando
esgotar a entrada. O scanner deve retornar tanto o texto da string quanto sua categoria
sinttica, ou classe gramatical. Como a maioria dos programas reais contm mais de
uma palavra, precisamos transformar ou a linguagem ou o reconhecedor.
No nvel da linguagem, podemos insistir que cada palavra termine com algum delimitador facilmente reconhecvel, como um espao ou uma tabulao. A ideia

2.4 Da expresso regular ao scanner 47

enganosamente atraente. Literalmente, isso requer delimitadores em torno de todos os


operadores, como +, , parnteses e vrgula.
No nvel do reconhecedor, podemos mudar a implementao do DFA e sua noo de
aceitao. Para encontrar a palavra mais longa que corresponde a uma das REs, o DFA
deve ser executado at alcanar o ponto onde o estado atual, s, no possui transio de sada
para o prximo caractere. Neste ponto, a implementao deve decidir a qual RE ele correspondeu. Surgem duas opes; a primeira simples. Se s for um estado de aceitao, ento
o DFA ter encontrado uma palavra na linguagem e deve relat-la e sua categoria sinttica.
Se s no for um estado de aceitao, as coisas so mais complexas. Dois casos podem
ocorrer. Se o DFA passou por um ou mais estados de aceitao em seu caminho para
s, o reconhecedor deve recuar at o mais recente de tais estados. Essa estratgia corresponde ao prefixo vlido mais longo na string de entrada. Mas, se nunca conseguiu
alcanar um estado de aceitao, ento nenhum prefixo da string de entrada uma
palavra vlida, e o reconhecedor deve relatar um erro. Os scanners na Seo 2.5.1
implementam essas duas noes.
Como uma complicao final, um estado de aceitao no DFA pode representar vrios
estados de aceitao no NFA original. Por exemplo, se a especificao lxica inclui REs
para palavras-chave, bem como uma RE para identificadores, ento uma palavra-chave
como new poderia corresponder a duas REs. O reconhecedor precisa decidir qual
categoria sinttica retornar: identificador ou a categoria nica (para todas as palavras-chave) para a palavra-chave new.
A maioria das ferramentas geradoras de scanners permite que o construtor de compiladores especifique uma prioridade entre padres. Quando o reconhecedor faz correspondncia a vrios padres, retorna a categoria sinttica do padro de maior prioridade.
Esse mecanismo resolve o problema de uma forma simples. O gerador de scanner lex,
distribudo com muitos sistemas Unix, atribui prioridades com base na posio na lista
de REs. A primeira RE tem prioridade mais alta, enquanto a ltima, a mais baixa.
Como uma questo prtica, o construtor de compiladores tambm precisa especificar
REs para partes do fluxo de entrada que no formam palavras no texto do programa.
Na maioria das linguagens de programao, o espao em branco ignorado, mas
todo programa o contm. Para lidar com este espao, o construtor de compiladores
normalmente inclui uma RE que corresponde a espaos, tabulaes e caracteres de
fim de linha; a ao sobre a aceitao do espao em branco chamar o scanner,
recursivamente, e retornar seu resultado. Se os comentrios forem descartados, eles
sero tratados de forma semelhante.

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:

2.5 IMPLEMENTANDO SCANNERS


A construo de scanners um problema para o qual a teoria das linguagens formais
produziu ferramentas que podem automatizar a implementao. Para a maioria das
linguagens, o construtor de compilador pode produzir um scanner aceitavelmente
rpido diretamente a partir de um conjunto de expresses regulares. O construtor de
compiladores cria uma RE para cada categoria sinttica e fornece as REs como entrada para um gerador de scanner. Este constri um NFA para cada RE, junta-os com
-transies, cria um DFA correspondente e o minimiza. Nesse ponto, o gerador de
scanner deve converter o DFA em cdigo executvel.
Esta seo discute trs estratgias de implementao para converter um DFA em cdigo
executvel: scanner controlado por tabela, scanner codificado diretamente e scanner
codificado mo. Todos operam da mesma maneira, simulando o DFA. Repetidamente
leem o prximo caractere da entrada e simulam a transio do DFA causada por este
caractere. Esse processo termina quando o DFA reconhece uma palavra. Conforme
descrevemos na seo anterior, isto ocorre quando o estado atual, s, no tem transio
de sada no caractere de entrada atual.
Se s um estado de aceitao, o scanner reconhece a palavra e retorna um lexema e sua
categoria sinttica ao procedimento que o chamou. Se s um estado de no aceitao,
o scanner precisa determinar se ele passou ou no por um estado de aceitao no
caminho at s. Se o scanner encontrou um estado de aceitao, deve reverter (rollback)
seu estado interno e seu fluxo de entrada para esse ponto e relatar o sucesso. Se no,
deve informar o fracasso.
Essas trs estratgias de implementao (controlada por tabela, codificada diretamente e codificada mo) diferem nos detalhes dos custos de runtime. Porm,
todas tm a mesma complexidade assinttica custo constante por caractere, mais
o custo de rollback. A diferena na eficincia dos scanners bem implementados
muda os custos constantes por caractere, mas no a complexidade assinttica da
anlise lxica.
As trs subsees seguintes discutem as diferenas na implementao entre os scanners
controlados por tabela, codificados diretamente e mo. As estratgias diferem no
modo como modelam a estrutura de transio do DFA e como simulam sua operao.
Essas diferenas, por sua vez, produzem diferentes custos em tempo de execuo.
A subseo final examina duas estratgias distintas para lidar com palavras-chave
reservadas.

2.5 Implementando scanners 49

2.5.1 Scanners controlados por tabela


Este mtodo usa um esqueleto de scanner para controle e um conjunto de tabelas
geradas que codificam o conhecimento especfico da linguagem. Como podemos ver
na Figura2.13, o construtor de compilador oferece um conjunto de padres lxicos,
especificados como expresses regulares. O gerador de scanner, ento, produz tabelas
que controlam o esqueleto de scanner.

FIGURA 2.13 Gerando um scanner controlado por tabela.

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

Para exemplos pequenos, como r[09]+, a tabela de


classificao maior do que a de transio completa.
J para um exemplo com tamanho realista, esse
relacionamento deve ser invertido.

50 CAPTULO 2 Scanners

FIGURA 2.14 Scanner controlado por tabela para nomes de registrador.

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.

2.5 Implementando scanners 51

Evitando rollback em excesso


Algumas expresses regulares podem produzir chamadas quadrticas para rollback
no scanner apresentado na Figura2.14. O problema surge pelo nosso desejo de fazer
que o scanner retorne palavra mais longa do fluxo de entrada que seja um prefixo.
Considere a RE ab | (ab)* c. O DFA correspondente, mostrado ao lado, reconhece
ab ou qualquer nmero de ocorrncias de ab seguido por um c final. Na string de
entrada ababababc, um scanner montado a partir do DFA ler todos os caracteres e retornar a string inteira como uma nica palavra. Porm, se a entrada for
abababab, ele deve analisar todos os caracteres antes que possa determinar que
o prefixo mais longo ab. Na prxima chamada, analisar ababab para retornar
ab. Na terceira, analisar abab para retornar ab, e a final simplesmente retornar
ab sem qualquer rollback. No pior caso, ele pode gastar um tempo quadrtico lendo
o fluxo de entrada.
A Figura2.15 mostra uma modificao no scanner da Figura2.14 que evita este problema. Ele difere do scanner anterior em trs pontos importantes. Primeiro, tem um
contador global, InputPos, para registrar a posio no fluxo de entrada. Segundo, um
array de bits, Failed, para registrar as transies sem sada medida que o scanner
as encontra. Failed tem uma linha para cada estado e uma coluna para cada posio
no fluxo de entrada. Terceiro, uma rotina de inicializao que deve ser chamada antes
de chamar NextWord(). Esta rotina define InputPos como zero e Failed,
uniformemente, como false.
Este scanner, chamado scanner de correspondncia mais longa, evita o comportamento
patolgico marcando as transies sem sada medida que so removidas da pilha.
Assim, com o tempo, ele registra pares estado, posio de entrada especficos que
no podem levar a um estado de aceitao. Dentro do lao de anlise, o primeiro lao

FIGURA 2.15 Scanner de correspondncia mais longa.

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.

Gerando tabelas de transio e de classificao


Dado um DFA, o gerador de scanner pode gerar as tabelas de uma forma simples. A
inicial tem uma coluna para cada caractere no alfabeto de entrada e uma linha para
cada estado no DFA. Para cada estado, em ordem, o gerador examina as transies de
sada e preenche a linha com os estados apropriados. O gerador pode reduzir as colunas
idnticas para uma nica ocorrncia; ao faz-lo, ele pode construir o classificador de
caracteres. (Dois caracteres pertencem mesma classe se e somente se tiverem colunas
idnticas em d.) Se o DFA tiver sido minimizado, duas linhas no podero ser idnticas,
de modo que a compactao de linhas no um problema.

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:

2.5 Implementando scanners 53

a
s0
s1

b,c

s1
se
se
s1
Tabela de Transio

Outro
se
se

O classificador de caracteres tem trs classes: a, b ou c, e todos os outros caracteres.

2.5.2 Scanners codificados diretamente


Para melhorar o desempenho de um scanner controlado por tabela, temos de reduzir
o custo de uma ou ambas de suas aes bsicas: ler um caractere e calcular a prxima
transio do DFA. Os scanners codificados diretamente reduzem o custo da computao
de transies do DFA, substituindo a representao explcita do estado e grafo de
transio do DFA por uma representao implcita. A representao implcita simplifica
a computao de pesquisa em tabela em duas etapas: elimina as referncias de memria
envolvidas nessa computao, e permite outras especializaes. O scanner resultante
tem a mesma funcionalidade do controlado por tabela, mas com um overhead menor
por caractere. O scanner codificado diretamente no mais difcil de ser gerado do
que seu equivalente, controlado por tabela.
Este ltimo gasta a maior parte do seu tempo dentro do lao while central; assim, o
ncleo de um scanner codificado diretamente uma implementao alternativa desse
lao while. Com alguns detalhes removidos, esse loop realiza as seguintes aes:

Observe a varivel state que representa explicitamente o estado atual do DFA, e as


tabelas CharCat e d que representam o diagrama de transio do DFA.
REPRESENTANDO STRINGS
O scanner classifica palavras no programa de entrada num pequeno conjunto de
categorias. Por um ponto de vista funcional, cada palavra no fluxo de entrada torna-se
um par palavra,tipo, no qual palavra o texto real que forma a palavra, e tipo
representa sua categoria sinttica.
Para muitas categorias, ter tanto palavra quanto tipo redundante. As palavras
+,e for tm apenas uma grafia. Para identificadores, nmeros e strings de
caracteres, porm, o compilador usar repetidamente a palavra. Infelizmente, muitos
compiladores so escritos em linguagens que no possuem uma representao
apropriada para a parte palavra do par. Precisamos de uma representao que seja
compacta e oferea um teste de igualdade rpido para duas palavras.
Uma prtica comum para resolver este problema fazer com que o scanner crie uma
nica tabela hash (ver Apndice B.4) para manter todas as strings distintas usadas no
programa de entrada. O compilador, ento, usa, ou o ndice da string nessa tabela de
strings, ou um ponteiro para sua imagem armazenada na tabela de strings como um
proxy para a string. As informaes derivadas da string como o tamanho de uma
constante de caracteres, ou o valor e o tipo de uma constante numrica podem
ser calculadas uma vez e referenciadas rapidamente pela tabela. Como a maioria dos
computadores possui representaes eficientes em termos de armazenamento para
inteiros e ponteiros, isto reduz a quantidade de memria usada internamente no
compilador. Usar os mecanismos de comparao de hardware nos proxies de inteiro e
ponteiro, tambm simplifica o cdigo usado para compar-los.

54 CAPTULO 2 Scanners

Overhead da pesquisa em tabela


Para cada caractere, o scanner controlado por tabela realiza duas pesquisas em tabela:
uma em CharCat e outra em d. Embora ambas levem um tempo O(1), a abstrao de
tabela impe overheads com custo constante, que um scanner codificado diretamente
pode evitar. Para acessar o i-simo elemento de CharCat, o cdigo precisa calcular
seu endereo, dado por

A discusso detalhada do cdigo para endereamento


de array pode ser encontrada na Seo 7.5.

onde @CharCat0 uma constante relacionada ao endereo inicial de CharCat na


memria, e w o nmero de bytes em cada elemento de CharCat. Aps calcular o
endereo, o cdigo precisa carregar os dados nele encontrados na memria.
Como d tem duas dimenses, o clculo de endereo mais complexo. Para a referncia
d(state,cat), o cdigo deve calcular

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.

Substituindo o lao while do scanner controlado por tabela


Em vez de representar explicitamente o estado do DFA atual e o diagrama de transio,
o scanner codificado diretamente tem um fragmento de cdigo especializado para
implementar cada estado. Ele transfere o controle diretamente de um fragmento de
estado para outro, a fim de simular as aes do DFA. A Figura2.16 mostra um scanner
codificado diretamente para r [09]+; ele equivalente ao scanner controlado por tabela
apresentado na Figura2.14.
Considere o cdigo para o estado s1. Ele l um caractere, concatena-o palavra
atual e avana o contador de caracteres. Se char for um dgito, ele salta para o
estado s2. Caso contrrio, salta para sout. O cdigo no exige clculos de endereo
complicados, porque se refere a um pequeno conjunto de valores que podem ser
mantidos nos registradores. Os outros estados possuem implementaes igualmente
simples.
O cdigo na Figura2.16 usa o mesmo mecanismo do scanner controlado por
tabela para rastrear os estados de aceitao e reverter para eles aps um passo
excessivo. Como o cdigo representa um DFA especfico, poderamos especializ-lo ainda mais. Em particular, como o DFA tem apenas um estado de aceitao,
a pilha desnecessria e as transies para s out a partir de s 0 e s 1 podem ser
substitudas por relata falha. Em um DFA no qual alguma transio leva
de um estado de aceitao a um de no aceitao, o mecanismo mais geral
necessrio.
Um gerador de scanner pode emitir diretamente um cdigo semelhante ao mostrado
na Figura2.16. Cada estado tem algumas atribuies-padro, seguidas pela lgica de
desvio que implementa as transies a partir do estado. Diferentemente do scanner

2.5 Implementando scanners 55

FIGURA 2.16 Um scanner codificado diretamente para r [09]+.

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.

Naturalmente, o cdigo gerado viola muitos dos preceitos da programao estruturada.


Embora pequenos exemplos possam ser compreensveis, o cdigo para um conjunto
complexo de expresses regulares pode ser difcil para um humano acompanhar.
Novamente, como o cdigo gerado, os humanos no devem ter a necessidade de
l-lo ou depur-lo. A velocidade adicional obtida pela codificao direta torna esta
uma opo atraente, particularmente porque no ocasiona trabalho extra para o construtor de compiladores. Qualquer trabalho extra empurrado para a implementao
do gerador de scanner.

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.

O scanner pode, de modo fcil e eficiente, classificar um determinado caractere, como


mostra a Figura2.16. O estado s0 usa um teste direto sobre r para determinar se
char est em Registrador. Como todas as outras classes possuem aes equivalentes
no DFA, o scanner no precisa realizar outros testes. Os estados s1 e s2 classificam
char como Dgito entre outras denominaes, e aproveitam o fato de que os dgitos
de 0 a 9 ocupam posies adjacentes na sequncia de classificao ASCII, correspondentes aos inteiros de 48 a 57.
Em um scanner no qual a classificao de caractere mais complicada, o
mtodo de tabela de traduo usado no scanner controlado por tabela pode ser
menos dispendioso do que testar os caracteres diretamente. Em particular, se
uma classe contm mltiplos caracteres que no ocupam posies adjacentes
na sequncia de classificao, uma pesquisa em tabela pode ser mais eficiente
do que o teste direto. Por exemplo, uma classe que contenha os operadores
aritmticos +, , *, \, e ^ (43, 45, 42, 48 e 94 na sequncia ASCII) exigiria uma
srie moderadamente longa de comparaes. O uso de uma tabela de traduo,
como CharCat no exemplo controlado por tabela, poderia ser mais rpido do
que as comparaes se a tabela de traduo permanecer no cache primrio
doprocessador.

2.5.3 Scanners codificados mo


Scanners gerados, sejam controlados por tabela ou codificados diretamente, utilizam
uma quantidade de tempo pequena e constante por caractere. Apesar disto, muitos
compiladores usam scanners codificados mo. Em um estudo informal de grupos de
compiladores comerciais, descobrimos que uma frao surpreendentemente grande os
usava. De modo semelhante, muitos dos compiladores open-source populares contam
com este tipo de scanner. Por exemplo, o gerador de scanner flex foi aparentemente
criado para dar suporte ao projeto gcc, mas o gcc 4.0 usa scanners codificados mo
em vrios de seus front ends.
O scanner codificado diretamente reduziu o overhead da simulao do DFA; o codificado mo pode reduzir o overhead das interfaces entre ele e o restante do sistema.
Em particular, uma implementao cuidadosa pode melhorar os mecanismos usados

2.5 Implementando scanners 57

para ler e manipular caracteres na entrada e as operaes necessrias para produzir


uma cpia do lexema real na sada.

Buffering do fluxo de entrada


Embora a E/S caractere a caractere leve a formulaes algortmicas claras, o overhead de uma chamada de procedimento por caractere significativo em relao ao
custo da simulao do DFA em um scanner controlado por tabela ou codificado
diretamente. Para reduzir o custo da E/S por caractere, o construtor de compiladores
pode usar a E/S em buffer, em que cada operao de leitura retorna uma string de
caracteres maior, ou buffer, e o scanner, ento, indexa por meio deste. O scanner
mantm um ponteiro para o buffer. A responsabilidade por manter o buffer cheio e
acompanhar a posio atual no buffer fica com NextChar. Essas operaes podem
ser realizadas em linha; normalmente so codificadas em uma macro, para evitar
encher o cdigo com incrementos de ponteiro e recuperao de dados apontados
por ponteiro (dereference).
O custo da leitura de um buffer cheio de caracteres tem dois componentes, um overhead
fixo grande e um custo por caractere pequeno. Um esquema de buffer e ponteiro
amortiza os custos fixos da leitura por muitas buscas de nico caractere. Aumentar o
buffer reduz o nmero de vezes que o scanner contrai este custo e reduz o overhead
por caractere.
O uso de buffer e ponteiro tambm ocasiona uma implementao simples e eficaz da
operao RollBack que ocorre ao final de ambos os scanners gerados. Para reverter
a entrada, o scanner pode simplesmente decrementar o ponteiro de entrada. Esse esquema funciona desde que o scanner no decremente o ponteiro alm do incio do
buffer. Nesse ponto, porm, o scanner precisa de acesso ao contedo anterior do buffer.
Na prtica, o construtor de compiladores pode limitar a distncia de rollback que um
scanner precisar. Com o rollback limitado, o scanner pode simplesmente usar dois
buffers adjacentes e incrementar o ponteiro em um padro de mdulo, como vemos a
seguir:

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

FIGURA 2.17Implementando NextChar e RollBack.

implementao suficientemente simples para ser expandida em linha. (Se usssemos


essa implementao de NextChar e RollBack nos scanners gerados, RollBack
precisaria truncar o caractere final para fora do lexema.)
Como uma consequncia natural do uso de buffers finitos, RollBack tem um
histrico limitado no fluxo de entrada. Para evitar que decremente o ponteiro alm
do incio desse contexto, NextChar e RollBack cooperam. O ponteiro Fence
sempre indica o incio do contexto vlido. NextChar define Fence toda vez que
preenche um buffer. RollBack verifica Fence toda vez que tenta decrementar o
ponteiro Input.
Depois de uma longa srie de operaes NextChar, digamos, mais de n vezes,
RollBack sempre pode reverter pelo menos n caracteres. Porm, uma sequncia
de chamadas para NextChar e RollBack que funciona para a frente e para
trs no buffer pode criar uma situao em que a distncia entre Input e Fence
menor que n. Valores maiores de n diminuem esta probabilidade. As distncias
de reverso esperadas devem ser levadas em considerao na seleo do tamanho
do buffer, n.

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

2.5 Implementando scanners 59

da Figura2.16, podemos excluir todas as instrues que se referem ao lexeme,


acrescentar RegNum 0; a sinit, e substituir as ocorrncias de goto s2 nos
estados s1 e s2 por:

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.

2.5.4 Tratando de palavras-chave


Consideramos, consistentemente, que as palavras-chave na linguagem de entrada devem
ser reconhecidas incluindo REs explcitas para elas na descrio que gera o DFA e o
reconhecedor. Muitos autores propuseram uma estratgia alternativa: fazer que o DFA
os classifique como identificadores e testar cada identificador para determinar se uma
palavra-chave ou no.
Esta estratgia fez sentido no contexto de um scanner implementado mo. A complexidade adicional acrescentada pela verificao expltica de palavras-chave causa
uma expanso significativa no nmero de estados do DFA. Esse peso adicional na implementao importa em um programa codificado mo. Com uma tabela hash razovel
(ver Apndice B.4), o custo esperado de cada pesquisa seria constante. De fato, este
esquema tem sido usado como uma aplicao clssica para o hashing perfeito. Neste,
o implementador garante, para um conjunto fixo de chaves, que a funo hash gera
um conjunto compacto de inteiros sem colises, o que reduz o custo da pesquisa em
cada palavra-chave. Se a implementao da tabela levar em conta a funo de hashing
perfeito, uma nica sondagem serve para distinguir palavras-chave de identificadores.
Porm, se tentar novamente em caso de falha, o comportamento pode ser muito pior
para palavras no chave do que para as palavras-chave.
Se o construtor de compilador usar um gerador de scanner para construir o reconhecedor, ento a complexidade adicional do reconhecimento de palavras-chave no
DFA tratada pelas ferramentas. Os estados extras que isso acrescenta consomem
memria, mas no tempo de compilao. O uso do mecanismo de DFA para reconhecer
palavras-chave evita uma pesquisa em tabela em cada identificador. E tambm evita
o overhead de implementar uma tabela de palavras-chave e suas funes de suporte.

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.

2. Uma implementao alternativa poderia usar um reconhecedor para (a|b|c) (a|b|c)


(a|b|c), seguido por uma pesquisa em uma tabela que contm as trs palavras:
abc, bca e cab.
a. Desenhe o DFA para essa linguagem.
b. Mostre o scanner codificado diretamente, incluindo a chamada necessria para
realizar a pesquisa de palavra-chave.
c. Compare o custo desta tcnica com aquelas da questo 1, acima.
3. Que impacto teria a incluso das aes de transio-por-transio sobre o processo de minimizao de DFA? (Suponha que tenhamos um mecanismo lingustico de
conexo de fragmentos de cdigo s arestas no grafo de transio.)

2.6 Tpicos avanados 61

2.6 TPICOS AVANADOS


2.6.1 DFA para expresso regular
A etapa final no ciclo de construes, mostrado na Figura2.3, construir uma RE
a partir de um DFA. A combinao da construo de Thompson e da construo de
subconjunto fornece uma prova construtiva de que os DFAs so, pelo menos, to
poderoso quanto as REs. Esta seo apresenta a construo de Kleene, que monta uma
RE para descrever o conjunto de strings aceitos por um DFA qualquer. Esse algoritmo
estabelece que as REs so pelo menos to poderosas quanto os DFAs. Juntos, eles
mostram que REs e DFAs so equivalentes.
Considere o diagrama de transio de um DFA como um grafo com arestas rotuladas. O
problema de derivar uma RE que descreve a linguagem aceita pelo DFA corresponde a
um problema de caminho sobre o diagrama de transio do DFA. O conjunto de strings
em L(DFA) consiste do conjunto de rtulos de aresta para cada caminho de d0 a di,
di DA. Para qualquer DFA com um grafo de transio cclico, o conjunto desses
caminhos infinito. Felizmente, as REs tm o operador de fechamento de Kleene para
lidar com este caso e resumir o conjunto completo de subcaminhos criados por um ciclo.
A Figura2.18 mostra um algoritmo para calcular esta expresso de caminho, considerando que o DFA tem estados numerados de 0 a |D| 1, com d0 como o estado inicial.
Ele gera uma expresso que representa os rtulos ao longo de todos os caminhos entre
dois ns para cada par de ns no diagrama de transio. Como etapa final, combina as
expresses para cada caminho que sai de d0 e alcana algum estado de aceitao, di DA.
Deste modo, sistematicamente constri as expresses de caminho para todos os caminhos.
O algoritmo calcula um conjunto de expresses, indicado por Rkij, para todos os valores
relevantes de i, j e k. Rkij uma expresso que descreve todos os caminhos pelo grafo de
transio a partir do estado i para o estado j, sem passar por um estado com numerao
maior do que k. Aqui, passar por significa tanto entrar quanto sair, de modo que R21,16
pode ser no vazio se uma aresta passar diretamente de 1 para 16.

FIGURA 2.18 Derivando uma expresso regular a partir de um DFA.

62 CAPTULO 2 Scanners

As instrues tradicionais deste algoritmo consideram


que os nomes de n variam de 1 a n, e no de 0 a n 1.
Assim, colocam os caminhos diretos em R0ij.

Inicialmente, o algoritmo coloca todos os caminhos diretos de i a j em Rij1, com {}


acrescentado a Rij1 se i=j. Por iteraes sucessivas, constri caminhos maiores para
produzir Rijk acrescentando a Rijk-1 os caminhos que passam por k no seu caminho de i a
j. Dado Rijk1, o conjunto de caminhos acrescentados passando de k 1 a k exatamente
o conjunto de caminhos que vo de i a k no usando qualquer estado maior que k 1,
concatenado com os de k a si mesmo que no passam por qualquer estado maior que
k 1, seguido pelos caminhos que vo de k a j que no passam por qualquer estado
maior que k 1. Ou seja, cada iterao do loop em k acrescenta os caminhos que passam
por k a cada conjunto Rijk1 para produzir Rijk.
Quando o loop k termina, as diversas expresses Rkij so responsveis por todos os
caminhos pelo grafo. A etapa final calcula o conjunto de caminhos que comea com d0 e
terminam em algum estado de aceitao, dj dA, como a unio das expresses de caminho.

2.6.2 Outra tcnica para minimizao de DFA: algoritmo


de Brzozowski
Se aplicarmos a construo de subconjunto a um NFA que possui vrios caminhos do
estado inicial para algum prefixo, ela agrupar os estados envolvidos nesses caminhos
de prefixo duplicados e criar um nico caminho para este prefixo no DFA. A construo de subconjunto sempre produz DFAs que no possuem caminhos de prefixo
duplicados. Brzozowski usou esta observao para criar um algoritmo de minimizao
de DFA alternativo, que constri diretamente o DFA mnimo a partir de um NFA.
Para um NFA n, considere que reverse(n) seja o NFA obtido revertendo o sentido de
todas as transies, transformando o estado inicial em final, acrescentando um novo
estado inicial e conectando-o a todos os estados que eram finais em n. Alm disso,
considere que reachable(n) seja uma funo que retorna o conjunto de estados e transies em n que sejam alcanveis a partir do seu estado inicial. Finalmente, considere
que subset(n) seja o DFA produzido pela aplicao da construo de subconjunto a n.
Agora, dado um NFA n, o DFA equivalente mnimo simplesmente

A aplicao interna de subset e reverse elimina sufixos duplicados no NFA original.


Em seguida, reachable descarta quaisquer estados e transies que no sejam mais
interessantes. Finalmente, a aplicao externa dos trs (reachable, subset e reverse)
elimina quaisquer prefixos duplicados no NFA. (A aplicao de reverse a um DFA
pode produzir um NFA.)
O exemplo na Figura2.19 mostra as etapas do algoritmo em um NFA simples para a RE
abc | bc | ad. O NFA na Figura2.19a semelhante ao que a construo de Thompson
produziria; removemos as -transies que colam os NFAs para letras individuais.
A Figura2.19b mostra o resultado da aplicao de reverse a este NFA. A Figura2.19c
representa o DFA que subset constri a partir do reverse do NFA. Nesse ponto, o
algoritmo aplica reachable para remover quaisquer estados inalcanveis; nosso NFA de
exemplo no os possui. Em seguida, o algoritmo aplica reverse ao DFA, o que produz o
NFA da Figura2.19d. A aplicao de subset a este NFA produz o DFA da Figura2.19e.
Como ele no possui estados inalcanveis, ele o DFA mnimo para abc | bc | cd.
Esta tcnica parece ser dispendiosa, pois aplica subset duas vezes, e sabemos que
subset pode construir um conjunto de estados exponencialmente grande. Os estudos dos
tempos de execuo de diversas tcnicas de minimizao de FA sugerem, porm, que
este algoritmo funciona razoavelmente bem, talvez devido s propriedades especficas

2.6 Tpicos avanados 63

FIGURA 2.19 Minimizando um DFA com o algoritmo de Brzozowski.

do NFA produzidas pela primeira aplicao de reachable (subset( reverse(n))). Do


ponto de vista da engenharia de software, pode ser que a implementao de reverse e
reachable seja mais fcil do que a depurao do algoritmo de particionamento.

2.6.3 Expresses regulares sem fechamento


Uma subclasse das linguagens regulares que tem aplicao prtica, alm da anlise
lxica, o conjunto de linguagens descritas por expresses regulares sem fechamento.
Essas REs tm a forma w1 | w2 | w3 | | wn, na qual as palavras individuais, wi, so
apenas concatenaes de caracteres no alfabeto, O, e a propriedade de que produzem
DFAs com grafos de transio acclicos.
Essas linguagens regulares simples so interessantes por dois motivos. Primeiro,
muitos problemas de reconhecimento de padres podem ser descritos com uma RE sem
fechamento. Alguns exemplos so palavras em um dicionrio, URLs que devem ser
filtradas e chaves para uma tabela hash. Segundo, o DFA para uma RE sem fechamento
pode ser construdo de modo particularmente eficaz.
Para constru-lo, comece com um estado inicial s0. Para acrescentar uma palavra ao
DFA existente, o algoritmo segue o caminho para a nova palavra at esgotar o padro
ou encontrar uma transio para se. No primeiro caso, ele designa o estado final para
a nova palavra como um estado de aceitao. No outro, acrescenta um caminho para
o sufixo restante da nova palavra. O DFA resultante pode ser codificado em formato
tabular ou codificado diretamente (ver Seo 2.5.2). De qualquer forma, o reconhecedor
usa um tempo constante por caractere no fluxo de entrada.
Nesse algoritmo, o custo de acrescentar uma nova palavra a um DFA existente
proporcional ao tamanho da nova palavra. O algoritmo tambm funciona de modo
incremental; uma aplicao pode facilmente acrescentar novas palavras a um DFA
que esteja em uso. Essa propriedade torna o DFA acclico uma alternativa interessante

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.

2.7 RESUMO DO CAPTULO E PERSPECTIVA


O uso difundido de expresses regulares para pesquisa e anlise lxica uma das histrias de sucesso da cincia da computao moderna. Essas ideias foram desenvolvidas
como parte inicial da teoria das linguagens formais e autmatos. E so aplicadas de
forma rotineira em ferramentas que variam desde editores de textos a mecanismos
de filtragem na Web e compiladores, como um meio de especificar, de forma concisa,
grupos de strings que sejam linguagens regulares. Sempre que uma coleo finita de
palavras precisa ser reconhecida, os reconhecedores baseados em DFA merecem uma
sria considerao.
A teoria de expresses regulares e autmatos finitos tm desenvolvido tcnicas que
permitem o reconhecimento de linguagens regulares em um tempo proporcional ao
tamanho do fluxo de entrada. As tcnicas para derivao automtica de DFAs a partir
de REs e para minimizao de DFA tm permitido a construo de ferramentas robustas
que geram reconhecedores baseados em DFA. Tanto os scanners gerados quanto os
codificados mo so usados em compiladores modernos bem respeitados. De qualquer
forma, uma implementao cuidadosa deve ser executada em um tempo proporcional
ao tamanho do fluxo de entrada, com um pequeno overhead por caractere.

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].

2.7 Resumo do captulo e perspectiva 65

Kleene [224] estabeleceu a equivalncia entre REs e FAs. Tanto o fechamento de


Kleene quanto o algoritmo de DFA para RE ostentam seu nome. McNaughton e
Yamada mostraram uma construo que relaciona REs a NFAs [262]. A construo
mostrada neste captulo foi moldada no trabalho de Thompson [333], motivado pela
implementao de um comando de busca textual para um antigo editor de textos.
Johnson descreve a primeira aplicao dessa tecnologia para automatizar a construo
do scanner [207]. A construo de subconjunto deriva de Rabin e Scott [292]. Deve-se
a Hopcroft [193] o algoritmo de minimizao de DFA na Seo 2.4.4, que tem encontrado aplicao para muitos problemas diferentes, incluindo a deteco de quando duas
variveis de programa sempre tm o mesmo valor [22].
A ideia de gerar cdigo ao invs de tabelas para produzir um scanner codificado
diretamente parece ter origem no trabalho de Waite [340] e Heuring [189], que relatam
um fator de melhoria de cinco em relao s implementaes controladas por tabela.
Ngassam etal. descrevem experimentos que caracterizam os ganhos de velocidade possveis nos scanners codificados mo [274]. Diversos autores examinaram os dilemas
na implementao do scanners. Jones [208] defende a codificao direta, mas sustenta
uma tcnica estruturada para o controle de fluxo, ao invs do cdigo espaguete mostrado
na Seo 2.5.2. Brouwer etal. comparam a velocidade de 12 diferentes implementaes
de scanner; e descobriram um fator de 70 de diferena entre as implementaes mais
rpidas e mais lentas [59].
A tcnica alternativa de minimizao de DFA apresentada na Seo 2.6.2 foi descrita por Brzozowski em 1962 [60]. Diversos autores tm comparado as tcnicas de
minimizao de DFA e seu desempenho [328,344]. Muitos outros, ainda, examinaram
a construo e a minimizao de DFAs acclicos [112,343,345].

EXERCCIOS
Seo 2.2
1. Descreva, informalmente, as linguagens aceitas pelos seguintes FAs:
a.

b.

c.

66 CAPTULO 2 Scanners

2. Construa um FA aceitando cada uma das seguintes linguagens:


a. {w {a, b}* | w comea com a e contm baba como uma substring}
b. {w {0, 1}* | w contm 111 como uma substring e no contm 00 como
uma substring}
c. {w {a, b, c}* | em w o nmero de a's no mdulo 2 igual ao de b's no
mdulo 3}
3. Crie FAs para reconhecer (a) palavras que representem nmeros complexos, e
(b) palavras que representem nmeros decimais escritos em notao cientfica.

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:

a. Use a construo de Thompson para construir um NFA para cada RE.


b. Converta os NFAs para DFAs.
c. Minimize os DFAs.

2.7 Resumo do captulo e perspectiva 67

8. Um modo de provar que duas REs so equivalentes construir seus DFAs


minimizados e depois compar-los. Se diferirem apenas por nomes de estado,
ento as REs so equivalentes. Use esta tcnica para verificar os seguintes pares
de REs e indique se so equivalentes ou no:
a. (0 | 1)* e (0* | 10*)*
b. (ba)+ (a* b* | a*) e (ba)* ba+ (b* | )
9. Em alguns casos, dois estados conectados por um -movimento podem ser
combinados.
a. Sob que conjunto de condies dois estados conectados por um -movimento podem ser combinados?
b. D um algoritmo para eliminar -movimentos.
c. Como seu algoritmo se relaciona com a funo de -fechamento usada
para implementar a construo de subconjunto?
10. Mostre que o conjunto de expresses regulares fechado sob interseo.
11. O algoritmo de minimizao de DFA dado na Figura2.9 formulado para
enumerar todos os elementos de P e todos os caracteres em O em cada iterao
do lao while.
a. Reformule o algoritmo de modo que ele use uma worklist para manter os
conjuntos que ainda precisam ser examinados.
b. Reformule a funo Split de modo que ela particione o conjunto devido
a todos os caracteres em O.
c. Como a complexidade esperada dos seus algoritmos modificados se
compara com a complexidade esperada do algoritmo original?
Seo 2.5
12. Construa um DFA para cada uma das seguintes construes da linguagem C e
depois monte a tabela correspondente para uma implementao controlada por
tabela para cada uma delas:
a. Constantes inteiras.
b. Identificadores.
c. Comentrios.
13. Para cada um dos DFAs no exerccio anterior, monte um scanner codificado
diretamente.
14. Este captulo descreveu diversos estilos de implementaes de DFA. Uma alternativa usaria funes mutuamente recursivas para implementar um scanner.
Discuta as vantagens e desvantagens desta implementao.
15. Para reduzir o tamanho da tabela de transio, o gerador de scanner pode usar
um esquema de classificao de caracteres. No entanto, a gerao da tabela de
classificao parece ser onerosa. O algoritmo bvio exigiria um tempo O(|O|2
|estados|). Derive um algoritmo assintoticamente mais rpido para encontrar
colunas idnticas na tabela de transio.
16. A Figura2.15 mostra um esquema que evita o comportamento de rollback
quadrtico em um scanner criado pela simulao de um DFA. Infelizmente,
este esquema exige que o scanner saiba com antecedncia o tamanho do fluxo
de entrada e que mantenha uma matriz de bits, Failed, de tamanho
|estados||entrada|. Crie um esquema que evite conhecer o tamanho do fluxo
de entrada com antecedncia. Voc consegue usar o mesmo esquema para
reduzir o tamanho da tabela Failed em casos onde a entrada de pior caso no
ocorre?

Captulo

Analisadores Sintticos (Parsers)


VISO GERAL DO CAPTULO
A tarefa do analisador sinttico (parser) determinar se o programa de entrada, representado pelo fluxo de palavras classificadas produzidas pelo scanner, uma sentena
vlida na linguagem de programao. Para tanto, o parser tenta construir uma derivao
para o programa de entrada, usando uma gramtica para a linguagem de programao.
Este captulo introduz as gramticas livres de contexto, uma notao usada para especificar a sintaxe das linguagens de programao. E desenvolve vrias tcnicas para
encontrar uma derivao, dada uma gramtica e um programa de entrada.
Palavras-chave: Parsing, Analisador sinttico, Gramtica, LL(1), LR(1), Descida
recursiva

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

70 CAPTULO 3 Analisadores Sintticos (Parsers)

introduz as gramticas livres do contexto (ou CFGs Context-Free Grammars) como


uma notao para especificar a sintaxe.
Muitos algoritmos foram propostos para responder questo sobre a condio de pertinncia para CFGs. Este captulo examina duas tcnicas diferentes para o problema.
A Seo3.3 introduz a anlise sinttica descendente (top-down) na forma de parsers
de descida recursiva (tambm conhecidos como parsers recursivos descendentes) e
LL(1). A Seo3.4 examina a anlise sinttica ascendente (bottom-up) conforme
exemplificada por parsers LR(1). A Seo3.4.2 apresenta o algoritmo detalhado para
gerar parsers LR(1) cannicos. A ltima seo explora diversas questes prticas que
surgem na construo do parser.

Viso geral
Parsing
Dado um fluxo s de palavras e uma gramtica G, encontrar uma derivao em G que produza s.

O parser de um compilador tem como responsabilidade principal reconhecer a sintaxe


ou seja, determinar se o programa sendo compilado uma sentena vlida no
modelo sinttico da linguagem de programao. Este modelo expresso como uma
gramtica formal G; se alguma sequncia de palavras s est na linguagem definida por
G, dizemos que G deriva s. Para um fluxo de palavras s e uma gramtica G, o parser
tenta montar uma prova construtiva de que s pode ser derivado em G processo
chamado parsing.
Algoritmos de parsing podem ser de duas categorias gerais. Os parsers top-down
tentam combinar o fluxo de entrada com as produes da gramtica prevendo a prxima
palavra (em cada ponto). Para uma classe de gramticas limitada, esta previso pode
ser precisa e eficiente. Os parsers bottom-up trabalham a partir do detalhe em baixo
nvel a sequncia real de palavras e acumulam o contexto at que a derivao seja
aparente. Novamente, existe uma classe restrita de gramticas para as quais podemos
gerar parsers bottom-up eficientes. Na prtica, esses conjuntos restritos de gramticas
so grandes o suficiente para compreender a maioria dos recursos de interesse nas
linguagens de programao.

3.2 EXPRESSANDO A SINTAXE


A tarefa do parser determinar se algum fluxo de palavras se encaixa ou no na sintaxe
da linguagem-fonte que lhe cabe examinar. Implcita nesta descrio est a noo de
que podemos descrever a sintaxe e verific-la; na prtica, precisamos de uma notao
para descrever a sintaxe das linguagens que as pessoas poderiam usar para programar
computadores. No Captulo2, trabalhamos com uma notao assim, as expresses
regulares, que fornecem uma notao concisa para descrever sintaxe e um mecanismo
eficaz para testar a condio de pertinncia de uma string na linguagem descrita por
uma RE. Infelizmente, REs no tm o poder de descrever a sintaxe complexa da maioria
das linguagens de programao.
Para estas, a sintaxe expressa na forma de uma gramtica livre de contexto. Esta
seo apresenta e define as CFGs e explora seu uso na verificao da sintaxe. Mostra
como podemos comear a codificar significado na sintaxe e na estrutura. E, finalmente,
apresenta as ideias em que se baseiam as tcnicas de parsing eficientes, descritas nas
sees seguintes.

3.2.1 Por que no expresses regulares?


Para motivar o uso de CFGs, considere o problema de reconhecer expresses algbricas sobre variveis e os operadores +, , e . Podemos definir varivel como
qualquer string que corresponda RE [a...z]([a...z] | [0...9])*, verso simplificada e

3.2 Expressando a sintaxe 71

em minsculas de um identificador Algol. Agora, podemos definir uma expresso da


seguinte forma:

Esta RE corresponde a a + b c e fee fie foe. Nada a respeito da RE


sugere uma noo de precedncia de operador; em a + b c, qual operador deve
ser executado primeiro,+ou ? A regra-padro da lgebra sugere quee tm
precedncia sobre+e . Para forar outras ordens de avaliao, a notao algbrica
normal inclui parnteses.
A incluso de parnteses RE nos locais onde precisam aparecer um pouco delicada.
Uma expresso pode comear com (, de modo que precisamos da opo para um (
inicial. De modo semelhante, precisamos da opo para um ) final.

Sublinharemos ( e ) de modo que sejam visualmente


distintos dos ( e ) usados para o agrupamento nas REs.

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:

Observe que simplesmente mudamos o ) final para dentro do fechamento.


Esta RE combina com a+bc e com ( a+b)c. E combinar com qualquer
expresso com uso correto de parnteses sobre variveis e dos quatro operadores na
RE. Mas, infelizmente, tambm combinar com muitas expresses sintaticamente
incorretas, como a+(bc e a+b)c). De fato, no podemos escrever uma
RE que combine com todas as expresses com parnteses balanceados. (Construes
emparelhadas, como begin e end ou then e else, desempenham papel importante
na maioria das linguagens de programao.) Este fato uma limitao fundamental
das REs; os reconhecedores correspondentes no podem contar, pois tm apenas
um conjunto finito de estados. A linguagem (m)n, onde m=n, no regular. Em
princpio, os DFAs no podem contar. Embora funcionem bem para microssintaxe,
no so adequados para descrever alguns recursos importantes das linguagens de
programao.

3.2.2 Gramticas livres de contexto


Para descrever a sintaxe de linguagens de programao, precisamos de uma notao mais
poderosa do que as expresses regulares, e que ainda leve a reconhecedores eficientes. A
soluo tradicional usar uma gramtica livre de contexto (CFG). Felizmente, grandes
subclasses das CFGs tm a propriedade de levar a reconhecedores eficientes.

Gramtica livre de contexto


Para uma linguagem L, sua CFG define os conjuntos de
strings de smbolos que so sentenas vlidas em L.

72 CAPTULO 3 Analisadores Sintticos (Parsers)

Sentena
String de smbolos que podem ser derivados das regras
de uma gramtica.

Uma gramtica livre de contexto, G, um conjunto de regras que descrevem como


formar sentenas. A coleo de sentenas que podem ser derivadas de G chamada
linguagem definida por G, indicada como L(G). O conjunto de linguagens definidas por
gramticas livres de contexto chamado de linguagens livres de contexto. Um exemplo
pode ajudar. Considere a seguinte gramtica, que chamamos de SN:

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.

Para entender o relacionamento entre a gramtica SN e L(SN), precisamos especificar


como aplicar as regras da SN para derivar sentenas em L(SN). Para comear, temos que
identificar o smbolo-alvo, ou smbolo inicial da SN, que representa o conjunto de todas
as strings em L(SN). Desta forma, ele no pode ser uma das palavras na linguagem. Ao
invs disso, deve ser um dos smbolos no terminais introduzidos para acrescentar estrutura e abstrao linguagem. Como a SN tem apenas um no terminal, SomOvelha
deve ser o smbolo-alvo.

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:

Isto completamente equivalente nossa gramtica SN.


BNF tem suas origens no final da dcada de 1950 e incio da de 1960 [273]. As
convenes sintticas dos sinais , sublinhado, ::= e | surgiram a partir de opes
tipogrficas limitadas disponveis queles que escreviam descries de linguagem.
(Por exemplo, veja o livro de David Gries, Compiler Construction for Digital Computers,
impresso inteiramente em uma impressora de linha padro [171].) Em todo este livro,
usamos uma forma de BNF tipograficamente atualizada. No terminais so escritos
em itlico; terminais em uma fonte monoespaada. Usamos o smbolo para
deriva.

3.2 Expressando a sintaxe 73

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.

GRAMTICAS LIVRES DE CONTEXTO


Formalmente, gramtica livre de contexto G uma qudrupla (T, NT, S, P), onde:
T o conjunto de smbolos terminais, ou palavras, na linguagem L(G). Smbolos
terminais correspondem s categorias sintticas retornadas pelo scanner.
NT o conjunto de smbolos no terminais que aparecem nas produes de G. No
terminais so variveis sintticas introduzidas para fornecer abstrao e estrutura
nas produes.
S um no terminal designado como smbolo-alvo, ou smbolo inicial da gramtica. S
representa o conjunto de sentenas em L(G).
P o conjunto de produes ou regras de reescrita em G. Cada regra em P tem a
forma NT (T NT)+; ou seja, ela substitui um no terminal por uma string de um
ou mais smbolos da gramtica.
Os conjuntos T e NT podem ser derivados diretamente do conjunto de produes, P.
O smbolo inicial pode ser no ambguo, como na gramtica SomOvelha, ou no ser
bvio, como na gramtica a seguir:
Parntese

(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

74 CAPTULO 3 Analisadores Sintticos (Parsers)

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

Reescrever com a regra 2

Regra

Forma sequencial

1
2

SomOvelha
baa SomOvelha
baa baa

Reescrever com as regras 1 e depois 2

Como uma convenincia de notao, usaremos + para indicar deriva em uma ou


mais etapas. Assim, SomOvelha + baa e SomOvelha + baa baa.
A regra 1 estende a string, enquanto a 2 elimina o no terminal SomOvelha. (A string
nunca pode conter mais de uma ocorrncia de SomOvelha.) Todas as strings vlidas
em SN so derivadas por zero ou mais aplicaes da regra 1, seguidas pela regra 2.
A aplicao da regra 1 k vezes seguida pela regra 2 gera uma string com k+1 baas.

3.2.3 Exemplos mais complexos


A gramtica SomOvelha muito simples para exibir a potncia e a complexidade das
CFGs. Em vez disso, vamos retornar ao exemplo que mostrou as deficincias das REs:
a linguagem de expresses com parnteses.
1

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

Derivao mais direita de ( a+b )c

3.2 Expressando a sintaxe 75

A rvore acima, chamada rvore de anlise ou rvore sinttica, representa a derivao


como um grafo.

rvore de anlise ou rvore sinttica


Grafo que representa uma derivao.

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

Derivao mais esquerda de ( a+b )c

Derivao mais direita


Derivao que reescreve, a cada etapa, o no terminal
mais direita.
Derivao mais esquerda
Derivao que reescreve, a cada etapa, o no terminal
mais esquerda.

76 CAPTULO 3 Analisadores Sintticos (Parsers)

As derivaes mais esquerda e mais direita utilizam o mesmo conjunto de regras;


e aplicam essas regras em uma ordem diferente. Como a rvore sinttica representa as
regras aplicadas, mas no a ordem de sua aplicao, as rvores sintticas para ambas
as derivaes so idnticas.
Ambiguidade
Uma gramtica G ambgua se alguma sentena em
L(G) tiver mais de uma derivao mais direita (ou
mais esquerda).

Do ponto de vista do compilador, importante que cada sentena na linguagem


definida pela CFG tenha uma nica derivao mais direita (ou mais esquerda). Se
existirem vrias derivaes mais direita (ou mais esquerda) para alguma sentena,
ento, em algum ponto na derivao vrias reescritas distintas do no terminal mais
direita (ou mais esquerda) levam mesma sentena. Uma gramtica em que
existem vrias derivaes mais direita (ou mais esquerda) para uma sentena
chamada gramtica ambgua. Uma gramtica ambgua pode produzir vrias
derivaes e vrias rvores sintticas. Como os estgios posteriores da traduo
associaro significado forma detalhada da rvore sinttica, vrias destas rvores
implicam vrios significados possveis para um nico programa uma propriedade
ruim para uma linguagem de programao ter. Se o compilador no puder ter certeza
do significado de uma sentena, ele no poder traduzi-la para uma sequncia de
cdigo definitiva.
O exemplo clssico de uma construo ambgua na gramtica para uma linguagem
de programao a construo if-then-else de muitas linguagens tipo Algol. A
gramtica simples para if-then-else poderia ser

if Expr then Comando else Comando

if Expr then Comando

3
4

|
|

Atribuio
...outros comandos. . .

Comando

Este fragmento mostra que else opcional.


Infelizmente, o fragmento de cdigo

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:

3.2 Expressando a sintaxe 77

A segunda derivao associa a clusula else com o primeiro if, de modo que
Atribuio2 executada quando Expr1 falsa, independente do valor de Expr2:

Claramente, essas duas derivaes produzem comportamentos diferentes no cdigo


compilado.
Para remover essa ambiguidade, a gramtica deve ser modificada para incluir uma
regra que determina qual if controla um else. Para resolver a gramtica do if
-then-else, podemos reescrev-la como:
1
2
3
4
5

Comando

WithElse

|
|

if Expr then Comando


if Expr then WithElse else
Comando
Atribuio
if Expr then WithElse else
WithElse
Atribuio

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

A gramtica reescrita elimina a ambiguidade.


A ambiguidade do if-then-else surge de uma deficincia na gramtica original.
A soluo resolve a ambiguidade impondo uma regra que fcil para o programador
se lembrar. (Para evitar a ambiguidade totalmente, alguns projetistas de linguagem
reestruturaram a construo if-then-else introduzindo elseif e endif.)
Na Seo 3.5.3, vamos examinar outros tipos de ambiguidade e formas sistemticas
de trat-las.

78 CAPTULO 3 Analisadores Sintticos (Parsers)

3.2.4 Codificao do significado na estrutura


A ambiguidade do if-then-else aponta o relacionamento entre significado e estrutura gramatical. Porm, ambiguidade no a nica situao em que o significado
e a estrutura gramatical interagem. Considere a rvore sinttica que seria montada a
partir da derivao mais direita da expresso simples a + b c.
Regra

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

Um modo natural de avaliar a expresso com um percurso em ps-ordem na rvore.


Isto primeiro calcularia a + b, e depois multiplicaria o resultado por c para produzir
o resultado (a + b) c. Essa ordem de avaliao contradiz as regras clssicas de
precedncia algbrica, que avaliaria a expresso como a + (b c). Como o objetivo
final da anlise sinttica da expresso produzir o cdigo que a implementar, a
gramtica da expresso deveria ter a propriedade de montar uma rvore cuja avaliao
pelo percurso natural produzisse o resultado correto.
O problema real est na estrutura da gramtica. Ela trata todos os operadores aritmticos
da mesma forma, sem considerar qualquer precedncia. Na rvore sinttica para (a +
b) c, o fato de que a subexpresso entre parnteses foi forada a passar por uma
produo extra na gramtica aumenta um nvel rvore. Este nvel extra, por sua vez,
fora um percurso em ps-ordem na rvore a avaliar a subexpresso entre parnteses
antes de avaliar a multiplicao.

3.2 Expressando a sintaxe 79

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

FIGURA 3.1 Gramtica de expresso clssica.

80 CAPTULO 3 Analisadores Sintticos (Parsers)

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.

3.2 Expressando a sintaxe 81

CLASSES DE GRAMTICAS LIVRES DE CONTEXTO E SEUS PARSERS


Podemos particionar o universo das gramticas livres de contexto em uma hierarquia
com base na dificuldade da anlise sinttica das gramticas. Essa hierarquia tem
muitos nveis. Este captulo menciona quatro deles, a saber: CFGs arbitrrias,
gramticas LR(1), gramticas LL(1) e gramticas regulares (RGs). Esses conjuntos so
aninhados conforme mostra o diagrama a seguir.

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.

3.2.5 Descobrindo uma derivao para uma string de entrada


Vimos como usar uma CFG G como um sistema de reescrita para gerar sentenas que
esto em L(G). Ao contrrio, um compilador precisa inferir uma derivao para uma
determinada string de entrada, ou determinar que no existe tal derivao. O processo
de construir uma derivao a partir de uma sentena de entrada especfica chamado
parsing (ou anlise sinttica).

82 CAPTULO 3 Analisadores Sintticos (Parsers)

Um parser utiliza, como entrada, um suposto programa escrito em alguma linguagem


fonte.
O parser v o programa conforme ele vem do scanner: um fluxo de palavras associadas com suas categorias sintticas. Assim, o parser veria a + b c como
nome,a + nome,bnome,c. Como sada, o parser precisa produzir, ou
uma derivao para o programa de entrada, ou uma mensagem de erro para um programainvlido. Para uma linguagem no ambgua, uma rvore sinttica equivalente
a uma derivao; assim, podemos pensar na sada do parser como uma rvore sinttica.
til visualizar o parser como criando uma rvore sinttica para o programa de entrada.
A raiz da rvore sinttica conhecida; ela representa o smbolo inicial da gramtica. As
folhas desta rvore tambm o so; elas precisam corresponder, na ordem da esquerda
para a direita, ao fluxo de palavras retornado pelo scanner. A parte difcil da anlise
sinttica est na descoberta da conexo gramatical entre as folhas e a raiz. Duas tcnicas
distintas e opostas para construir a rvore so sugeridas:
1. Parsers top-down comeam com a raiz e fazem a rvore crescer em direo
s folhas. A cada etapa, ele seleciona um n para algum no terminal na borda
inferior da rvore e o estende como uma subrvore que representa o lado direito
de uma produo que reescreve o no terminal.
2. Parsers bottom-up comeam com as folhas e fazem a rvore crescer em direo
raiz. Em cada etapa, ele identifica uma substring contgua da borda superior da
rvore, que corresponde ao lado direito de alguma produo; depois, constri um
n para o lado esquerdo da regra e o conecta rvore.
Em qualquer cenrio, o parser faz uma srie de escolhas sobre quais produes aplicar.
A maior parte da complexidade intelectual na anlise sinttica encontra-se nos mecanismos para fazer essas escolhas. A Seo3.3 explora os aspectos e os algoritmos
que surgem na anlise sinttica top-down, enquanto a Seo3.4 examina a anlise
bottom-up em detalhes.

3.3 ANLISE SINTTICA DESCENDENTE (TOP-DOWN)


Um parser top-down comea com a raiz da rvore sinttica e a estende sistematicamente
para baixo at que suas folhas correspondam s palavras classificadas retornadas pelo
scanner. Em cada ponto, o processo considera uma rvore sinttica parcialmente construda. Ele seleciona um smbolo no terminal na borda inferior da rvore e o estende
acrescentando filhos, que correspondem ao lado direito de alguma produo para esse
no terminal. Ele no pode estender a fronteira a partir de um terminal. Esse processo
continua at que:
a. A borda da rvore sinttica contenha apenas smbolos terminais e o fluxo de
entrada tenha sido esgotado.
b. Uma divergncia clara ocorra entre a borda da rvore sinttica parcialmente
construda e o fluxo de entrada.
No primeiro caso, a anlise tem sucesso. No segundo, duas situaes so possveis. O
parser pode ter selecionado a produo errada em alguma etapa anterior no processo,
e pode retroceder, reconsiderando sistematicamente as decises anteriores. Para uma
string de entrada que uma sentena vlida, o retrocesso (backtrack) levar o parser
a uma sequncia correta de escolhas e permitir que ele construa uma rvore sinttica
correta. Alternativamente, se a string de entrada no for uma sentena vlida, o retrocesso falhar e o parser dever relatar o erro de sintaxe ao usurio.

3.3 Anlise sinttica descendente (top-down) 83

Uma ideia-chave torna a anlise sinttica top-down eficiente: um grande subconjunto


das gramticas livres de contexto pode ser analisado sem retrocesso. A Seo3.3.1
mostra transformaes que normalmente podem converter uma gramtica qualquer em
outra adequada para a anlise sinttica top-down livre de retrocesso. As duas sees
seguintes introduzem duas tcnicas distintas para construir os parsers top-down: analisadores sintticos de descida recursiva codificados mo, e analisadores LL(1) gerados.
A Figura3.2 mostra um algoritmo concreto para um parser top-down que constri
uma derivao mais esquerda. Ele monta uma rvore sinttica, ancorada na varivel
root. Ele usa uma pilha, com funes de acesso push( ) e pop( ), para rastrear
a parte no correspondida da borda.
A parte principal do parser consiste em um lao que focaliza o smbolo no correspondido mais esquerda na borda inferior da rvore sinttica parcialmente construda.
Se o smbolo em foco um no terminal, ele expande a rvore para baixo; escolhe
uma produo, monta a parte correspondente da rvore de anlise e move o foco para
o smbolo mais esquerda dessa nova parte da borda. Se o smbolo em foco for um
terminal, ele compara o foco contra a prxima palavra na entrada. Uma correspondncia
move o foco para o prximo smbolo na borda e avana o fluxo de entrada.
Se o foco um smbolo terminal que no corresponde entrada, o parser precisa
retroceder. Primeiro, ele sistematicamente considera alternativas para a regra escolhida
mais recentemente. Se esgotar essas alternativas, retrocede de volta na rvore sinttica
e reconsidera as escolhas em um nvel mais alto. Se este processo no conseguir corres-

FIGURA 3.2 Algoritmo de anlise sinttica top-down mais esquerda.

84 CAPTULO 3 Analisadores Sintticos (Parsers)

ponder entrada, o parser informa um erro de sintaxe. O retrocesso aumenta o custo


assinttico da anlise sinttica; na prtica, este um modo dispendioso de descobrir
erros de sintaxe.
Para facilitar a descoberta da prxima regra, o parser
pode armazenar o nmero da regra em um n no
terminal quando o expande.

A implementao do retrocesso (backtrack) simples. Ele estabelece focus


como o pai na rvore sinttica parcialmente construda e desconecta seus filhos. Se
ainda restar uma regra no tentada com focus em seu lado esquerdo, o parser expande focus usando essa regra. E constri filhos para cada smbolo no lado direito,
empilha esses smbolos na ordem da direita para a esquerda e define focus para
que aponte para o primeiro filho. Se no restar nenhuma regra no tentada, o parser
sobe outro nvel e tenta novamente. Quando esgota as possibilidades, informa um
erro de sintaxe e sai.
Ao realizar o retrocesso, o parser tambm precisa recuar o fluxo de entrada. Felizmente,
a rvore sinttica parcial codifica informaes suficientes para tornar esta ao eficiente.
O parser precisa colocar cada terminal correspondido na produo descartada de volta
ao fluxo de entrada, ao que pode ser realizada enquanto os desconecta da rvore
sinttica na travessia da esquerda para a direita dos filhos descartados.

3.3.1 Transformao de uma gramtica para anlise sinttica


top-down
A eficincia de um parser top-down depende criticamente da sua capacidade de
escolher a produo correta toda vez que expandir um no terminal. Se o parser
sempre fizer a escolha certa, a anlise sinttica top-down eficiente. Se restar
escolhas ruins, o custo da anlise aumenta. Para algumas gramticas, o comportamento de pior caso que o parser no termina. Esta seo examina duas questes
estruturais com CFGs que ocasionam problemas com parsers top-down e apresenta
transformaes que o construtor de compiladores pode aplicar gramtica para
evitar esses problemas.

Um parser top-down com escolha oracular


Como exerccio inicial, considere o comportamento do parser da Figura3.2 com a
gramtica de expresso clssica na Figura3.1 quando aplicada string a + b c.
Por enquanto, considere que o parser tem um orculo que escolhe a produo
correta em cada ponto na anlise. Com a escolha oracular, ele pode proceder conforme
mostra a Figura3.3. A coluna da direita exibe a string de entrada, com um marcador
para indicar a posio atual do parser na string. O smbolo na coluna Regra
representa uma etapa em que o parser corresponde a um smbolo terminal contra a
string de entrada e avana a entrada. A cada etapa, a forma sentencial representa a
borda inferior da rvore sinttica parcialmente construda.
Com a escolha oracular, o parser dever usar um nmero de etapas proporcional ao
tamanho da derivao mais o tamanho da entrada. Para a + b c, o parser aplicou
oito regras e efetuou correspondncia com cinco palavras.
Observe, porm, que a escolha oracular significa escolha inconsistente. Nas primeira
e segunda etapas, o parser considerou o no terminal Expr. Na primeira, ele aplicou
a regra 1, Expr Expr+Termo. Na segunda, aplicou a regra 3, Expr Termo. De
modo semelhante, ao expandir Termo em uma tentativa de correspondncia com a,
aplicou a regra 6, Termo Fator, mas, ao expandir Termo para corresponder com
b, aplicou a regra 4, Termo TermoFator. Seria difcil fazer o parser top-down
funcionar com escolha consistente, algortmica, ao usar esta verso da gramtica de
expresso.

3.3 Anlise sinttica descendente (top-down) 85

FIGURA 3.3 Anlise sinttica top-down mais esquerda de a+bc com escolha oracular.

Eliminao da recurso esquerda


Um problema com a combinao da gramtica de expresso clssica e um parser topdown mais esquerda surge devido estrutura da gramtica. Para ver a dificuldade,
considere uma implementao que sempre tenta aplicar as regras na ordem em que
aparecem na gramtica. Suas primeiras, vrias, aes seriam:
Regra

Forma sentencial

Entrada

1
1
1

Expr
Expr+Termo
Expr+Termo+Termo

nome + nome nome


nome + nome nome
nome + nome nome
nome + nome nome

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.

86 CAPTULO 3 Analisadores Sintticos (Parsers)

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

A transformao introduz um novo no terminal, Fee9, e transfere a recurso para Fee9.


Tambm acrescenta a regra Fee9 , onde representa a string vazia. Esta -produo exige interpretao cuidadosa no algoritmo de anlise sinttica. Para expandir a
produo Fee9 , o parser simplesmente faz focus pop( ), que avana sua
ateno para o prximo n, terminal ou no terminal, na borda.
Na gramtica de expresso clssica, a recurso esquerda direta aparece nas produes
tanto para Expr quanto para Termo.
Original
Expr

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

Fazendo essas substituies na gramtica de expresso clssica gera-se uma variante


recursiva direita da gramtica, mostrada na Figura3.4. Esta nova gramtica especifica
o mesmo conjunto de expresses que a gramtica de expresso clssica.

FIGURA 3.4 Variante recursiva direita da gramtica de expresso clssica.

3.3 Anlise sinttica descendente (top-down) 87

FIGURA 3.5 Anlise sinttica top-down mais esquerda de a+bc com a gramtica de expresso
recursiva direita.

A gramtica nesta figura elimina o problema de no terminao, mas no evita a


necessidade de retrocesso. A Figura3.5 mostra o comportamento do parser top-down
com essa gramtica na entrada a+bc. O exemplo ainda assume a escolha oracular
(resolveremos este problema na prxima subseo), consegue a correspondncia com
todos os 5 terminais e aplica 11 produes 3 a mais do que na gramtica recursiva
esquerda. Todas as aplicaes de regra adicionais envolvem produes que derivam .
Esta transformao simples elimina a recurso esquerda direta. Tambm precisamos
eliminar a recurso esquerda indireta, que ocorre quando uma cadeia de regras como
a b, b g e g ad cria a situao em que a +ad. Esta recurso esquerda indireta nem sempre bvia, e pode se tornar obscura por uma longa cadeia de produes.
Para converter a recurso esquerda indireta em recurso direita, precisamos de um
mtodo mais sistemtico do que a inspeo seguida pela aplicao da nossa transformao. O algoritmo na Figura3.6 elimina toda a recurso esquerda de uma gramtica
pela aplicao completa de duas tcnicas: substituio adiante para converter a recurso
esquerda indireta em recurso esquerda direta, e reescrita da recurso esquerda
direta como recurso direita, assumindo que a gramtica original no possui ciclos
(A + A) nem -produes.

88 CAPTULO 3 Analisadores Sintticos (Parsers)

FIGURA 3.6 Remoo da recurso esquerda indireta.

O algoritmo impe uma ordem arbitrria sobre os no terminais. O lao externo os


percorre nessa ordem. O lao interno procura qualquer produo que expanda Ai em um
lado direito que comea com Aj, para j<i. Essa expanso pode levar a uma recurso
esquerda indireta. Para evitar isto, o algoritmo substitui a ocorrncia de Aj por todos os
lados direitos alternativos para Aj. Ou seja, se o lao interno descobrir uma produo
Ai Ajg , e Aj d1|d2 | |dk, ento o algoritmo substitui Ai Ajg por um conjunto
deprodues Ai d1g |d2g | |dkg. Este processo, eventualmente, converte cada
recurso esquerda indireta em uma recurso esquerda direta. A etapa final no lao
externo converte qualquer recurso esquerda direta sobre Ai em recurso direita
usando a transformao simples mostrada anteriormente. Como novos no terminais
so acrescentados ao final e s envolvem recurso direita, o lao pode ignor-los
eles no precisam ser verificados e convertidos.
Considerando o invariante de lao para o lao externo pode esclarecer mais. No incio
da i-sima iterao de lao externo:

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.

Anlise sinttica livre de retrocesso


A principal fonte de ineficincia no parser top-down mais esquerda surge da sua
necessidade de retroceder. Se o parser expandir a borda inferior com a produo
errada, eventualmente encontra uma divergncia entre esta borda e as folhas da
rvore sinttica, que correspondem s palavras retornadas pelo scanner. Quando ele
descobre a divergncia, precisa desfazer as aes que resultaram na borda errada e
tentar outras produes. O ato de expandir, retrair e reexpandir a borda desperdia
tempo e esforo.
Na derivao da Figura3.5, o parser escolheu a regra correta em cada etapa. Escolhendo
consistentemente as regras, como considerar as produes na ordem de aparecimento
na gramtica, ele teria retrocedido em cada nome, primeiro tentando Fator (Expr),

3.3 Anlise sinttica descendente (top-down) 89

e depois Fator num, antes de derivar nome. De modo semelhante, as expanses


pelas regras 4 e 8 teriam considerado as outras alternativas antes de expandir para .
Para esta gramtica, o parser pode evitar o retrocesso com uma modificao simples.
Quando ele for selecionar a prxima produo, pode considerar tanto o smbolo em foco
quanto o prximo smbolo de entrada, chamado smbolo de antecipao. Usando este
nico smbolo de antecipao, o parser pode distinguir todas as escolhas que surgem
na anlise sinttica pela gramtica de expresso recursiva direita. Assim, dizemos
que a gramtica livre de retrocesso com antecipao de um smbolo. Uma gramtica
livre de retrocesso tambm chamada gramtica preditiva.

Gramtica livre de retrocesso


CFG para a qual o parser top-down, mais esquerda,
sempre pode prever a regra correta com antecipao de
no mximo uma palavra.

Podemos formalizar a propriedade que torna a gramtica de expresso recursiva


direita livre de retrocesso. Em cada ponto na anlise, a escolha de uma expanso
bvia porque cada alternativa para o no terminal mais esquerda leva a um smbolo
terminal distinto. A comparao da prxima palavra no fluxo de entrada com essas
escolhas revela a expanso correta.
A intuio clara, mas formaliz-la exigir alguma notao. Para cada smbolo a
da gramtica, defina FIRST(a) como o conjunto de smbolos terminais que podem
aparecer como a primeira palavra em alguma string derivada de a. O domnio de
FIRST o conjunto de smbolos da gramtica, T NT {, eof} e seu contradomnio T {, eof}. Se a um terminal, ou eof, ento FIRST(a) tem
exatamente um membro, a. Para um no terminal A, FIRST(A) contm o conjunto
completo de smbolos terminais que podem aparecer como smbolo inicial em uma
forma sentencial derivada de A.
A Figura3.7 mostra um algoritmo que calcula os conjuntos FIRST para cada smbolo
em uma gramtica. Como sua etapa inicial, o algoritmo define os conjuntos FIRST
para os casos simples, terminais, e eof. Para a gramtica de expresso recursiva
direita mostrada na Figura3.4, esta etapa inicial produz os seguintes conjuntos FIRST:

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

Definimos conjuntos FIRST sobre smbolos nicos da gramtica. Agora, conveniente


estender esta definio para strings de smbolos. Para uma string s=b1 b2 b3... bk,
definimos FIRST(s) como a unio dos conjuntos FIRST para b1, b2, b3,... bn, onde bn
o primeiro smbolo cujo conjunto FIRST no contm , e FIRST(s) se, e somente
se, ele estiver no conjunto para cada um dos bi, 1ik. O algoritmo na Figura3.7
calcula esta quantidade para a varivel rhs.

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.

90 CAPTULO 3 Analisadores Sintticos (Parsers)

FIGURA 3.7 Calculando conjuntos FIRST para smbolos de uma gramtica.

Conceitualmente, os conjuntos FIRST simplificam a implementao de um parser


top-down. Considere, por exemplo, as regras para Expr9 na gramtica de expresso
recursiva direita:

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.

3.3 Anlise sinttica descendente (top-down) 91

Intuitivamente, o parser deve aplicar a -produo quando o smbolo de antecipao


no for um membro do conjunto FIRST de qualquer outra alternativa. Para diferenciar
entre entradas legais e erros de sintaxe, o parser precisa saber quais palavras podem
aparecer como smbolo inicial aps uma aplicao vlida da regra 4 o conjunto de
smbolos que podem vir aps Expr9.
Para capturar este conhecimento, definimos o conjunto FOLLOW(Expr9) para conter
todas as palavras que podem ocorrer imediatamente direita de uma string derivada
de Expr9. A Figura3.8 apresenta um algoritmo para calcular este conjunto para cada
no terminal em uma gramtica, considerando a existncia de conjuntos FIRST. O
algoritmo inicializa cada conjunto FOLLOW como conjunto vazio, e depois, percorre
as produes, calculando a contribuio dos sufixos parciais para o conjunto FOLLOW
de cada smbolo em cada lado direito. O algoritmo termina quando alcana um ponto
fixo. Para a gramtica de expresso recursiva direita, o algoritmo produz:

FOLLOW

Expr
eof, )

Expr
eof, )

Termo
eof, +, , )

Termo
eof, +, , )

FIGURA 3.8 Calculando conjuntos FOLLOW para smbolos no terminais.

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.

92 CAPTULO 3 Analisadores Sintticos (Parsers)

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

Qualquer gramtica que tenha esta propriedade livre de retrocesso.


Para a gramtica de expresso recursiva direita, somente as produes 4 e 8 tm
conjuntos FIRST+ que diferem dos conjuntos FIRST.

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 para eliminar o retrocesso


Nem todas as gramticas so livres de retrocesso. Para ver um exemplo deste tipo de
gramtica, considere a extenso da gramtica de expresso para incluir chamadas de
funo, indicadas com parnteses, ( e ), e referncias a elementos de array, indicadas
com colchetes, [ e ]. Para acrescentar essas opes, substitumos a produo 11,
Fator nome, por um conjunto de trs regras, mais um conjunto de regras recursivas
direita para listas de argumentos.

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-

3.3 Anlise sinttica descendente (top-down) 93

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 )

A reescrita quebra a derivao de Fator em duas etapas. A primeira corresponde ao


prefixo comum das regras 11, 12 e 13. A segunda reconhece trs sufixos distintos:
[ Expr ], ( Expr ) e . A reescrita acrescenta um novo no terminal, Argumentos, e
empurra os sufixos alternativos de Fator para os lados direitos de Argumentos. Chamamos esta transformao de fatorao esquerda.
Podemos fatorar esquerda qualquer conjunto de regras que tenha lados direitos
alternativos com um prefixo comum. A transformao toma um no terminal e suas
produes:

onde a o prefixo comum e gi's representam os lados direitos que no comeam


com a. A transformao introduz um novo no terminal B para representar os sufixos
alternativos para a e reescreve as produes originais de acordo com o padro:

Para fatorar esquerda uma gramtica completa, temos de inspecionar cada no


terminal, descobrir prefixos comuns e aplicar a transformao de forma sistemtica.
Por exemplo, no padro acima, devemos considerar a fatorao dos lados direitos de
B, pois dois ou mais dos bi's poderiam compartilhar um prefixo. O processo termina
quando todos os prefixos comuns tiverem sido identificados e reescritos.
Fatorar esquerda com frequncia pode eliminar a necessidade de retrocesso. Porm,
algumas linguagens livres de contexto no possuem gramtica livre de retrocesso.
Dada uma CFG qualquer, o construtor de compilador pode sistematicamente eliminar
a recurso esquerda e usar a fatorao esquerda para eliminar prefixos comuns.
Essas transformaes podem produzir uma gramtica livre de retrocesso. Em geral,
porm, indecidvel o problema de determinar se existe ou no uma gramtica livre
de retrocesso para uma linguagem livre de contexto qualquer.

Uma antecipao de duas palavras trataria deste caso.


Porm, para qualquer antecipao finita, podemos
idealizar uma gramtica onde esta antecipao seja
insuficiente.

94 CAPTULO 3 Analisadores Sintticos (Parsers)

PARSERS PREDITIVOS VERSUS DFAS


Anlise sinttica preditiva a extenso natural do raciocno estilo-DFA sobre os parsers.
Um DFA passa de um estado para outro com base unicamente no prximo caractere
da entrada. Um parser preditivo escolhe uma expanso com base na prxima palavra
no fluxo de entrada. Assim, para cada no terminal na gramtica, deve haver um
mapeamento exclusivo da primeira palavra em qualquer string de entrada aceitvel
para uma produo especfica que leva a uma derivao para essa string. A diferena
real em potncia entre um DFA e uma gramtica analisvel preditivamente deriva
do fato de que uma predio pode levar a um lado direito com muitos smbolos,
enquanto, em uma gramtica regular, ela prev apenas um nico smbolo. Isso
permite que as gramticas preditivas incluam produes como p (p), que esto
alm do poder de descrever de uma expresso regular. (Lembre-se de que uma
expresso regular pode reconhecer (+ O* )+, mas isso no especifica que os nmeros de
abre parnteses e fecha parnteses devem corresponder.)
Naturalmente, um parser de descida recursiva codificado mo pode usar quaisquer
truques para remover a ambiguidade das escolhas de produo. Por exemplo, se um
lado esquerdo em particular no puder ser previsto com uma antecipao de nico
smbolo, o parser poderia usar dois smbolos. Feito de maneira prudente, isto no deve
causar problemas.

3.3.2 Parsers de descida recursiva


Gramticas livres de retrocesso prestam-se a uma anlise sinttica simples e eficiente
com um paradigma chamado descida recursiva. O parser de descida recursiva (tambm
conhecido como parser descendente recursivo) estruturado como um conjunto de
procedimentos mutuamente recursivos, um para cada no terminal na gramtica. O
procedimento correspondente ao no terminal A reconhece uma ocorrncia de A no
fluxo de entrada. Para reconhecer um no terminal B em algum lado direito de A, o
parser chama o procedimento correspondente a B. Assim, a prpria gramtica serve
como um guia para a implementao do parser.
Considere as trs regras para Expr9 na gramtica de expresso recursiva direita:

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.

3.3 Anlise sinttica descendente (top-down) 95

FIGURA 3.9 Implementao de EPrime().

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.

3.3.3 Parsers LL(1) dirigidos por tabela


Seguindo as ideias em que se baseiam os conjuntos FIRST+, podemos gerar automaticamente parsers top-down para gramticas livres de retrocesso. A ferramenta constri os
conjuntos FIRST, FOLLOW e FIRST+. Os conjuntos FIRST+ ditam completamente as
decises de anlise sinttica, de modo que a ferramenta pode ento construir um parser
top-down eficiente. O parser resultante chamado parser LL(1), nome que deriva do
fato de que esses parsers varrem sua entrada da esquerda (Left) para a direita, constroem uma derivao mais esquerda (Left) e usam um (1) smbolo de antecipao. As
gramticas que funcionam neste esquema LL(1) normalmente so chamadas gramticas
LL(1), que so, por definio, livres de retrocesso.
Para construir um parser LL(1), o construtor de compiladores fornece uma gramtica
recursiva direita, livre de retrocesso, e um gerador sinttico constri o parser real.

96 CAPTULO 3 Analisadores Sintticos (Parsers)

FIGURA 3.10 Parser de descida recursiva para expresses.

3.3 Anlise sinttica descendente (top-down) 97

A tcnica de implementao mais comum para um gerador de parser LL(1) usa um


esqueleto de parser controlado por tabela, como o mostrado no topo da Figura3.11.
O gerador de parser constri a tabela, Table, que codifica as decises de anlise e
dirige o esqueleto de parser. A parte inferior da Figura3.11 mostra a tabela LL(1) para
a gramtica de expresso recursiva direita mostrada na Figura3.4.

FIGURA 3.11 Parser LL(1) para expresses.

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.

98 CAPTULO 3 Analisadores Sintticos (Parsers)

No esqueleto do parser, a varivel foco mantm o prximo smbolo da gramtica na


borda inferior da rvore sinttica parcialmente construda, que deve ser correspondido.
(foco desempenha o mesmo papel na Figura3.2.) A tabela de anlise, Table, mapeia
pares de no terminais e smbolos de antecipao (terminais ou eof) para produes.
Dado um no terminal A e um smbolo de antecipao w, Table[A, w] especifica a
expanso correta.
O algoritmo para montar Table simples. Ele considera que os conjuntos FIRST,
FOLLOW e FIRST+ esto disponveis para a gramtica, percorre os smbolos da
gramtica e preenche Table, como mostra a Figura3.12. Se a gramtica atende
condio de livre de retrocesso (ver subseo Fatorao esquerda para eliminar o
retrocesso da Seo3.3.1), a construo produz uma tabela correta em um tempo
O(| P ||T |), onde P o conjunto de produes, e T o conjunto de terminais.
Se a gramtica no for livre de retrocesso, a construo ir atribuir mais de uma
produo a alguns elementos de Table. Se a construo atribuir a Table[A,w]
vrias produes, ento dois ou mais lados direitos alternativos para A tm w em seus
conjuntos FIRST+, violando a condio livre de retrocesso. O gerador de parser pode
detectar esta situao com um simples teste sobre duas atribuies para Table.
O exemplo na Figura3.13 mostra as aes do parser de expresso LL(1) para a string
de entrada a+bc. A coluna central evidencia o contedo da pilha do parser, que
mantm a borda inferior parcialmente completa da rvore sinttica. O parse conclui
com sucesso quando retira Expr9 da pilha, nela deixando eof no topo e eof como o
prximo smbolo, implicitamente, no fluxo de entrada.
Agora, considere as aes do parser LL(1) sobre a string de entrada ilegal x+ y, mostrada na Figura3.14. Ele detecta o erro de sintaxe quando tenta expandir um Termo com o
smbolo de antecipao . Table[Termo,] contm , indicando um erro de sintaxe.

FIGURA 3.12 Algoritmo de construo de tabela de anlise LL(1).

3.3 Anlise sinttica descendente (top-down) 99

FIGURA 3.13 Aes do parser LL(1) sobre a+bc.

FIGURA 3.14 Aes do parser LL(1) sobre x+ y.

100 CAPTULO 3 Analisadores Sintticos (Parsers)

Como alternativa, um gerador de parser LL(1) poderia construir um parser codificado


diretamente, no estilo dos scanners codificados diretamente discutidos no Captulo2.
Este gerador montaria os conjuntos FIRST, FOLLOW e FIRST+. Em seguida, percorreria a gramtica, seguindo o mesmo esquema usado pelo algoritmo de construo
de tabela da Figura3.12. Ao invs de emitir entradas de tabela, geraria, para cada
no terminal, um procedimento para reconhecer cada um dos lados direitos possveis
para esse no terminal. Esse processo seria guiado pelos conjuntos FIRST+, e teria as
mesmas vantagens de velocidade e localidade que decorrem dos scanners codificados
diretamente e parsers de descida recursiva, mantendo as vantagens de um sistema
gerado por gramtica, como especificao concisa, de alto nvel, e esforo de implementao reduzido.

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.

3.4 ANLISE SINTTICA BOTTOM-UP


Parsers bottom-up constroem uma rvore sinttica comeando com suas folhas e
trabalhando em direo raiz. O parser constri um n de folha na rvore para cada
palavra retornada pelo scanner. Essas folhas formam a borda inferior da rvore sinttica.
Para montar uma derivao, o parser acrescenta camadas de no terminais em cima
das folhas em uma estrutura orientada tanto pela gramtica quanto pela parte inferior
parcialmente completa da rvore sinttica.
Em qualquer estgio da anlise, a rvore parcialmente completa representa seu estado.
Cada palavra que o scanner tiver retornado representada por uma folha. Os ns acima
das folhas codificam todo o conhecimento que o parser j derivou. O parser funciona
junto fronteira superior dessa rvore sinttica parcialmente completa; essa fronteira
corresponde forma sentencial atual na derivao sendo montada pelo parser.

3.4 Anlise sinttica bottom-up 101

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.

102 CAPTULO 3 Analisadores Sintticos (Parsers)

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).

3.4.1 O algoritmo de anlise sinttica LR(1)


A etapa crtica em um parser bottom-up, como em um parser LR(1) dirigido por
tabela, encontrar o prximo handle. A localizao eficiente do handle a chave para
a anlise bottom-up eficiente. Um parser LR(1) usa um autmato de localizao de
handles codificado em duas tabelas, chamadas Action e Goto. A Figura3.15 exibe
um parser LR(1) simples dirigido por tabela.
O esqueleto de parser LR(1) interpreta as tabelas Action e Goto para encontrar handles
sucessivos na derivao mais direita reversa da string de entrada. Quando encontra
um handle A b, k, ele reduz b em k para A na forma sentencial atual a fronteira
superior da rvore sinttica parcialmente completa. Ao invs de montar uma rvore
sinttica explcita, o esqueleto de parser mantm a fronteira superior atual da rvore
parcialmente construda em uma pilha, intercalada com estados do autmato de
localizao de handles que lhe permitem encadear as redues para uma anlise. Em
qualquer ponto na anlise, a pilha contm um prefixo da fronteira atual. Alm deste
prefixo, a fronteira consiste em ns de folha. A varivel word mantm a primeira
palavra no sufixo que se encontra alm do contedo da pilha; este o smbolo de
antecipao.
O uso de uma pilha permite que o parser LR(1)
faa com que a posio k no handle seja constante e
implcita.

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.

3.4 Anlise sinttica bottom-up 103

FIGURA 3.15 Esqueleto de parser LR(1).

FIGURA 3.16 A gramtica de parnteses.

104 CAPTULO 3 Analisadores Sintticos (Parsers)

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

3.4 Anlise sinttica bottom-up 105

FIGURA 3.17 Estados do parser LR(1) sobre ( ( ) ) ( ).

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

106 CAPTULO 3 Analisadores Sintticos (Parsers)

FIGURA 3.18 Sequncia de rvores sintticas parciais montadas para ( ( ) ) ( ).

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.

Analisando uma string de entrada com erro


Para ver como um parser LR(1) descobre um erro de sintaxe, considere a sequncia de
aes que ele toma sobre a string ( ) ), como vemos a seguir:

3.4 Anlise sinttica bottom-up 107

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

As duas primeiras iteraes do parse procedem como no primeiro exemplo, ( ). O


parser desloca ( e ). Na terceira iterao do lao while, examina a entrada da tabela
Action para o estado 7 e ). Essa entrada no contm shift, nem reduce, nem accept,
de modo que ele a interpreta como um erro.
O parser LR(1) detecta erros de sintaxe por meio de um mecanismo simples: a entrada
da tabela correspondente invlida. O parser detecta o erro o mais cedo possvel, antes
de ler quaisquer palavras alm daquelas necessrias para provar a entrada errnea. Esta
propriedade permite que o parser localize o erro para um ponto especfico na entrada.
Usando o contexto disponvel e o conhecimento da gramtica, podemos construir
parsers LR(1) que fornecem boas mensagens de diagnstico de 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.

O uso de mais antecipao


Na realidade, as ideias que esto por trs dos parsers LR(1) definem uma famlia de
parsers que variam na quantidade de antecipao que utilizam. Um parser LR(k) usa,
no mximo, k smbolos de antecipao. A antecipao adicional permite que um parser
LR(2) reconhea um conjunto maior de gramticas do que um sistema de anlise
sinttica LR(1). Quase paradoxalmente, porm, a antecipao acrescentada no aumenta
o conjunto de linguagens que esses parsers podem reconhecer. Parsers LR(1) aceitam
o mesmo conjunto de linguagens que os parsers LR(k), para k>1. A gramtica LR(1)
para uma linguagem pode ser mais complexa do que uma gramtica LR(k).

108 CAPTULO 3 Analisadores Sintticos (Parsers)

3.4.2 Construo de tabelas LR(1)


Para construir tabelas Action e Goto, um gerador de parser LR(1) constri um
modelo do autmato de reconhecimento de handles e usa esse modelo para preencher as
tabelas. O modelo, chamado coleo cannica de conjuntos de itens LR(1), representa
todos os estados possveis do parser e as transies entre esses estados; semelhante
construo de subconjunto, da Seo 2.4.3.
Para ilustrar o algoritmo de construo de tabela, usaremos dois exemplos. O primeiro
a gramtica de parnteses dada na Figura3.16a. Ela pequena o suficiente para
ser usada como um exemplo, mas grande o suficiente para exibir algumas das complexidades do processo.

1
2
3
4
5

Alvo Lista
Lista Lista Par
| Par
Par ( Par )
|()

O segundo exemplo, na Seo 3.4.3, uma verso resumida da ambiguidade clssica


do if-then-else. A construo da tabela falha nessa gramtica por causa de sua
ambiguidade. O exemplo destaca as situaes que levam a falhas no processo de construo de tabelas.
Item LR(1)
[A b g, a], onde A b g uma produo de
gramtica, representa a posio do topo da pilha do
parser, e a um smbolo terminal na gramtica.

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.

3.4 Anlise sinttica bottom-up 109

3. [A bg,a] indica que o parser encontrou bg em um contexto onde um A


seguido por um a seria vlido. Se o smbolo de antecipao for a, ento o item
um handle e o parser pode reduzir bg para A. Este item completo.
Em um item LR(1), o codifica algum contexto esquerdo local as partes da produo
j reconhecidas. (Lembre-se, dos exemplos anteriores, que os estados colocados na pilha
codificam um resumo do contexto esquerda do item LR(1) atual basicamente, o
histrico da anlise sinttica at aqui.) O smbolo de antecipao codifica um smbolo
de contexto vlido direita. Quando o parser se encontra em um estado que inclui
[A bg,a] com uma antecipao de a, ele tem um handle e deve reduzir bg para A.
A Figura3.19 mostra o conjunto completo de itens LR(1) gerados pela gramtica de
parnteses. Dois itens merecem meno em particular. O primeiro, [AlvoLista,eof],
representa o estado inicial do parser encontrando uma string que reduz para Alvo,
seguida por eof. Toda anlise comea nesse estado. O segundo, [AlvoLista,eof],
representa o estado final desejado do parser encontrando uma string que reduz
para Alvo, seguido por eof. Este item representa toda anlise bem-sucedida. Todos
as possveis anlises resultam do encadeamento de estados do parser de uma maneira dirigida pela gramtica, comeando com [AlvoLista,eof] e terminando com
[AlvoLista,eof].

Construo da coleo cannica


Para construir uma coleo cannica de conjuntos de itens LR(1), CC, um gerador de
parser precisa comear do estado inicial do parser, [AlvoLista,eof], e construir um
modelo de todas as transies em potencial que podem ocorrer. O algoritmo representa
cada configurao possvel, ou estado, do parser como um conjunto de itens LR(1).

FIGURA 3.19 Itens LR(1) para a gramtica de parnteses.

110 CAPTULO 3 Analisadores Sintticos (Parsers)

O algoritmo baseia-se em duas operaes fundamentais sobre esses conjuntos de itens


LR(1): fechamento e clculo de transies.
j

A operao de fechamento completa um estado; dado algum conjunto inicial de


itens LR(1) (denominado ncleo), ela acrescenta a esse conjunto quaisquer outrositens LR(1) relacionados que sejam implicados pelos itens do conjunto. Por
exemplo, em qualquer lugar em que AlvoLista seja vlido, as produes que
derivam uma Lista tambm o so. Assim, o item [Alvo Lista, eof] implica tanto
[Lista Lista Par, eof] quanto [Lista Par, eof]. O procedimento closure
implementa essa funo.
j Para modelar a transio que o parser faria a partir de determinado estado em algum
smbolo da gramtica, , o algoritmo calcula o conjunto de itens que resultariadoreco
nhecimento de um . Para fazer isto, o algoritmo seleciona o subconjunto do conjunto
atual de itens LR(1) onde precedee avana o marcador para alm doem cada
um deles. O procedimento goto implementa esta funo.
Para simplificar a tarefa de localizar o smbolo-alvo, exigimos que a gramtica tenha
um nico smbolo-alvo que no aparece no lado direito de qualquer produo. Na
gramtica de parnteses, este smbolo Alvo.
O item [Alvo Lista, eof] representa o estado inicial do parser para a gramtica
de parnteses; cada anlise vlida reconhece Alvo seguido por eof. Este item
forma o ncleo do primeiro estado em CC, rotulado com cc0. Se a gramtica tiver
vrias produes para o smbolo-alvo, cada uma delas gera um item no ncleo
inicial de cc0.

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.

FIGURA 3.20 O procedimento closure.

3.4 Anlise sinttica bottom-up 111

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.

FIGURA 3.21 A funo goto.

Em nossa experincia, esse uso de FIRST(da) o ponto


no processo onde um humano mais provavelmente
cometeria um erro.

112 CAPTULO 3 Analisadores Sintticos (Parsers)

A funo goto, apresentada na Figura3.21, toma um conjunto de itens LR(1) s e um


smbolo x da gramtica e retorna um novo conjunto de itens LR(1). Ela percorre os itens
em s. Quando encontra um item em que o marcador precede imediatamente x, cria
um novo item movendo o marcador para a direita aps x. Esse novo item representa
a configurao do parser aps reconhecer x. A funo goto coloca esses novos itens
em um novo conjunto, invoca o procedimento closure para completar o estado do
parser e retorna esse novo estado.
Dado o conjunto inicial para a gramtica de parnteses,

podemos derivar o estado do parser aps ele reconhecer um ( inicial calculando


goto(cc0, ( ). O lao interno encontra quatro itens que tm o marcador antes de (.
Afuno goto cria um novo item para cada um, com o marcador avanado para alm
de (. O procedimento closure acrescenta mais dois itens, gerados a partir dos itens
com o marcador antes de Par. Esses itens introduzem o smbolo de antecipao ).
Assim, goto(cc0, ( ) retorna

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.

3.4 Anlise sinttica bottom-up 113

FIGURA 3.22 O algoritmo para construir CC.

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.

Coleo cannica para a gramtica de parnteses


Como primeiro exemplo, considere o problema de construir CC para a gramtica de
parnteses. O conjunto inicial, cc0, calculado como closure([AlvoLista,eof]).

114 CAPTULO 3 Analisadores Sintticos (Parsers)

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 cc1 representa as configuraes do parser que resultam do reconhecimento


de uma Lista. Todos os itens so possibilidades que levam a outro par de parnteses,
exceto para o item [Alvo Lista , eof]. Ele representa o estado de aceitao do
parser uma reduo usando Alvo Lista, com uma antecipao de eof.

FIGURA 3.23 Acompanhamento da construo LR(1) na gramtica de parnteses.

3.4 Anlise sinttica bottom-up 115

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 conjunto cc3 representa a configurao do parser aps ele reconhecer um ( inicial.


Quando o parser entra no estado 3, precisa reconhecer um ) correspondente em algum
ponto no futuro.
A segunda iterao do lao while tenta derivar novos conjuntos a partir de cc1, cc2
e cc3. Cinco das combinaes produzem conjuntos no vazios, quatro dos quais so
novos.

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.

O conjunto cc5 consiste de dois itens parcialmente completos. O parser reconheceu


um ( seguido por um Par; e agora precisa encontrar um ) correspondente. Se o parser
encontr-lo, reduzir usando a regra 4, Par ( Par ).

116 CAPTULO 3 Analisadores Sintticos (Parsers)

O parser chega a cc6 quando encontra um ( e j tem pelo menos um ( na pilha. Os


itens mostram que um ( ou um ) levam a estados vlidos.

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.

Ao chegar ao estado 8, o parser ter reconhecido uma ocorrncia da regra 4, Par (


Par ). Os dois itens especificam a reduo correspondente.

Em cc9, o parser precisa encontrar um ) para completar a regra 4.


goto(cc6,( ) cc6. Em cc6, outro ( far com que o parser empilhe outro estado 6 para
representar a necessidade de um ) correspondente.

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.

O estado 11 exige uma reduo usando Par ( Par ).

3.4 Anlise sinttica bottom-up 117

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.

O preenchimento das tabelas


Dada a coleo cannica de conjuntos de itens LR(1) para uma gramtica, o gerador
de parser pode preencher as tabelas Action e Goto percorrendo CC e examinando
os itens em cada ccj CC. Cada ccj torna-se um estado do parser. Seus itens geram os
elementos no vazios de uma linha de Action; as transies correspondentes registradas durante a construo de CC especificam os elementos no vazios de Goto. Trs
casos geram entradas na tabela Action:
1. Um item na forma [Abcg,a] indica que encontrar o smbolo terminal c seria
uma prxima etapa vlida em direo descoberta do no terminal A. Assim,
isto gera um shift em c no estado atual. O prximo estado para o reconhecedor
o estado gerado pelo clculo de goto no estado atual com o terminal c. Ou b ou
g podem ser .
2. Um item na forma [Ab,a] indica que o parser reconheceu um b, e se o smbolo de antecipao for a, ento o item um handle. Assim, ele gera um reduce
para a produo A b em a no estado atual.
3. Um item da forma [S9S,eof], onde S9 o smbolo-alvo, indica o estado de
aceitao para o parser; o parser reconheceu um fluxo de entrada que se reduz ao
smbolo-alvo, e o smbolo de antecipao eof. Este item gera uma ao accept
em eof no estado atual.
A Figura3.24 torna isto concreto. Para uma gramtica LR(1), necessrio definir
exclusivamente as entradas que no so de erro nas tabelas Action e Goto.
Observe que o algoritmo de preenchimento de tabela basicamente ignora itens onde
o marcador precede um smbolo no terminal. Aes shift so geradas quando o
marcador precede um smbolo terminal. Aes reduce e accept so geradas quando

FIGURA 3.24 Algoritmo de preenchimento de tabela LR(1).

118 CAPTULO 3 Analisadores Sintticos (Parsers)

As aes de preenchimento de tabela podem ser


integradas construo de CC.

o marcador est no final direita da produo. E se cci tiver um item [Ab g d,


a], onde g NT? Embora este item no gere quaisquer entradas na tabela por si
s, sua presena no conjunto fora o procedimento closure a incluir itens que as
geram. Quando closure encontra um marcador que precede imediatamente um
smbolo no terminal g, ele acrescenta produes que possuem g como seu lado esquerdo, com um marcador antecedendo seus lados direitos. Este processo requer
FIRST(g) em cci. O procedimento closure encontrar cada x FIRST(g) e incluir
os itensem cci para gerar itens shift para cada x.
Para a gramtica de parnteses, a construo produz as tabelas Action e Goto mostradas na Figura3.16b. Como vimos, a combinao das tabelas com o esqueleto de
parser da Figura3.15 cria um parser funcional para a linguagem.
Na prtica, um gerador de parser LR(1) precisa produzir outras tabelas necessrias para
o esqueleto de parser. Por exemplo, quando o esqueleto de parser da Figura3.15 efetua
uma reduo usando A b, ele remove 2| b | smbolos da pilha e empilha A. O
gerador de tabela precisa produzir estruturas de dados que mapeiem uma produo a
partir de uma entrada reduce na tabela Action, digamos, A b, em | b | e A. Outras
tabelas, como um mapeamento da representao de um smbolo da gramtica como
um nmero inteiro para seu nome textual, so necessrias para depurao e para
mensagens de diagnstico.

Localizao de handle, reviso


Parsers LR(1) obtm sua eficincia a partir do mecanismo rpido de localizao de
handles embutido nas tabelas Action e Goto. A coleo cannica, CC, representa
um DFA de localizao de handles para a gramtica. A Figura3.25 mostra o DFA para
nosso exemplo, a gramtica de parnteses.

FIGURA 3.25 DFA de localizao de handles para a gramtica de parnteses.

O parser LR(1) torna a posio do handle implcita no


topo da pilha. Essa deciso de projeto reduz bastante o
nmero de handles possveis.

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.

3.4 Anlise sinttica bottom-up 119

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.

3.4.3 Erros na construo de tabela


Como segundo exemplo da construo de tabela LR(1), considere a gramtica ambgua
para a construo if-then-else clssica. A abstrao dos detalhes da expresso de
controle e todos os outros comandos (tratando-os como smbolos terminais) produzem
a seguinte gramtica de quatro produes:
1
2
3
4

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 partir desse conjunto, a construo comea derivando os membros restantes da


coleo cannica dos conjuntos de itens LR(1).

120 CAPTULO 3 Analisadores Sintticos (Parsers)

FIGURA 3.26 Acompanhamento da construo LR(1) na gramtica If-Then-Else.

A Figura3.26 mostra o progresso da construo. A primeira iterao examina as


transies a partir de cc0 para cada smbolo da gramtica. Ela produz trs novos
conjuntos para a coleo cannica de cc0: cc1 para Cmd, cc2 para if, e cc3 para assign.
Estes conjuntos so:

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).

3.4 Anlise sinttica bottom-up 121

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.

Quando a sexta iterao examina os conjuntos produzidos na iterao anterior, cria


dois novos conjuntos, cc11 a partir de cc9 em Cmd e cc12 a partir de cc10 em then. E,
tambm, conjuntos duplicados para cc2 e cc3 a partir de cc9.

A stima iterao cria cc13 a partir de cc12 em Cmd. E recria cc7 e cc8.

122 CAPTULO 3 Analisadores Sintticos (Parsers)

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

Uma mensagem de erro tpica de um gerador de parser


inclui os itens LR(1) que geram o conflito; outro motivo
para estudar a construo de tabela.

expr
expr
expr
expr

then Cmd , else]


then Cmd , eof ]
then Cmd else Cmd, else]
then Cmd else Cmd, eof ]

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).

3.5 Questes prticas 123

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.

3.5 QUESTES PRTICAS


Mesmo com geradores de parser automticos, o construtor de compiladores precisa
gerenciar vrias questes a fim de produzir um parser robusto e eficiente para uma linguagem de programao real. Esta seo trata de diversas questes que surgem na prtica.

3.5.1 Recuperao de erros


Os programadores constantemente compilam cdigos que contm erros de sintaxe.
Na verdade, os compiladores so bastante aceitos como o modo mais rpido para descobrir esses erros. Nesta aplicao, o compilador deve encontrar tantos erros de sintaxe
quantos forem possveis em uma nica tentativa de anlise sinttica do cdigo. Isto
exige ateno ao comportamento do parser nos estados de erro.
Todos os parsers mostrados neste captulo tm o mesmo comportamento quando
encontram um erro de sintaxe: eles relatam o problema e param. Este comportamento
impede que o compilador desperdice tempo tentando traduzir um programa incorreto.
Por outro lado, garante que o compilador encontre no mximo um erro de sintaxe por
compilao. Esse compilador tornaria a descoberta de todos os erros de sintaxe em um
arquivo de texto de programa um processo potencialmente longo e doloroso.
Um parser deve encontrar o mximo de erros possvel em cada compilao. Isto requer
um mecanismo que lhe permita recuperar de um erro passando para um estado a partir
do qual pode continuar analisando. Um modo comum de conseguir isto selecionar

O Exerccio 12 mostra uma gramtica LR(1) que no


tem uma gramtica LL(1) equivalente.

Como um exemplo final, as tabelas LR para a gramtica


de expresso clssica aparecem nas Figuras3.31 e3.32.

124 CAPTULO 3 Analisadores Sintticos (Parsers)

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.

3.5.2 Operadores unrios


A gramtica de expresso clssica inclui apenas operadores binrios. A notao algbrica, porm, inclui operadores unrios, como o menos unrio e o valor absoluto. Outros
operadores unrios surgem nas linguagens de programao, incluindo o autoincremento,
autodecremento, endereo-de, valor-apontado-por, complemento booleano e converses
de tipo. A incluso desses operadores gramtica da expresso exige algum cuidado.
Considere a incluso de um operador de valor absoluto unrio, ||, gramtica de
expresso clssica. O valor absoluto deve ter a precedncia mais alta do queou .
Porm, ele precisa de uma precedncia mais baixa do que Fator para forar a avaliao
das expresses entre parnteses antes da aplicao de ||. Um modo de escrever essa
gramtica aparece na Figura3.27. Com essas adies, a gramtica ainda LR(1). Ela
permite que o programador forme o valor absoluto de um nmero, um identificador,
ou uma expresso entre parnteses.
A Figura3.27b mostra a rvore sinttica para a string ||x 3, mostrando corretamente
que o cdigo deve avaliar ||x antes de realizar a subtrao. A gramtica no permite que
o programador escreva || ||x, pois isto faz pouco sentido matemtico. Porm, permite
|| ( || x), que faz to pouco sentido quanto || || x.

3.5 Questes prticas 125

FIGURA 3.27 Incluso do valor absoluto unrio gramtica de expresso clssica.

A incapacidade de escrever || ||x dificilmente limita a expressividade da linguagem.


Com outros operadores unrios, porm, a questo parece mais sria. Por exemplo,
um programador C pode precisar escrever **p para obter o valor apontado por uma
varivel declarada como char **p;. Tambm podemos acrescentar uma produo alternativa para Valor especfica para isso: Valor * Valor. A gramtica resultante ainda uma gramtica LR(1), mesmo que substitussemos o operadorem
Termo TermoValor por *, sobrecarregando o operador *" do modo como feito
em C. Esta mesma tcnica funciona para o menos unrio.

3.5.3 Tratamento da ambiguidade sensvel ao contexto


O uso de uma palavra para representar dois significados diferentes pode criar uma
ambiguidade sinttica. Um exemplo deste problema apareceu nas definies de diversas
linguagens de programao, incluindo FORTRAN, PL/I e Ada. Essas linguagens usavam
parnteses para delimitar tanto as expresses de subscrito de uma referncia de array
quanto a lista de argumentos de uma sub-rotina ou funo. Dada uma referncia textual,
como fee(i,j), o compilador no pode saber se fee um array bidimensional ou
um procedimento que deve ser chamado. A diferenciao entre estes dois casos exige
conhecimento do tipo declarado de fee. Esta informao no sintaticamente bvia.

126 CAPTULO 3 Analisadores Sintticos (Parsers)

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:

Reescrita desta forma, a gramtica no ambgua. Como o scanner retorna uma


categoria sinttica distinta em cada caso, o parser pode distinguir os dois casos.

3.5.4 Recurso esquerda versus recurso direita


Como vimos, os parsers top-down precisam de gramticas recursivas direita ao invs
das recursivas esquerda. Os parsers bottom-up podem acomodar a recurso esquerda
ou direita. Assim, o construtor de compiladores precisa escolher entre a recurso
esquerda e a recurso direita na escrita da gramtica para um parser bottom-up.
Diversos fatores influenciam esta deciso.

3.5 Questes prticas 127

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

FIGURA 3.28 Gramticas de lista recursiva esquerda e direita.

128 CAPTULO 3 Analisadores Sintticos (Parsers)

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.

A recurso esquerda produz naturalmente associatividade esquerda, e a recurso


direita, associatividade direita. Em alguns casos, a ordem de avaliao faz diferena.
Considere as rvores sintticas abstratas (ASTs) para as duas listas de cinco elementos apresentadas nas Figuras3.28e e3.28f. A gramtica recursiva esquerda reduz
elt1 a uma Lista, depois, reduz Lista elt2, e assim por diante. Isto produz a AST
apresentada esquerda. De modo semelhante, a gramtica recursiva direita produz
a AST apresentada direita.
Para uma lista, nenhuma dessas ordens obviamente incorreta, embora a AST recursiva
direita possa parecer mais natural. Considere, porm, o resultado se substituirmos o
construtor de lista por operaes aritmticas, como nas gramticas
Expr

|
|

Expr+Operando
Expr Operando
Operando

Expr

|
|

Operando+Expr
Operando Expr
Operando

Para a string x1 + x2 + x3 + x4 + x5, a gramtica recursiva esquerda implicauma


ordem de avaliao da esquerda para a direita, enquanto a recursiva direita,
uma ordem de avaliao da direita para a esquerda. Com alguns sistemas de nmeros,
como na aritmtica de ponto flutuante, essas duas ordens de avaliao podem produzir
resultados diferentes.
Como a mantissa de um nmero de ponto flutuante pequena em relao ao intervalo
do expoente, a adio pode se tornar uma operao de identidade com dois nmeros
distantes em magnitude. Por exemplo, se x4 for muito menor que x5, o processador
pode calcular x4 + x5 = x5. Com valores bem escolhidos, este efeito pode ser propagado e gerar respostas diferentes a partir de avaliaes da esquerda para a direita
e vice-versa.
De modo semelhante, se qualquer um dos termos na expresso for uma chamada de
funo, ento a ordem de avaliao pode ser importante. Se a chamada de funo mudar
o valor de uma varivel na expresso, ento mudar a ordem de avaliao pode mudar o
resultado.
Em uma string com subtraes, como x1 x2 + x3, mudar a ordem de avaliao pode
produzir resultados incorretos. A associatividade esquerda avaliada, em um percurso
de rvore ps-ordem, como (x1 x2) + x3, o resultado esperado. A associatividade
direita, por outro lado, implica uma ordem de avaliao de x 1 (x 2 + x 3 ). O
compilador deve, naturalmente, preservar a ordem de avaliao ditada pela definio
da linguagem. O construtor de compiladores pode escrever a gramtica de expresso
de modo que produza a ordem desejada ou cuide de gerar a representao intermediria
para refletir a ordem e associatividade corretas, conforme descrito na Seo 4.5.2.

3.6 Tpicos avanados 129

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.

3.6 TPICOS AVANADOS


Para construir um parser satisfatrio, o construtor de compiladores precisa entender
os fundamentos da criao de uma gramtica e um parser. Com um parser funcionando,
normalmente existem maneiras de melhorar seu desempenho. Esta seo examina duas
questes especficas na construo do parser. Primeiro, examinamos as transformaes
na gramtica que reduzem o tamanho de uma derivao para produzir uma anlise
mais rpida. Essas ideias aplicam-se a parsers top-down e bottom-up. Segundo, discutimos as transformaes na gramtica e nas tabelas Action e Goto que reduzem
o tamanho da tabela. Essas tcnicas aplicam-se apenas a parsers LR.

3.6.1 Otimizao de uma gramtica


Embora a anlise sinttica no consuma mais uma grande fatia do tempo de compilao,
o compilador no deve desperdiar um tempo indevido nesta anlise. A forma real de
uma gramtica tem efeito direto sobre a quantidade de trabalho exigida para analis-la.
Os parsers top-down e bottom-up constroem derivaes. O primeiro realiza uma expanso para cada produo na derivao. O segundo, uma reduo para cada produo
na derivao. Uma gramtica que produza derivaes mais curtas exige menos tempo
para a anlise.
O construtor de compiladores normalmente pode reescrever a gramtica para reduzir
a altura da rvore sinttica, reduzindo assim o nmero de expanses em um parser
top-down e o nmero de redues em um parser bottom-up. A otimizao da gramtica
no pode mudar o comportamento assinttico do parser; afinal, a rvore sinttica precisa
ter um n de folha para cada smbolo no fluxo de entrada. Ainda assim, a reduo das
constantes em partes altamente utilizadas da gramtica, como a gramtica de expresso,
pode gerar uma diferena suficiente para justificar o esforo.
Considere, novamente, a gramtica de expresso clssica da Seo 3.2.4. (As tabelas
LR(1) para a gramtica aparecem nas Figuras3.31 e3.32.) Para impor a precedncia
desejada entre os operadores, acrescentamos dois no terminais, Termo e Fator, e remodelamos a gramtica para a forma mostrada na Figura3.29a, que produz rvores sintticas
um tanto grandes, mesmo para expresses simples. Por exemplo, na expresso a+2b,
esta rvore tem 14 ns, como mostra a Figura3.29b. Cinco desses ns so folhas que
no podemos eliminar. (Mudar a gramtica no pode encurtar o programa de entrada.)

130 CAPTULO 3 Analisadores Sintticos (Parsers)

FIGURA 3.29 Gramtica de expresso clssica revisada.

Qualquer n interior que tenha apenas um filho um candidato para otimizao.


A sequncia de ns de Expr para Term para Fator para nome,a usa quatro
ns para uma nica palavra no fluxo de entrada. Podemos eliminar pelo menos
uma camada, a de ns Fator, desdobrando as expanses alternativas para Fator
em Termo, como mostra a Figura3.30a. Ela multipica por trs o nmero de
alternativas para Termo, mas encurta a rvore sinttica em uma camada, mostrada
na Figura3.30b.
Em um parser LR(1), essa mudana elimina trs das nove aes reduce, e deixa os
cinco shifts intactos. Em um parser de descida recursiva para uma gramtica preditiva
equivalente, este processo eliminaria 3 das 14 chamadas de procedimento.
Em geral, qualquer produo que tenha um nico smbolo no seu lado direito pode
ser desdobrada dessa forma. Essas produes, por vezes, so chamadas produes
inteis. s vezes, produes inteis tm uma finalidade tornar a gramtica mais
compacta e, talvez, mais legvel, ou forar a derivao a assumir uma forma em
particular. (Lembre-se de que a mais simples de nossas gramticas de expresso aceita
a+2b, mas no codifica qualquer noo de precedncia na rvore sinttica.)
Conforme veremos no Captulo4, o construtor de compiladores pode incluir uma
produo intil simplesmente para criar um ponto na derivao onde uma ao em
particular possa ser realizada.
Desdobrar produes inteis tem seus custos. Em um parser LR(1), isto pode tornar
as tabelas maiores. Em nosso exemplo, eliminar Fator remove uma coluna da tabela
Goto, mas as produes extras para Termo aumentam o tamanho de CC de 32 para 46
conjuntos. Assim, as tabelas tm uma coluna a menos, mas 14 linhas extras. O parser
resultante realiza menos redues (e executa mais rapidamente), mas tem tabelas
maiores.
Em um parser de descida recursiva codificado mo, a gramtica maior pode
aumentar o nmero de alternativas que devem ser comparadas antes de expandir

3.6 Tpicos avanados 131

algum lado esquerdo. O construtor de compiladores s vezes pode compensar o


custo aumentado combinando casos. Por exemplo, o cdigo para as expanses no
triviais de Expr na Figura3.10 idntico. O construtor de compiladores poderia
combin-los com um teste que corresponda word a+ou a . Como alternativa,
poderia atribuir tanto+quanto - mesma categoria sinttica, fazer que o parser inspecione a categoria sinttica e usar o lexema para diferenciar entre os dois quando
for preciso.

FIGURA 3.30 Substituio de produes para Termo.

3.6.2 Reduo do tamanho das tabelas LR(1)


Infelizmente, as tabelas LR(1) geradas para gramticas relativamente pequenas
podem ser grandes. As Figuras3.31 e3.32 mostram as tabelas LR(1) cannicas
para a gramtica de expresso clssica. Existem muitas tcnicas para encolher
essas tabelas, incluindo as trs abordagens para reduzir o tamanho da tabela descritas nesta seo.

132 CAPTULO 3 Analisadores Sintticos (Parsers)

FIGURA 3.31Tabela Action para a gramtica de expresso clssica.

3.6 Tpicos avanados 133

Combinao de linhas ou colunas


Se o gerador de tabela puder encontrar duas linhas ou duas colunas que sejam
idnticas, pode combin-las. Na Figura3.31, as linhas para os estados 0 e de 7 a 10
so idnticas, assim como as linhas 4, 14, 21, 22, 24 e 25. O gerador de tabela pode
implementar cada um desses conjuntos uma vez e remapear os estados de modo
correspondente. Isto removeria nove linhas da tabela, reduzindo seu tamanho em
28%. Para usar esta tabela, o esqueleto de parser precisa de um mapeamento de um
estado do parser para um ndice de linha na tabela Action. O gerador de tabela
pode combinar colunas idnticas de modo semelhante. Uma inspeo separada
da tabela Goto gerar um conjunto diferente de combinaes de estado em
particular, todas as linhas contendo apenas zeros devem ser condensadas para uma
nica linha.
Em alguns casos, o gerador de tabela pode provar que duas linhas ou duas colunas
diferem apenas em casos em que um dos dois tem uma entrada de erro (indicada por
um espao em branco em nossas figuras). Na Figura3.31, as colunas para eof e para
num diferem apenas quando um ou outro tem um espao. A combinao dessas colunas
produz o mesmo comportamento sobre entradas corretas. Isto muda o comportamento
do parser em entradas errneas e pode impedir a capacidade do parser de fornecer
mensagens de erro precisas e teis.
A combinao de linhas e colunas produz uma reduo direta no tamanho da
tabela. Se esta reduo de espao acrescentar uma indireo extra em cada acesso
tabela, o custo dessas operaes em memria deve agir diretamente contra as

FIGURA 3.32Tabela Goto para a gramtica de expresso clssica.

134 CAPTULO 3 Analisadores Sintticos (Parsers)

economias de memria. O gerador de tabela tambm poderia usar outras tcnicas


para representar matrizes esparsas novamente, o implementador deve considerar
o compromisso entre o tamanho de memria contra qualquer aumento nos custos
de acesso.

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.

Codificao direta da tabela


Como melhoria final, o gerador de parser pode abandonar o esqueleto de parser dirigido
por tabela em favor de uma implementao rigidamente codificada. Cada estado se
torna um pequeno comando case ou uma coleo de comandos if-then-else que
testam o tipo do prximo smbolo e geram aes shift, reduce, accept, ou informam
um erro. O contedo inteiro das tabelas Action e Goto pode ser assim codificado.
(Uma transformao semelhante para scanners discutida na Seo 2.5.2.)
O parser resultante evita a representao direta de todos os estados no se preocupe
nas tabelas Action e Goto, mostrados como espaos em branco nas figuras. Essas
economias de espao podem ser contrabalanadas pelo cdigo de maior tamanho,
pois cada estado agora inclui mais cdigo. O novo parser, porm, no possui tabela
de anlise, no realiza pesquisas em tabela e nem tem o lao externo encontrado no
esqueleto de parser. Embora sua estrutura o torne quase ilegvel para pessoas, ele deve

3.6 Tpicos avanados 135

FIGURA 3.33 Gramtica de expresso reduzida e suas tabelas.

136 CAPTULO 3 Analisadores Sintticos (Parsers)

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.

Uso de outros algoritmos de construo


Existem vrios outros algoritmos para construir parsers no estilo LR. Entre estas
tcnicas esto a construo SLR(1), de Simple LR(1), e a construo LALR(1), de
LookAhead LR(1). Ambas produzem tabelas menores do que o algoritmo LR(1)
cannico.
O algoritmo SLR(1) aceita uma classe menor de gramticas do que a construo LR(1)
cannica. Essas gramticas so restritas, de modo que os smbolos de antecipao nos
itens LR(1) no so necessrios. O algoritmo utiliza conjuntos FOLLOW para distinguir entre os casos em que o parser deve usar a ao shift e aqueles em que deve
usar reduce. Este mecanismo poderoso o suficiente para resolver muitas gramticas
de interesse prtico. Usando os conjuntos FOLLOW, o algoritmo elimina a necessidadede smbolos de antecipao, produzindo uma coleo cannica menor e uma
tabela com menos linhas.
O algoritmo LALR(1) aproveita a observao de que alguns itens no conjunto representando um estado so crticos e que os restantes podem ser derivados dos itens
crticos. A construo de tabela LALR(1) s representa os itens crticos; novamente,
isto produz uma coleo cannica que equivalente quela produzida pela construo
SLR(1). Os detalhes diferem, mas os tamanhos de tabela so os mesmos.
A construo LR(1) cannica apresentada anteriormente neste captulo o mais
genrico desses algoritmos de construo de tabela. Ela produz as maiores tabelas, mas
aceita a maior classe de gramticas. Com tcnicas apropriadas de reduo de tabela, as
tabelas LR(1) podem se aproximar em tamanho daquelas produzidas por tcnicas mais
limitadas. Porm, em um resultado ligeiramente contraintuitivo, qualquer linguagem
que tenha uma gramtica LR(1) tambm tem uma gramtica LALR(1) e uma gramtica
SLR(1). As gramticas para essas formas mais restritivas sero modeladas de modo
a permitir a seus respectivos algoritmos de construo distinguir as situaes em que
o parser deve efetuar um deslocamento (shift) daquelas em que ele deve efetuar uma
reduo (reduce).

3.7 RESUMO E PERSPECTIVA


Quase todo compilador contm um parser. Por muitos anos, a anlise sinttica foi
um assunto de grande interesse, o que levou ao desenvolvimento de muitas tcnicas
diferentes para a criao de parsers eficientes. A famlia de gramticas LR(1) inclui
todas as gramticas livres de contexto que podem ser analisadas de modo determinstico. As ferramentas produzem parsers eficientes com propriedades provadamente
fortes de deteco de erros. Esta combinao de recursos, junto com a disponibilidade
generalizada de geradores de parser para gramticas LR(1), LALR(1) e SLR(1),
diminuiu o interesse em outras tcnicas de anlise sinttica automticas, como parsers
de precedncia de operador.
Os parsers de descida recursiva tm seu prprio conjunto de vantagens. Eles so,
comprovadamente, os parsers codificados mo mais fceis de construir, e oferecem
excelentes oportunidades para detectar e reparar erros de sintaxe, alm de eficientes. De
fato, um parser de descida recursiva pode ser mais rpido que um parser LR(1) dirigido

3.7 Resumo e perspectiva 137

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.

138 CAPTULO 3 Analisadores Sintticos (Parsers)

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

5. Considere a seguinte gramtica:


A
B

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.

3.7 Resumo e perspectiva 139

9. A linguagem SomRelgio (SR) representada pela seguinte gramtica:

a. Quais so os itens LR(1) de SR?


b. Quais so os conjuntos FIRST de SR?
c. Construa a coleo cannica de conjuntos de itens LR(1) para SR.
d. Derive as tabelas Action e Goto.
10. Considere a seguinte gramtica:

Incio
S
A
B
C

S
Aa
BC
BCf
b
c

a. Construa a coleo cannica de conjuntos de itens LR(1) para esta gramtica.


b. Derive as tabelas Action e Goto.
c. A gramtica LR(1)?
11. Considere um brao de rob que aceite dois comandos: coloca uma ma na
bolsa e tira uma ma da bolsa. Suponha que o brao do rob comece com
uma bolsa vazia.
Uma sequncia de comando vlida para o brao do rob no deve ter prefixo
que contenha mais comandos do que . Como exemplos, e so
sequncias de comando vlidas, mas e no so.
a. Escreva uma gramtica LR(1) que represente todas as sequncias de
comando de valor para o brao do rob.
b. Prove que a gramtica LR(1).
12. A gramtica a seguir no possui uma equivalente LL(1) conhecida:
0
1
2
3
4
5

Incio
A
B

A
B
(A)
a
(B>
b

Mostre que a gramtica LR(1).

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.

140 CAPTULO 3 Analisadores Sintticos (Parsers)

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

Anlise sensvel ao contexto


VISO GERAL DO CAPTULO
Um programa de entrada gramaticalmente correto ainda pode conter srios erros que
impediriam a compilao. Para detect-los, um compilador realiza outro nvel de
verificao que envolve a considerao de cada comando em seu contexto real. Essas
verificaes encontram erros de tipo e de concordncia.
Este captulo introduz duas tcnicas para a verificao sensvel ao contexto. As gramticas de atributo so um formalismo funcional para especificar a computao sensvel
ao contexto. A traduo ad hoc dirigida pela sintaxe oferece um framework simples
no qual o construtor de compiladores pode pendurar trechos de cdigo quaisquer para
realizar essas verificaes.
Palavras-chave: Elaborao semntica, Verificao de tipo, Gramticas de atributo,
Traduo ad hoc dirigida pela sintaxe

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

142 CAPTULO 4 Anlise sensvel ao contexto

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

Que tipo de valor est armazenado em x? As linguagens de programao


modernas usam diversos tipos de dados, incluindo nmeros, caracteres, valores
booleanos, ponteiros para outros objetos, conjuntos (como {red, yellow,
green}) e outros. A maioria das linguagens inclui objetos compostos que agregam valores individuais; estes incluem arrays, estruturas, conjuntos e strings.
Qual o tamanho de x? Como o compilador precisa manipular x, ele precisa
saber o tamanho da representao de x na mquina-alvo. Se x for um nmero,
ele poderia ocupar uma palavra (um inteiro ou nmero de ponto flutuante),
duas palavras (um nmero de ponto flutuante de preciso dupla ou um nmero
complexo) ou quatro palavras (um nmero de ponto flutuante com preciso
qudrupla ou um nmero complexo com preciso dupla). Para arrays e strings,
o nmero de elementos poderia ser fixado em tempo de compilao ou ser
determinado em tempo de execuo (runtime).
Se x um procedimento, que argumentos ele utiliza? Que tipo de valor, se
houver, ele retorna? Antes que o compilador possa gerar cdigo para chamar um
procedimento, precisa saber quantos argumentos o cdigo para o procedimento
chamado espera, onde imagina encontrar esses argumentos e que tipo de valor
espera de cada argumento. Se o procedimento retorna um valor, onde a rotina
que chama o encontrar, e que tipo de dado ele ser? (O compilador precisa
garantir que o procedimento que chama, use o valor de maneira coerente e
segura. Se o procedimento que chama, pressupe que o valor de retorno um
ponteiro que ele pode seguir para encontrar o valor apontado, e o procedimento
chamado retornar uma string de caracteres qualquer, os resultados podem no
ser previsveis, seguros ou coerentes.)
Por quanto tempo o valor de x deve ser preservado? O compilador precisa
garantir que o valor de x permanece acessvel para qualquer parte da computao
que possa legalmente referenci-lo. Se x for uma varivel local em Pascal,
o compilador pode facilmente superestimar seu tempo de vida preservando seu
valor pela durao do procedimento que declara x. Se x for uma varivel global
que possa ser referenciada em qualquer lugar, ou um elemento de uma estrutura
alocada explicitamente pelo programa, o compilador pode ter certa dificuldade
para determinar seu tempo de vida. O compilador sempre poder preservar
o valor de x por toda a computao; porm, informaes mais precisas sobre o
tempo de vida de x poderiam permitir que o compilador reutilizasse seu espao
para outros valores com tempos de vida no conflitantes.
Quem responsvel por alocar espao para x (e inicializ-lo)? O espao
alocado para x implicitamente ou o programa aloca o espao explicitamente

4.2 Introduo aos sistemas de tipo 143

para ele? Se a alocao explcita, ento o compilador precisa assumir que o


endereo de x no pode ser conhecido at que o programa seja executado. Se, por
outro lado, o compilador alocar espao para x em uma das estruturas de dados
que ele gerencia, ento ele sabe mais sobre o endereo de x. Esse conhecimento
pode permitir que ele gere um cdigo mais eficiente.
O compilador precisa obter as respostas para essas perguntas, e de outras, a partir do
programa fonte e das regras da linguagem-fonte. Em uma linguagem tipo Algol, como
Pascal ou C, a maior parte dessas perguntas pode ser respondida examinando-se as declaraes para x. Se a linguagem no possui declaraes, como em APL, o compilador
precisa obter esse tipo de informao analisando o programa ou deve gerar cdigo que
possa tratar qualquer caso que possa surgir.
Muitas dessas perguntas (se no todas) esto fora da sintaxe livre de contexto da
linguagem-fonte. Por exemplo, as rvores sintticas para x y e x z diferem apenas
no texto do nome no lado direito da atribuio. Se x e y so inteiros, enquanto z uma
string de caracteres, o compilador pode ter que emitir um cdigo diferente para x y
do que emite para x z. Para distinguir entre esses casos, ele precisa se aprofundar no
significado do programa. As anlises lxica e sinttica lidam unicamente com a forma
do programa; a anlise de significado est no mbito da anlise sensvel ao contexto.
Para ver essa diferena entre sintaxe e significado mais claramente, considere a estrutura
de um programa na maioria das linguagens tipo Algol. Essas linguagens exigem que
cada varivel seja declarada antes de ser usada e que cada uso de uma varivel seja
coerente com sua declarao. O construtor de compiladores pode estruturar a sintaxe
para garantir que todas as declaraes ocorram antes de qualquer comando executvel.
Uma produo como

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.

4.2 INTRODUO AOS SISTEMAS DE TIPO


A maior parte das linguagens de programao associa uma coleo de propriedades
a cada valor de dados. Chamamos esta coleo de propriedades de tipo do valor. O
tipo especifica um conjunto de propriedades mantidas em comum por todos os valores
desse tipo. Os tipos podem ser especificados por uma condio de pertinncia; por

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.

144 CAPTULO 4 Anlise sensvel ao contexto

exemplo, um integer poderia ser qualquer nmero inteiro i pertencente ao intervalo


231i<231; ou, red poderia ser um valor em um tipo enumerado colors, definido
como o conjunto {red, orange, yellow, green, blue, brown, black,
white}. Os tipos podem, ainda, ser especificados por regras; por exemplo, a declarao
de uma estrutura em C define um tipo. Neste caso, o tipo inclui qualquer objeto com
os campos declarados na ordem declarada; os campos individuais possuem tipos que
especificam os intervalos de valores permitidos e sua interpretao. (Representamos
o tipo de uma estrutura como o produto dos tipos de seus campos constituintes, em
ordem.) Alguns tipos so predefinidos por uma linguagem de programao; outros so
construdos pelo programador. O conjunto de tipos em uma linguagem de programao,
junto com as regras que utilizam tipos para especificar o comportamento do programa,
so chamados, coletivamente, um sistema de tipos.
Tipo
Categoria abstrata que especifica propriedades
mantidas em comum por todos os seus membros.
Tipos comuns incluem integer, list e character.

4.2.1 A finalidade dos sistemas de tipos


Os projetistas de linguagens de programao introduzem sistemas de tipos de modo
que possam especificar o comportamento do programa em um nvel mais preciso do
que possvel em uma gramtica livre de contexto. O sistema de tipos cria um segundo
vocabulrio para descrever a forma e o comportamento dos programas vlidos. A
anlise de um programa do ponto de vista do seu sistema de tipos gera informaes
que no podem ser obtidas usando as tcnicas de anlises lxica e sinttica. Em um
compilador, essa informao normalmente usada para trs finalidades distintas:
segurana, expressividade e eficincia de execuo.

A garantia da segurana em tempo de execuo


Um sistema de tipos bem projetado ajuda o compilador a detectar e evitar erros de
execuo. Este sistema deve garantir que os programas sejam bem comportados
ou seja, o compilador e o sistema de runtime podem identificar todos os programas
malformados antes que executem uma operao que cause um erro de execuo. Na
verdade, o sistema de tipos no consegue capturar todos os programas malformados; o
conjunto de programas malformados no computvel. Alguns erros de runtime, como
a desreferenciao1 de um ponteiro fora dos limites, tm efeitos bvios (e normalmente
catastrficos). Outros, como a interpretao equivocada de um inteiro como um nmero
de ponto flutuante, podem ter efeitos sutis e acumulativos. O compilador deve eliminar
o mximo de erros de runtime que puder, usando as tcnicas de verificao de tipos.
Inferncia de tipos
Processo de determinar um tipo para cada nome e cada
expresso no cdigo.

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.

4.2 Introduo aos sistemas de tipo 145

FIGURA 4.1 Tipos de resultado para a adio em FORTRAN 77 .

situao e a informaria antes que o programa fosse executado um exemplo simples


de segurana de tipos.
A alternativa exigir que o programador escreva uma converso explcita (conhecida
como cast).
Para algumas linguagens, o compilador no pode inferir os tipos para todas as expresses. APL, por exemplo, no possui declaraes, permite que o tipo de uma varivel
mude em qualquer atribuio e que o usurio entre com um cdigo qualquer nas
requisies (prompts) de entrada. Embora isto torna a APL poderosa e expressiva,
exige que a implementao realize alguma quantidade de inferncia e verificao de
tipo em tempo de execuo. A alternativa, naturalmente, considerar que o programa se
comporta bem e ignorar esta verificao. Em geral, isto leva a um mau comportamento
e o programa malsucedido. Em APL, muitos dos recursos avanados dependem bastante da disponibilidade da informao de tipo e dimenso.

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 segurana um forte motivo para usar linguagens tipadas. Uma implementao de


linguagem que garanta capturar a maioria dos erros relacionados a tipo antes que sejam
executados pode simplificar o projeto e a implementao dos programas. Uma linguagem em que cada expresso pode receber um tipo no ambguo chamada fortemente
tipada. Se cada expresso pode ser tipada em tempo de compilao, a linguagem estaticamente tipada; se algumas expresses s podem ser tipadas em tempo de execuo,
a linguagem dinamicamente tipada. Existem outras duas alternativas: uma linguagem
no tipada, como o cdigo assembly ou BCPL, e uma fracamente tipada com um
sistema de tipos fraco.

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.

146 CAPTULO 4 Anlise sensvel ao contexto

ponto flutuante. Dadas duas clulas a e b, tanto a+b quanto a #+ b so expresses


vlidas, e nenhuma delas realiza qualquer converso em seus operandos.
Em contraste, at mesmo as linguagens tipadas mais antigas utilizam sobrecarga para
especificar comportamento complexo. Conforme descrevemos na seo anterior, FORTRAN tem um nico operador de adio, +, e usa a informao de tipo para determinar
como ele deve ser implementado. ANSI C usa prottipos de funo declaraes do
nmero e tipo dos parmetros de uma funo e o tipo do seu valor retornado para
converter argumentos para os tipos apropriados. A informao de tipo determina o efeito
do autoincremento de um ponteiro em C; a quantidade de incremento determinada
pelo tipo do ponteiro. As linguagens orientadas a objeto usam a informao de tipo
para selecionar a implementao apropriada em cada chamada de procedimento. Por
exemplo, Java seleciona entre um construtor padro (default) e um especializado,
examinando a lista de argumentos do construtor.

A gerao de um cdigo melhor


Um sistema de tipos bem projetado oferece ao compilador informaes detalhadas sobre
cada expresso no programa informaes que normalmente podem ser usadas para
produzir tradues mais eficientes. Considere a implementao da adio em FORTRAN 77. O compilador pode determinar completamente os tipos de todas as expresses, de modo que pode consultar uma tabela semelhante que aparece na Figura4.2.
O cdigo direita mostra a operao ILOC para a adio, junto com as converses
especificadas no padro FORTRAN para cada expresso de tipos misturados. A tabela
completa incluiria todos os casos da Figura4.1.
Em uma linguagem com tipos que no podem ser totalmente determinados em tempo
de compilao, parte desta verificao deve ser adiada at o tempo de execuo. Para
conseguir isto, o compilador precisa emitir um cdigo semelhante ao pseudocdigo
na Figura4.3. A figura s mostra o cdigo para dois tipos numricos, inteiro e real.

FIGURA 4.2 Implementao da adio em FORTRAN 77 .

4.2 Introduo aos sistemas de tipo 147

FIGURA 4.3 Esquema para implementar adio com verificao de tipo em tempo de execuo.

148 CAPTULO 4 Anlise sensvel ao contexto

Uma implementao realista precisaria abranger todo o conjunto de possibilidades.


Embora esta tcnica garanta a segurana em tempo de execuo, acrescenta um
overhead significativo a cada operao. Um dos objetivos da verificao em tempo
de compilao oferecer tal segurana sem o custo em tempo de execuo (custo
de runtime).
Observe que a verificao de tipo em tempo de execuo exige uma representao
de runtime para o tipo. Assim, cada varivel tem um campo de valor e um de tag. O
cdigo que realiza a verificao em runtime a estrutura if-then-else aninhada na
Figura4.3 conta com os campos de tag, enquanto a aritmtica usa os campos de
valor. Com tags, cada item de dados precisa de mais espao, ou seja, mais bytes na
memria. Se uma varivel armazenada em um registrador, tanto seu valor quanto seu
tag precisaro de registradores. Finalmente, as tags precisam ser inicializadas, lidas,
comparadas e escritas em runtime. Todas essas atividades aumentam o overhead para
uma simples operao de adio.
O benefcio de manter x em um registrador vem da
velocidade de acesso. Se a tag de x estiver na RAM, este
benefcio se perde.
Uma alternativa usar parte do espao em x para
armazenar a tag e reduzir o intervalo de valores que x
pode conter.

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.

4.2 Introduo aos sistemas de tipo 149

4.2.2 Componentes de um sistema de tipo


Um sistema de tipo para uma linguagem moderna tpica tem quatro componentes principais: um conjunto de tipos bsicos, ou tipos embutidos; regras para construir novos
tipos a partir dos existentes; um mtodo para determinar se dois tipos so equivalentes
ou compatveis; e regras para inferir o tipo de cada expresso da linguagem-fonte.
Muitas linguagens tambm incluem regras para a converso implcita de valores de um
tipo para outro com base no contexto. Esta seo descreve cada um destes com mais
detalhes, com exemplos de linguagens de programao populares.

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.

150 CAPTULO 4 Anlise sensvel ao contexto

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).

Tipos compostos e construdos


Embora os tipos bsicos de uma linguagem de programao normalmente ofeream
uma abstrao adequada dos tipos reais de dados tratados diretamente pelo hardware,
frequentemente so inadequados para representar o domnio de informaes necessrio
aos programas. Os programas rotineiramente lidam com estruturas de dados mais complexas, como grafos, rvores, tabelas, arrays, registros, listas e pilhas. Essas estruturas
consistem em um ou mais objetos, cada um com seu prprio tipo. A capacidade de construir novos tipos para esses objetos compostos e agregados um recurso essencial de
muitas linguagens de programao, que permite ao programador organizar informaes
de maneiras novas e especficas ao programa. Unir essa organizao ao sistema de tipos
melhora a capacidade do compilador de detectar programas malformados, e tambm
permite que a linguagem expresse operaes de nvel mais alto, como uma atribuio
de estrutura inteira.
Veja, por exemplo, o caso da linguagem Lisp, que oferece bastante suporte para a
programao com listas. Nela, uma lista um tipo construdo. Uma lista , ou o valor
designado nil, ou (cons first rest), onde first um objeto, rest uma
lista, e cons um construtor que cria uma lista a partir de seus dois argumentos. Esta
implementao Lisp pode verificar cada chamada a cons para garantir que seu segundo
argumento seja, de fato, uma lista.

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

4.2 Introduo aos sistemas de tipo 151

um nome implcito, calculado, ao invs de um explcito, designado pelo programador.


A declarao C int a[100][200]; reserva espao para 100200=20.000
inteiros e garante que eles possam ser endereados usando o nome a. As referncias
a[1][17] e a[2][30] acessam locais de memria distintos e independentes.
A propriedade essencial de um array que o programa pode computar nomes para
cada um de seus elementos usando nmeros (ou algum outro tipo ordenado, discreto)
como subscritos.
O suporte para operaes sobre arrays varia bastante. FORTRAN 90, PL/I e APL
admitem a atribuio de arrays inteiros ou parciais, e, tambm, a aplicao elemento
a elemento de operaes aritmticas com arrays. Para os arrays 1010 x, y e
z, indexados de 1 a 10, a instruo x=y+z sobrescreveria cada x[i,j] como
y[i,j]+z[i,j] para todo 1i, j10. APL usa a noo de operaes com
array mais do que na maioria das linguagens, e inclui operadores para produto interno,
produto externo e vrios tipos de redues. Por exemplo, a reduo de soma de y, escrita como x +/y, atribui a x a soma escalar dos elementos de y.
Um array pode ser visto como um tipo construdo, pois o construmos especificando
o tipo dos seus elementos. Assim, um array 1010 de inteiros tem o tipo array
bidimensional de inteiros. Algumas linguagens incluem as dimenses do array em
seu tipo; assim, um array 1010 de inteiros tem um tipo diferente de um array
1212 de inteiros. Isto permite que o compilador capture operaes de array em
que as dimenses so incompatveis como um erro de tipo. A maioria das linguagens
permite arrays de qualquer tipo bsico; algumas linguagens tambm permitem arrays
de tipos construdos.

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:

152 CAPTULO 4 Anlise sensvel ao contexto

O compilador mapeia cada elemento de um tipo enumerado para um valor distinto. Os


elementos de um tipo enumerado so ordenados, assim, comparaes entre elementos
do mesmo tipo fazem sentido; por exemplo, Segunda<Tera e Junho<Julho.
Operaes que comparam diferentes tipos enumerados no fazem sentido por
exemplo, Tera>Setembro e devem produzir um erro de tipo; Pascal garante
que cada tipo enumerado se comporte como se fosse uma subfaixa dos inteiros. Por
exemplo, o programador pode declarar um array indexado pelos elementos de um
tipo enumerado.

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.

4.2 Introduo aos sistemas de tipo 153

UMA VISO ALTERNATIVA DAS ESTRUTURAS


A viso clssica das estruturas trata cada tipo de estrutura como um tipo distinto. Esta
tcnica para os tipos de estrutura segue o tratamento de outras agregaes, como
arrays e strings; ela parece natural, e faz distines que so teis para o programador.
Por exemplo, um n de rvore com dois filhos provavelmente deve ter um tipo
diferente de um n de rvore com trs filhos; presume-se que eles sejam usados
em situaes diferentes. Um programa que atribui um n de trs filhos a um n
de dois filhos deve gerar um erro de tipo e uma mensagem de advertncia para o
programador.
Sob o ponto de vista do sistema de runtime, porm, tratar cada estrutura como um
tipo distinto complica o quadro. Com tipos de estrutura distintos, a heap contm um
conjunto qualquer de objetos retirados de um conjunto qualquer de tipos. Isto torna
difcil raciocinar sobre programas que lidam diretamente com os objetos na heap,
como um coletor de lixo, por exemplo. Para simplificar tais programas, seus autores s
vezes usam uma tcnica diferente para os tipos de estrutura.
Esse modelo alternativo considera todas as estruturas no programa como de um nico
tipo. As declaraes de estruturas individuais criam, cada uma, uma forma variante
do tipo structure. Este tipo, por si s, a unio de todas essas variantes. Esta tcnica
permite que o programa veja a heap como uma coleo de objetos de um nico tipo,
ao invs de uma coleo de muitos tipos. Esta viso torna o cdigo que manipula a
heap muito mais simples de analisar e otimizar.

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.

O operador de endereo, quando aplicado a um objeto


do tipo t, retorna um valor do tipo ponteiro para t.

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.

Algumas linguagens permitem a manipulao direta de ponteiros. A aritmtica sobre


ponteiros, incluindo autoincremento e autodecremento, permite que o programa construa novos ponteiros. C utiliza o tipo de um ponteiro para determinar as magnitudes
de autoincremento e autodecremento. O programador pode definir um ponteiro para o
incio de um array; o autoincremento avana o ponteiro de um elemento no array para
o prximo elemento.

Se o conjunto de tipos tiver que ser especificado


explicitamente, a funo usa o polimorfismo ad hoc;
se o corpo da funo no especificar tipos, usa o
polimorfismo paramtrico.

154 CAPTULO 4 Anlise sensvel ao contexto

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.

4.2 Introduo aos sistemas de tipo 155

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.

Este esquema sobrecarrega 2 com diferentes


significados em diferentes contextos. A experincia
sugere que os programadores so bons em entender
este tipo de sobrecarga.

156 CAPTULO 4 Anlise sensvel ao contexto

CLASSIFICAO DE SISTEMAS DE TIPOS


Muitos termos so usados para descrever os sistemas de tipos. No texto, apresentamos
os termos linguagens fortemente tipadas, no tipadas e fracamente tipadas. Outras
distines entre sistemas de tipos e suas implementaes so importantes.
Implementaes verificadas e no verificadas. A implementao de uma linguagem
de programao pode decidir realizar verificao suficiente para detectar e impedir
todos os erros de runtime que resultem do mau uso de um tipo. (Isto realmente pode
excluir alguns erros especficos de valor, como a diviso por zero). Tal implementao
chamada fortemente verificada. O oposto desta a implementao no verificada
que considera um programa bem formado. Entre esses extremos encontra-se um
espectro de implementaes fracamente verificadas, que realizam verificao parcial.
Atividade em tempo de compilao versus em tempo de execuo. Uma linguagem
fortemente tipada pode ter a propriedade de que toda a inferncia e verificao
podem ser feitas em tempo de compilao. Uma implementao que realmente
faz todo esse trabalho em tempo de compilao chamada estaticamente tipada e
estaticamente verificada. Algumas linguagens tm construes que devem ser tipadas
e verificadas em tempo de execuo. Esta so chamadas dinamicamente tipadas e
dinamicamente verificadas. Para confundir ainda mais as coisas, claro, um construtor
de compiladores pode implementar uma linguagem fortemente tipada, estaticamente
tipada com verificao dinmica. Java um exemplo de uma linguagem que pode ser
estaticamente tipada e verificada, exceto para um modelo de execuo que impea
o compilador de ver todo o cdigo-fonte ao mesmo tempo, forando-o a realizar a
inferncia de tipo medida que as classes so carregadas e fazer parte da verificao
em tempo de execuo.

Inferncia de tipos para expresses


O objetivo da inferncia de tipos atribuir um tipo a cada expresso que ocorre em um
programa. O caso mais simples ocorre quando o compilador pode atribuir um tipo a
cada elemento bsico de uma expresso ou seja, para cada folha na rvore sinttica
para uma expresso. Isto requer declaraes para todas as variveis, tipos inferidos para
todas as constantes, e informaes de tipo sobre todas as funes.
Conceitualmente, o compilador pode atribuir um tipo a cada valor na expresso durante
um percurso simples de rvore em ps-ordem. Isto deve permitir que o compilador
detecte cada violao de uma regra de inferncia e a informe em tempo de compilao.
Se a linguagem no possuir um ou mais dos recursos que possibilitam este estilo de inferncia simples, o compilador precisar usar tcnicas mais sofisticadas. Se a inferncia
de tipo em tempo de compilao se tornar muito difcil, o construtor de compiladores
pode ter que mover parte da anlise e verificao para o runtime.
A inferncia de tipos para expresses, neste caso simples, segue diretamente a estrutura da expresso. As regras de inferncia descrevem o problema em termos da
linguagem-fonte. A estratgia de avaliao opera de baixo para cima (bottom-up) na
rvore sinttica. Por esses motivos, a inferncia de tipos para expresses tornou-se um
problema-exemplo clssico para ilustrar a anlise sensvel ao contexto.

Aspectos interprocedurais da inferncia de tipos


A inferncia de tipos para expresses depende, inerentemente, dos outros procedimentos que formam o programa executvel. Mesmo nos sistemas de tipo mais simples
as expresses contm chamadas de funo. O compilador precisa verificar cada uma
delas, garantir que cada parmetro real tenha tipo compatvel com o parmetro formal
correspondente, e determinar o tipo de qualquer valor retornado para uso em outras
inferncias.

4.2 Introduo aos sistemas de tipo 157

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.

158 CAPTULO 4 Anlise sensvel ao contexto

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.

4.3 O FRAMEWORK DE GRAMTICA DE ATRIBUTO


Um formalismo que tem sido proposto para realizar anlise sensvel ao contexto
a gramtica de atributo, ou gramtica livre de contexto atribuda, que consiste
em uma gramtica livre de contexto aumentada por um conjunto de regras que
especificam computaes. Cada regra define um valor, ou atributo, em termos dos
valores de outros atributos. A regra associa o atributo a um smbolo especfico da
gramtica; cada ocorrncia do smbolo em uma rvore de derivao (ou rvore
sinttica) tem uma ocorrncia correspondente do atributo. As regras so funcionais;
no implicam uma ordem de avaliao especfica, e definem o valor de cada atributo
de forma nica.
Atributo
Valor ligado a um ou mais dos ns em uma rvore de
derivao.

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.

4.3 O framework de gramtica de atributo 159

Smbolo

Atributos

Number
Sign
List
Bit

value
negative
position, value
position, value

Neste caso, nenhum atributo necessrio para os smbolos terminais.


A Figura4.5 mostra as produes de SBN elaboradas com regras de atribuio.
Subscritos so acrescentados aos smbolos da gramtica sempre que um smbolo
especfico aparece vrias vezes em uma nica produo. Esta prtica retira a
ambiguidade das referncias a este smbolo nas regras. Assim, as duas ocorrncias
de List na produo 5 tm subscritos, tanto na produo quanto nas regras correspondentes.
As regras acrescentam atributos aos ns da rvore de derivao por meio de seus
nomes. O atributo mencionado em uma regra deve ser invocado a cada ocorrncia
desse tipo de n.
Cada regra especifica o valor de um atributo em termos de constantes literais e os
atributos de outros smbolos na produo. Uma regra pode passar informaes do
lado esquerdo da produo para o seu lado direito; e, tambm, no sentido inverso.
As regras para a produo 4 passam informaes nos dois sentidos. A primeira regra

FIGURA 4.5 Gramtica de atributo para nmeros binrios com sinal.

160 CAPTULO 4 Anlise sensvel ao contexto

define Bit.position como List.position, enquanto a segunda define List.value


como Bit.value. Gramticas de atributo mais simples podem resolver este problema em particular; escolhemos esta para demonstrar recursos em particular das
gramticas de atributo.
Dada uma string na gramtica SBN, as regras de atribuio definem Number.value
como o valor decimal da string de entrada binria. Por exemplo, a string 101 causa a
atribuio mostrada na Figura4.6a. (Os nomes para value, number e position
so truncados na figura.) Observe que Number.value tem o valor 5.
Para avaliar uma rvore de derivao atribuda para alguma sentena em L(SBN),
os atributos especificados nas diversas regras so invocados para cada n na rvore.
Isto cria, por exemplo, uma ocorrncia de atributo para value e position em
cada n List. Cada regra define implicitamente um conjunto de dependncias; o
atributo sendo definido depende de cada argumento para a regra. Tomadas sobre a
rvore de derivao inteira, essas dependncias formam um grafo de dependncia de
atributos. As arestas no grafo seguem o fluxo de valores na avaliao de uma regra;
uma aresta de nodei.fieldj para nodek.fieldl indica que a regra definindo nodek.fieldl
usa o valor de nodei.fieldj como uma de suas entradas. A Figura4.6b mostra o grafo
de dependncia de atributo induzido pela rvore de derivao para a string 101.
Atributo sintetizado
Atributo definido totalmente em termos dos atributos
do n, seus filhos e constantes.
Atributo herdado
Atributo definido totalmente em termos dos atributos
prprios do n e daqueles de seus irmos ou seu pai na
rvore de derivao (alm de constantes).
A regra node.field 1 pode ser tratada como
sintetizada ou herdada.

O fluxo bidirecional de valores que antes observamos (por exemplo, na produo 4)


aparece no grafo de dependncia, onde as setas indicam tanto o fluxo para cima, at a
raiz (Number), quanto para baixo, at as folhas. Os ns List mostram este efeito mais
claramente. Distinguimos entre os atributos com base no sentido do fluxo de valor.
Atributos sintetizados so definidos pelo fluxo de informaes de baixo para cima;
uma regra que define um atributo para o lado esquerdo da produo cria um atributo
sintetizado, que pode retirar valores do prprio n, dos seus descendentes na rvore de
derivao e de constantes. Atributos herdados so definidos pelo fluxo de informaes
de cima para baixo e lateralmente; a regra que define um atributo para o lado direito da
produo cria um atributo herdado. Como a regra de atribuio pode nomear qualquer
smbolo usado na produo correspondente, um atributo herdado pode retirar valores
do prprio n, de seu pai e de seus irmos na rvore de derivao, e de constantes. A

FIGURA 4.6 rvore atribuda para o nmero binrio com sinal 101.

4.3 O framework de gramtica de atributo 161

Figura4.6b mostra que os atributos value e negative so sintetizados, enquanto


position herdado.
Qualquer esquema para avaliar atributos precisa respeitar os relacionamentos codificados implicitamente no grafo de dependncia de atributo. Cada atributo deve ser
definido por alguma regra. Se esta regra depender dos valores de outros atributos, no
poder ser avaliada at que todos esses valores tenham sido definidos. Se no depender,
ento deve produzir seu valor a partir de uma constante ou de alguma fonte externa.
Desde que nenhuma regra conte com seu prprio valor, as regras devem definir cada
valor de forma nica.
Naturalmente, a sintaxe das regras de atribuio permite que uma regra referencie seu
prprio resultado, direta ou indiretamente. Uma gramtica de atributo contendo tais
regras malformada. Dizemos que essas regras so circulares porque podem criar
um ciclo no grafo de dependncia. Por enquanto, vamos ignorar a circularidade; a
Seo4.3.2 trata desta questo.
O grafo de dependncia captura o fluxo de valores que um avaliador precisa respeitar
na avaliao de um exemplar de uma rvore atribuda. Se a gramtica for no circular,
ela impe uma ordem parcial sobre os atributos. Esta ordem parcial determina quando
a regra que define cada atributo pode ser avaliada. A ordem de avaliao no est
relacionada ordem em que as regras aparecem na gramtica.
Considere a ordem de avaliao para as regras associadas ao n List mais alto na
rvore o filho direita de Number. O n resulta da aplicao da produo 5, List
List Bit; esta aplicao acrescenta trs regras avaliao. As duas regras que definem
atributos herdados para os filhos do n List precisam ser executadas primeiro. Elas
dependem do valor de List.position e definem os atributos position para as
subrvores do n. A terceira regra, que define o atributo value do n List, no pode
ser executada at que as duas subrvores tenham definido os atributos value. Como
estas subrvores no podem ser avaliadas at que as duas primeiras regras no n List
o tenham sido, a sequncia de avaliao incluir as duas primeiras regras mais cedo,
e a terceira muito mais tarde.
Para criar e usar uma gramtica de atributo, o construtor de compiladores determina um
conjunto de atributos para cada smbolo da gramtica e projeta um conjunto de regras
para calcular seus valores. Essas regras especificam uma computao para qualquer rvore de derivao vlida. Para criar uma implementao, ele precisa criar um avaliador,
o que pode ser feito com um programa ad hoc ou usando um gerador de avaliador a
opo mais atraente. O gerador de avaliador toma como entrada a especificao para
a gramtica de atributo, e produz como sada, o cdigo para um avaliador. Este o
atrativo das gramticas de atributo para o construtor de compiladores; as ferramentas
usam uma especificao de alto nvel, no procedimental, e automaticamente produzem
uma implementao.
Um detalhe crtico por trs do formalismo da gramtica de atributo a noo de que as
regras de atribuio podem ser associadas s produes na gramtica livre de contexto.
Como as regras so funcionais, os valores que produzem so independentes da ordem
de avaliao, para qualquer ordem que respeite os relacionamentos incorporados no
grafo de dependncia de atributo. Na prtica, qualquer ordem que avalia uma regra,
somente aps todas as suas entradas terem sido definidas respeita as dependncias.

4.3.1 Mtodos de avaliao


O modelo de gramtica de atributo tem uso prtico somente se pudermos criar avaliadores que interpretem as regras para avaliar um exemplar do problema automaticamente

Circularidade
Uma gramtica de atributo circular se puder, para
algumas entradas, criar um grafo de dependncia
cclico.

162 CAPTULO 4 Anlise sensvel ao contexto

uma rvore de derivao especfica, por exemplo. Muitas tcnicas de avaliao de


atributo foram propostas na literatura. Em geral, todas encontram-se em uma destas
trs categorias principais.
1. Mtodos dinmicos. Estas tcnicas utilizam a estrutura de uma particular rvore
de derivao atribuda para determinar a ordem de avaliao. O artigo original
de Knuth sobre gramticas de atributo props um avaliador que operasse de
maneira semelhante a uma arquitetura de computador de fluxo de dados cada
regra era disparada assim que todos os seus operandos estivessem disponveis.
Em termos prticos, isto pode ser implementado usando uma fila de atributos
que estejam prontos para avaliao. medida que cada atributo avaliado, seus
sucessores no grafo de dependncia de atributos tm sua prontido verificada
(ver Seo 12.3). Um esquema relacionado construiria o grafo de dependncia
de atributos, o ordenaria topologicamente e usaria esta ordem para avaliar os
atributos.
2. Mtodos desatentos. Nestes mtodos, a ordem de avaliao independe da
gramtica de atributo e da particular rvore de derivao atribuda. Presume-se
que o projetista do sistema seleciona um mtodo considerado apropriado para a
gramtica de atributo e para o ambiente de avaliao. Exemplos deste estilo de
avaliao incluem passagens repetidas da esquerda para a direita (at que todos
os atributos tenham valores), da direita para a esquerda, e alternadas da esquerda
para a direita e da direita para a esquerda. Estes mtodos tm implementaes
simples e overheads de runtime relativamente pequenos. Mas falta-lhes, naturalmente, qualquer melhoria que possa ser derivada do conhecimento da rvore
especfica sendo atribuda.
3. Mtodos baseados em regra. Mtodos baseados em regra baseiam-se em uma
anlise esttica da gramtica de atributo para construir uma ordem de avaliao.
Nesse framework, o avaliador confia na estrutura gramatical; assim, a rvore de
derivao guia a aplicao das regras. No exemplo do nmero binrio com sinal,
a ordem de avaliao para a produo 4 deve usar a primeira regra para definir
Bit.position, fazer a recurso para baixo at Bit e, no retorno, usar Bit.value
para definir List.value. De modo semelhante, para a produo 5, deve avaliar as
duas primeiras regras para definir os atributos position no lado direito, e depois
fazer a recurso para baixo at cada filho. No retorno, pode avaliar a terceira regra para definir o campo List.value do n List pai. Ferramentas que realizam a
anlise esttica necessria off-line podem produzir rpidos avaliadores baseados
em regra.

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

4.3 O framework de gramtica de atributo 163

classes mais genricas de gramticas de atributo no circulares; algumas, como


as gramticas de atributo fortemente no circulares, possuem testes de tempo
polinomial para verificar a condio de pertinncia.
2. Avaliao. O construtor de compiladores pode usar um mtodo de avaliao que
concede um valor a cada atributo, mesmo para aqueles envolvidos em ciclos. O
avaliador pode percorrer o ciclo e atribuir valores apropriados ou valores default,
evitando assim os problemas associados a uma falha para atribuir a rvore totalmente.
Na prtica, a maioria dos sistemas de gramtica de atributo restringe sua ateno a
gramticas no circulares. Os mtodos de avaliao baseados em regra podem falhar ao
construir um avaliador se a gramtica de atributo for circular. Os mtodos desatentos
e os dinmicos tentam avaliar um grafo de dependncia circular, mas simplesmente
no conseguiro definir algumas das ocorrncias de atributo.

4.3.3 Exemplos estendidos


Para melhor entender os pontos fortes e fracos das gramticas de atributo como ferramenta, trabalharemos com dois exemplos mais detalhados que podem surgir em um
compilador: inferindo tipos para rvores de expresso em uma linguagem simples, tipo
Algol, e estimativa do tempo de execuo, em ciclos, para uma sequncia de cdigo direta.

Inferncia de tipos de expresso


Qualquer compilador que tente gerar cdigo eficiente para uma linguagem tipada
precisa encarar o problema de inferir os tipos para cada expresso no programa. Este
problema conta, inerentemente, com a informao sensvel ao contexto; o tipo associado
a um name ou num depende de sua identidade seu nome textual , ao invs de
sua categoria sinttica.
Considere uma verso simplificada do problema de inferncia de tipos para expresses
derivadas da gramtica de expresso clssica dada no Captulo3. Suponha que as expresses sejam representadas como rvores sintticas, e que qualquer n representando
name ou num j tenha um atributo type. (Vamos retornar ao problema de levar a
informao de tipo para esses atributos type mais adiante neste captulo.) Para cada
operador aritmtico da gramtica, precisamos de uma funo que mapeie os dois tipos
de operando para um tipo de resultado. Chamaremos estas funes de F+, F, F e F,
que codificam a informao encontrada em tabelas, como a que aparece na Figura4.1.
Com essas suposies, podemos escrever regras de atribuio simples que definem um
atributo type para cada n na rvore. A Figura4.7 mostra as regras de atribuio.
Se a tem o tipo integer (indicado por I) e c tem o tipo real (indicado por R), ento este esquema gera a seguinte rvore de derivao atribuda para a string de entrada a 2c:

164 CAPTULO 4 Anlise sensvel ao contexto

FIGURA 4.7 Gramtica de atributo para inferir tipos de expresso.

Os ns folha tm seus atributos type inicializados de modo apropriado. O restante


dos atributos definido pelas regras da Figura4.7, com a suposio de que F+, F, F
e F refletem as regras do FORTRAN 77.
Uma viso mais aproximada das regras de atribuio mostra que todos os atributos so
sintetizados. Assim, todas as dependncias fluem de um filho para seu pai na rvore
de derivao. Essas gramticas s vezes so chamadas gramticas S-atribudas. Este
estilo de atribuio tem um esquema de avaliao simples, baseado em regra. Ele se
encaixa bem com a anlise sinttica bottom-up; cada regra pode ser avaliada quando
o parser reduz o lado direito correspondente. O paradigma de gramtica de atributo
encaixa-se bem a este problema. A especificao curta, e facilmente compreendida,
alm de levar a um avaliador eficiente.
A inspeo cuidadosa da rvore de expresso atribuda mostra dois casos em que uma
operao tem um operando cujo tipo diferente do tipo do resultado da operao. Em
FORTRAN 77, isto exige que o compilador insira uma operao de converso entre
o operando e o operador. Para o n Termo que representa a multiplicao de 2 e c,
o compilador converteria 2 de uma representao de inteiro para uma de real. Para o
n Expr na raiz da rvore, ele converteria a de um inteiro para um real. Infelizmente,
mudanas na rvore de derivao no se ajustam muito bem no paradigma de gramtica
de atributo.
Para representar essas converses na rvore atribuda, poderamos acrescentar um
atributo a cada n que mantm seu tipo convertido, junto com as regras para definir
os atributos de modo apropriado. Como alternativa, seria possvel contar com o
processo que gera cdigo a partir da rvore para comparar os dois tipos pai e
filho durante a travessia e inserir a converso necessria. A primeira tcnica
acrescenta algum trabalho durante a avaliao de atributo, mas localiza toda a
informao necessria para uma converso em um nico n da rvore sinttica.

4.3 O framework de gramtica de atributo 165

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.

Um simples estimador de tempo de execuo


Como segundo exemplo, considere o problema de estimar o tempo de execuo de uma
sequncia de instrues de atribuio. Podemos gerar uma sequncia de atribuies
acrescentando trs novas produes gramtica de expresso clssica:

onde Expr vem da gramtica de expresso. A gramtica resultante simplista porque


permite apenas identificadores simples como variveis, e no contm chamadas de
funo. Apesar disto, complexa o suficiente para transmitir os problemas que surgem
na estimativa do comportamento em tempo de execuo.
A Figura4.8 mostra uma gramtica de atributo que estima o tempo de execuo de um
bloco de instrues de atribuio. As regras de atribuio estimam o contador total de

FIGURA 4.8 Gramtica de atributo simples para estimar o tempo de execuo.

166 CAPTULO 4 Anlise sensvel ao contexto

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.

Melhorando a estimativa de custo de execuo


Para tornar este exemplo mais realstico, podemos melhorar o modelo de como o
compilador trata as variveis. A verso inicial da nossa gramtica de atributo para estimativa de custo considera que o compilador ingenuamente gera uma operao load
separada para cada referncia a uma varivel. Para a atribuio x=y+y, o modelo
conta duas operaes load para y. Poucos compiladores gerariam um load redundante
para y. Mais provavelmente, eles gerariam uma sequncia como:

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:

A chave para fazer isto funcionar o teste nome no foi carregado.


Para implementar este teste, o construtor de compiladores pode acrescentar um atributo
que mantm o conjunto de todas as variveis j carregadas. A produo Bloco
Atrib pode inicializar o conjunto. As regras devem orientar as rvores de expresso
a passarem o conjunto atravs de cada atribuio. Isto sugere aumentar cada n com
dois conjuntos, Before e After. O conjunto Before para um n contm os
lexemas de todos os nomes que ocorreram anteriormente no Bloco; cada um destes
j deve ter sido carregado. O conjunto After de um n contm todos os nomes
em seu conjunto Before, mais quaisquer outros nomes que seriam carregados na
subrvore enraizada nesse n.
As regras expandidas para Fator aparecem na Figura4.9. O cdigo considera que ele
pode obter o nome textual o lexema de cada nome. A primeira produo, que
deriva ( Expr ), copia o conjunto Before para baixo na subrvore Expr e o conjunto
After para cima at Fator. A segunda produo, que deriva num, simplesmente copia
o conjunto Before do seu pai para o conjunto After do seu pai. num deve ser uma
folha na rvore; portanto, nenhuma outra ao necessria. A produo final, que

4.3 O framework de gramtica de atributo 167

FIGURA 4.9 Regras para rastrear loads em produes Fator.

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.

De volta inferncia de tipos de expresso


Na discusso inicial sobre a inferncia de tipos de expresso, consideramos que os atributos
nome.type e num.type j estavam definidos por algum mecanismo externo. Para

168 CAPTULO 4 Anlise sensvel ao contexto

FIGURA 4.10 Regras de cpia para rastrear loads.

4.3 O framework de gramtica de atributo 169

preencher esses valores usando uma gramtica de atributo, o construtor de compiladores


precisaria desenvolver um conjunto de regras para a parte da gramtica que lida com
declaraes.
Essas regras precisariam registrar a informao de tipo para cada varivel nas produes
associadas sintaxe de declarao; coletar e agregar esta informao de modo que
um pequeno conjunto de atributos contivessem a informao necessria sobre todas
as variveis declaradas; propagar esta informao pela rvore de derivao at um n
que seja um ancestral de todas as instrues executveis, e depois copi-la para baixo,
para cada expresso. Finalmente, em cada folha que um nome ou num, as regras
precisariam extrair os fatos apropriados a partir da informao agregada.
O conjunto de regras resultante seria semelhante quelas que desenvolvemos para
rastrear loads, porm mais complexo no nvel detalhado. Essas regras tambm criam
atributos grandes e complexos, que devem ser copiados pela rvore de derivao. Em
uma implementao simples, cada ocorrncia de uma regra de cpia criaria uma nova
cpia. Algumas dessas cpias poderiam ser compartilhadas, mas muitas das verses
criadas pela juno de informaes de vrios filhos seriam diferentes (e, assim, precisam
ser cpias distintas). O mesmo problema surge com os conjuntos Before e After
no exemplo anterior.

Uma melhoria final no estimador de custo de execuo


Embora o rastreamento de loads melhore a fidelidade dos custos de execuo estimados,
muitos outros refinamentos so possveis. Considere, por exemplo, o impacto dos conjuntos finitos de registradores sobre o modelo. At aqui, nosso modelo considerou que
o computador de destino fornece um conjunto ilimitado de registradores. Na realidade,
os computadores disponibilizam pequenos conjuntos de registradores. Para modelar
a capacidade do conjunto de registradores, o estimador poderia limitar o nmero de
valores permitidos nos conjuntos Before e After.
Numa primeira etapa, temos de substituir a implementao de Before e After, que
foram implementados como conjuntos de tamanho arbitrrio; neste modelo refinado,
os conjuntos devem manter exatamente k valores, onde k o nmero de registradores
disponveis para manter os valores de variveis. Em seguida, temos que reescrever as
regras para a produo Fator nome a fim de modelar a ocupao do registrador. Se
um valor ainda no foi carregado, e um registrador est disponvel, ele cobra por um
nico load. Se um load for necessrio, mas nenhum registrador estiver disponvel, pode
remover um valor de algum registrador e cobrar pelo load. A escolha de qual valor
remover complexa (a discusso a este respeito encontra-se no Captulo13). Como
a regra para Atrib sempre cobra por um store, o valor na memria ser atual. Assim,
nenhum store necessrio quando um valor removido. Finalmente, se o valor j
tiver sido carregado e ainda estiver em um registrador, ento nenhum custo cobrado.
Este modelo complica o conjunto de regras para Fator nome e exige uma condio
inicial ligeiramente mais complexa (na regra para Bloco Atrib). Porm, no complica
as regras de cpia para todas as outras produes. Assim, a preciso do modelo no
aumenta significativamente a complexidade do uso de uma gramtica de atributo. Toda
a complexidade adicionada cai nas poucas regras que manipulam diretamente o modelo.

4.3.4 Problemas com a tcnica de gramtica de atributo


Os exemplos anteriores ilustram muitas das questes computacionais que surgem no
uso das gramticas de atributo para realizar computaes sensveis ao contexto sobre
rvores sintticas. Algumas impem problemas particulares para o uso de gramticas de

170 CAPTULO 4 Anlise sensvel ao contexto

atributo em um compilador. Em particular, a maioria das aplicaes das gramticas de


atributo no front end de um compilador assume que os resultados da atribuio devem
ser preservados, normalmente na forma de uma rvore de derivao atribuda. Esta
seo detalha o impacto dos problemas que vimos nos exemplos anteriores.

Tratamento de informaes no locais


Alguns problemas so mapeados claramente no paradigma de gramtica de atributo,
particularmente aqueles em que toda a informao flui no mesmo sentido. Porm, os
problemas com um padro complexo de fluxo de informaes podem ser difceis de
expressar como gramticas de atributo. Uma regra de atribuio pode nomear somente
valores associados a um smbolo da gramtica que aparece na mesma produo; isto
restringe a regra para usar apenas informaes prximas, ou locais. Se a computao
exigir um valor no local, a gramtica de atributo deve incluir regras de cpia para
mover esses valores para os pontos onde so usados.
As regras de cpia podem inchar o tamanho de uma gramtica de atributo; compare as
Figuras4.8,4.9 e4.10. O implementador precisa escrever cada uma dessas regras. No
avaliador, cada uma das regras precisa ser executada, criando novos atributos e trabalho
adicional. Quando a informao agregada, como na regra de declarar-antes-de-usar
ou no framework para estimar os tempos de execuo, uma nova cpia da informao
deve ser feita toda vez que uma regra muda o valor de uma agregao. Essas regras
de cpia acrescentam outra camada de trabalho s tarefas de escrever e avaliar uma
gramtica de atributo.

Gerenciamento de espao de armazenamento


Para exemplos realistas, a avaliao produz grandes quantidades de atributos. O uso de
regras de cpia para mover informaes pela rvore sinttica pode multiplicar o nmero
de ocorrncias de atributo que a avaliao cria. Se a gramtica agrega informaes em
estruturas complexas para passar informaes de declarao pela rvore sinttica, por
exemplo , os atributos individuais podem ser grandes. O avaliador precisa gerenciar
o espao de armazenamento para atributos; um esquema de gerenciamento de espao
de armazenamento fraco pode ter impacto negativo desproporcionalmente grande sobre
os requisitos de recursos do avaliador.
Se o avaliador puder determinar quais valores de atributo podem ser usados aps
a avaliao, ser capaz de reutilizar algum espao de armazenamento de atributo
recuperando o espao para os valores que podem nunca ser usados novamente. Por
exemplo, uma gramtica de atributo que avalie uma rvore de expresso para um
nico valor pode retorn-lo ao processo que o chamou. Nesse cenrio, os valores intermedirios calculados nos ns interiores poderiam ser descartados nunca usados
novamente e, assim, candidatos recuperao. Por outro lado, se a rvore resultante
da atribuio for persistente e sujeita a inspeo posterior como poderia ser o caso
em uma gramtica de atributo para inferncia de tipos , ento o avaliador deve assumir que uma fase posterior do compilador pode percorrer a rvore e inspecionar
atributos arbitrrios. Neste caso, ele no pode recuperar o espao de armazenamento
para qualquer uma das ocorrncias de atributo.
Este problema reflete um choque fundamental entre a natureza funcional do paradigma
de gramtica de atributo e o uso imperativo para o qual ele poderia ser colocado no
compilador. Os usos possveis de um atributo em fases posteriores do compilador tm o
efeito de acrescentar dependncias deste atributo a usos no especificados na gramtica
de atributos, o que modifica o paradigma funcional e remove um de seus pontos fortes:
a capacidade de gerenciar automaticamente o armazenamento de atributos.

4.3 O framework de gramtica de atributo 171

Instanciao da rvore de derivao


Uma gramtica de atributo especifica uma computao relativa rvore sinttica para
uma sentena vlida na gramtica subjacente. O paradigma conta, inerentemente, com
a disponibilidade da rvore de derivao. O avaliador pode simul-la, mas deve se
comportar como se ela existisse. Embora esta rvore seja til para discusses sobre a
anlise sinttica, poucos compiladores realmente a constroem.
Alguns compiladores usam uma rvore sinttica abstrata (AST) para representar
o programa que est sendo compilado. A AST tem a estrutura essencial da rvore
sinttica, mas elimina muitos dos ns internos que representam smbolos no terminais
na gramtica (veja a descrio comeando na pgina 198da Seo 5.2.1). Se o compilador construsse uma AST, ele poderia usar uma gramtica de atributo ligada a uma
gramtica para a AST. Porm, se o compilador no tiver outro uso para a AST, ento
o esforo de programao e o custo em tempo de compilao associado construo
e manuteno da AST devem ser pesados contra os benefcios de usar o formalismo
de gramtica de atributo.

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.

Quebra do paradigma funcional


Um modo de resolver todos esses problemas acrescentar um repositrio central para
atributos. Nesse cenrio, uma regra de atributo pode registrar informaes diretamente
em uma tabela global, onde outras regras possam ler as informaes. Esta tcnica
hbrida pode eliminar muitos dos problemas que surgem de informaes no locais.
Como a tabela pode ser acessada a partir de qualquer regra de atribuio, tem o efeito
de fornecer acesso local a qualquer informao j derivada.
A incluso de um repositrio central para fatos complica as coisas de outra maneira. Se
duas regras se comunicarem por um mecanismo diferente de uma regra de atribuio,
a dependncia implcita entre elas removida do grafo de dependncia de atributos.
As dependncias que faltam devem restringir o avaliador para garantir que ambas as
regras sejam processadas na ordem correta; sem isto, o avaliador pode terminar construindo uma ordem que, embora correta para a gramtica, tem comportamento no
intencionado, devido restrio removida. Por exemplo, passar informaes entre a
sintaxe de declarao e uma expresso executvel atravs de uma tabela pode permitir
que o avaliador processe declaraes aps algumas ou todas as expresses que usam
as variveis declaradas. Se a gramtica usar regras de cpia para propagar esta mesma
informao, estas regras restringem o avaliador a ordens que respeitam as dependncias
incorporadas por estas regras de cpia.

172 CAPTULO 4 Anlise sensvel ao contexto

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?

4.4TRADUO AD HOC DIRIGIDA PELA SINTAXE


Calculadora de quatro funes

Os avaliadores baseados em regra para gramticas de atributo introduzem uma ideia


poderosa, que serve como base para as tcnicas ad hoc usadas para a anlise sensvel
ao contexto em muitos compiladores. Nesses avaliadores, o construtor de compiladores
especifica uma sequncia de aes que esto associadas a produes da gramtica. A
observao bsica, de que as aes exigidas para a anlise sensvel ao contexto podem
ser organizadas em torno da estrutura da gramtica, leva a uma tcnica poderosa,
embora ad hoc, para incorporar esse tipo de anlise no processo de anlise de uma
gramtica livre de contexto. Vamos nos referir a esta tcnica como traduo ad hoc
dirigida pela sintaxe.
Neste esquema, o construtor de compiladores fornece trechos de cdigo que so
executados no momento da anlise sinttica. Cada trecho, ou ao, est ligado
diretamente a uma produo na gramtica. Toda vez que o parser reconhece que
est em um local em particular na gramtica, a ao correspondente chamada para
realizar sua tarefa. Para implementar isto em um parser de descida recursiva, o construtor de compiladores simplesmente acrescenta o cdigo apropriado nas rotinas
de anlise sinttica, e, com isto, tem controle completo sobre quando as aes so
executadas. Em um parser bottom-up, shift-reduce, as aes so realizadas toda
vez que o parser realiza uma ao reduce, processo mais restritivo, mas que ainda
assim funciona.

4.4 Traduo ad hoc dirigida pela sintaxe 173

Para tornar isto concreto, considere a reformulao do exemplo de nmero binrio


com sinal em um framework de traduo ad hoc dirigida pela sintaxe. A Figura4.11
mostra este framework. Cada smbolo da gramtica tem um nico valor associado
a ele, indicado por val nos trechos de cdigo. O trecho de cdigo para cada regra
define o valor associado ao smbolo do lado esquerdo da regra. A regra 1 simplesmente multiplica o valor de Sign pelo de List. As regras 2 e 3 definem o valor de Sign
de modo apropriado, assim como as regras 6 e 7 definem o valor para cada ocorrncia
de Bit. A regra 4 simplesmente copia o valor de Bit para List. O trabalho real ocorre
na regra 5, que multiplica o valor acumulado dos bits iniciais (em List.val) por dois
e depois soma ao prximo bit.
At aqui, isto parece ser muito semelhante a uma gramtica de atributo. Porm, h duas
simplificaes-chave. Os valores fluem em apenas um sentido, das folhas para a raiz,
e, por isso, somente permitido um nico valor por smbolo da gramtica. Mesmo
assim, o esquema na Figura4.11 calcula corretamente o valor do nmero binrio com
sinal. Ele deixa esse valor na raiz da rvore, assim como a gramtica de atributo para
nmeros binrios com sinal.
Essas duas simplificaes tornam possvel um mtodo de avaliao que funciona bem
com um parser bottom-up, como os parsers LR(1) descritos no Captulo3. Como cada
trecho de cdigo est associado ao lado direito de uma produo especfica, o parser
pode chamar a ao toda vez que efetuar uma reduo usando essa produo. Isto exige
pequenas modificaes na ao reduce no esqueleto do parser LR(1), apresentado na
Figura3.15.

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.

4.4.1 Implementao da traduo ad hoc dirigida


pela sintaxe
Para fazer a traduo ad hoc dirigida pela sintaxe funcionar, o parser precisa incluir
mecanismos para passar valores de suas definies em uma ao para seus usos em
outra, fornecer nomeao conveniente e coerente e permitir aes que sejam executadas

174 CAPTULO 4 Anlise sensvel ao contexto

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.

Comunicao entre aes


Para passar valores entre aes, o parser deve ter uma metodologia para alocar espao de armazenamento de valores produzidos pelas diversas aes. Este mecanismo
deve tornar possvel que uma ao que utiliza um valor o encontre. Uma gramtica
de atributo associa os valores (atributos) aos ns na rvore sinttica; unir o armazenamento do atributo ao armazenamento dos ns da rvore torna possvel encontrar
valores de atributo de um modo sistemtico. Na traduo ad hoc dirigida pela sintaxe,
o parser pode no construir a rvore sinttica. Ao invs disso, o parser pode integrar o
armazenamento para valores ao seu prprio mecanismo de rastreamento do estado da
anlise sua pilha interna.
Lembre-se de que o esqueleto de parser LR(1) armazenou dois valores na pilha para cada
smbolo da gramtica: o smbolo e um estado correspondente. Quando ele reconhece um
handle, como uma sequncia List Bit para corresponder com o lado direito da regra 5, o
primeiro par na pilha representa o Bit, abaixo, est o par representando a List. Podemos
substituir esses pares smbolo, estado por triplas valor, smbolo, estado. Isto fornece
um atributo de valor nico por smbolo da gramtica exatamente do que o esquema
simplificado precisa. Para gerenciar a pilha, o parser empilha e desempilha mais valores.
Em uma reduo usando A b, retira 3|b| itens da pilha, ao invs de 2|b|, e empilha
o valor junto com o smbolo e o estado.
Esta tcnica armazena os valores em locais facilmente calculados em relao ao
topo da pilha. Cada reduo coloca seu resultado na pilha como parte da tripla que
representa o lado esquerdo. A ao l os valores para o lado direito a partir de suas
posies relativas na pilha; o i-simo smbolo no lado direito tem seu valor na i-sima
tripla a partir do topo da pilha. Os valores so restritos a um tamanho fixo; na prtica,

4.4 Traduo ad hoc dirigida pela sintaxe 175

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

Number Sign List


Sign
Sign
List
List0
Bit
Bit

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.

Aes em outros pontos da anlise


Os construtores de compilador talvez tambm tenham de realizar uma ao no meio
de uma produo ou em uma ao shift. Para que consigam, os construtores de compilador podem transformar a gramtica de modo que seja realizada uma reduo em
cada ponto onde uma ao necessria. Para reduzir no meio de uma produo, eles
podem quebr-la em duas partes em torno do ponto onde a ao deve ser executada.
Uma produo de nvel mais alto, que sequencia a primeira parte e depois a segunda,
acrescentada. Quando a primeira parte reduzida, o parser chama a ao. Para

176 CAPTULO 4 Anlise sensvel ao contexto

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.

Rastreamento de loads, reviso


Considere, novamente, o problema de rastrear operaes load que surgiram como
parte da estimativa de custos de execuo. A maior parte da complexidade na gramtica
de atributos para este problema surgiu da necessidade de passar informaes pela
rvore. Em um esquema de traduo ad hoc dirigida pela sintaxe que usa tabela de
smbolos, o problema fcil de ser tratado. O construtor de compiladores pode reservar
um campo na tabela para manter um booleano que indica se esse identificador j foi ou
no carregado por um load. O campo inicialmente definido como false. O cdigo
crtico associado produo Fator nome. Se a entrada na tabela de smbolos de
nome indicar que ele no foi carregado por um load, ento o custo atualizado e o
campo definido como true.
A Figura4.12 ilustra este caso, juntamente com todas as outras aes. Como as aes
podem conter um cdigo qualquer, o compilador pode acumular cost em uma nica
varivel, ao invs de criar um atributo cost em cada n da rvore sinttica. Este esquema exige menos aes do que as regras de atribuio para o modelo de execuo
mais simples, embora fornea a preciso do modelo mais complexo.

4.4 Traduo ad hoc dirigida pela sintaxe 177

FIGURA 4.12 Rastreando loads com a traduo ad hoc dirigida pela sintaxe.

Observe que vrias produes no possuem aes, e as restantes so simples, exceto


para aquela tomada sobre uma reduo de nome. Toda complicao introduzida pelo
rastreamento de loads encontra-se nesta nica ao. Compare isto com a verso da
gramtica de atributo, onde a tarefa de passar os conjuntos Before e After chegou
a dominar a especificao. A verso ad hoc mais limpa e mais simples, em parte
porque o problema se encaixa bem na ordem de avaliao ditada pelas aes de reduo
(reduce) em um parser shift-reduce. Naturalmente, o construtor de compiladores precisa
implementar a tabela de smbolos ou import-la de alguma biblioteca de implementaes
de estruturas de dados.
Claramente, algumas dessas estratgias tambm poderiam ser aplicadas em um framework de gramtica de atributo. Porm, elas violam a natureza funcional da gramtica
de atributo. Elas foram partes crticas do trabalho, do framework de gramtica de
atributo para a configurao ad hoc.
O esquema na Figura4.12 ignora uma questo crtica: inicializar cost. A gramtica,
conforme est escrita, no contm uma produo que possa apropriadamente inicializar
cost como zero. A soluo, j descrita, modificar a gramtica de um modo que crie

178 CAPTULO 4 Anlise sensvel ao contexto

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.

Inferncia de tipos para expresses, reviso


O problema da inferncia de tipos para expresses encaixa-se bem ao framework de
gramtica de atributo, desde que assumamos que os ns folha j contenham informaes de tipo. A simplicidade da soluo mostrada na Figura4.7 deriva de dois fatos
principais. Primeiro, como os tipos de expresso so definidos recursivamente na rvore
de expresso, o fluxo natural de informaes corre de baixo para cima, das folhas at a
raiz, fazendo que a soluo se volte para uma gramtica S-atribuda. Segundo, os tipos
de expresso so definidos em termos da sintaxe da linguagem fonte. Isto se encaixa
bem ao framework da gramtica de atributo, que implicitamente exige a presena de
uma rvore sinttica. Todas as informaes de tipo podem ser ligadas a ocorrncias
de smbolos da gramtica, que correspondem exatamente aos ns na rvore sinttica.
Podemos reformular este problema em um framework ad hoc, como mostra a Figura4.13.
Ele usa as funes de inferncia de tipo introduzidas com a Figura4.7. O framework
resultante parece ser semelhante gramtica de atributo para a mesma finalidade da
Figura4.7. O framework ad hoc no oferece uma vantagem real para este problema.

Criao de uma rvore sinttica abstrata


Os front ends de compiladores precisam criar uma representao intermediria do
programa para usar na parte do meio do compilador e no seu back end. As rvores
sintticas abstratas so uma forma comum de IR estruturada em rvore. A tarefa de
criar uma AST assenta-se bem a um esquema de traduo ad hoc dirigida pela sintaxe.
Suponha que o compilador tenha uma srie de rotinas chamadas MakeNodei, para
0i3. A rotina usa, como primeiro argumento, uma constante que identifica exclusivamente o smbolo da gramtica que o novo n representar. Os i argumentos restantes

FIGURA 4.13Framework ad hoc para inferir tipos de expresso.

4.4 Traduo ad hoc dirigida pela sintaxe 179

so os ns que encabeam cada uma das i subrvores. Assim, MakeNode0 (number)


constri um n folha e o marca como representando um num. De modo semelhante,

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.

As rotinas MakeNode podem implementar a


rvore de qualquer maneira que seja apropriada. Por
exemplo, podem mapear a estrutura para uma rvore
binria, conforme discutido na Seo B.3.1.

180 CAPTULO 4 Anlise sensvel ao contexto

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.

Gerao de ILOC para expresses


Como exemplo final de manipulao de expresses, considere um framework ad hoc que
gera ILOC ao invs de uma AST. Faremos vrias suposies para simplificar. O exemplo
limita sua ateno a inteiros o tratamento de outros tipos aumenta a complexidade,
mas no muito a percepo , e tambm considera que todos os valores podem ser
mantidos em registradores; tanto, que os valores cabem nos registradores, como que a
implementao ILOC fornece mais registradores do que a computao usar.
A gerao de cdigo exige que o compilador rastreie muitos pequenos detalhes. Para
remover o mximo desses detalhes (e adiar algumas questes mais profundas para os
captulos seguintes), o framework de exemplo utiliza quatro rotinas de suporte.
1. Address utiliza um nome de varivel como seu argumento. Ela retorna o nmero
de um registrador que contm o valor especificado por nome. Se for preciso,
gera cdigo para carregar este valor.
2. Emit trata dos detalhes da criao de uma representao concreta para as diversas
operaes ILOC, e pode format-las e imprimi-las em um arquivo. Como
alternativa, pode ainda criar uma representao interna, para uso posterior.
3. NextRegister retorna um novo nmero de registrador. Uma implementao simples poderia incrementar um contador global.
4. Value utiliza um nmero como seu argumento e retorna um nmero de registrador. Garante que o registrador contm o nmero passado como seu argumento,
e, se for preciso, gera cdigo para mover esse nmero para o registrador.
A Figura4.15 mostra o framework dirigido pela sintaxe para este problema. As aes
se comunicam passando os nomes de registradores na pilha de anlise. As aes passam
esses nomes para Emit conforme a necessidade, a fim de criar as operaes que implementam a expresso de entrada.

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

4.4 Traduo ad hoc dirigida pela sintaxe 181

FIGURA 4.15 Emisso de ILOC para expresses.

um local que possa ser acessado rapidamente historicamente, um registrador de


hardware. O especificador extern diz ao compilador que as declaraes com o
mesmo nome em diferentes unidades de compilao devem ser ligadas como um
nico objeto.
O compilador precisa garantir que cada nome declarado tenha no mximo um atributo
de classe de armazenamento. A gramtica coloca os especificadores antes de uma lista
de um ou mais nomes. O compilador pode registrar os especificadores enquanto os
processa e aplic-los aos nomes quando os encontrar mais tarde. A gramtica admite
um nmero qualquer de palavras-chave de ClasseArmazenamento e EspecificadorTipo;
o padro limita as formas como as palavras-chave reais podem ser combinadas. Por
exemplo, permite apenas uma ClasseArmazenamento por declarao. O compilador
precisa impor esta restrio atravs da verificao sensvel ao contexto. Restries
semelhantes aplicam-se aos EspecificadoresTipo. Por exemplo, short vlido com
int, mas no com float.

Embora essas restries possam ser codificadas na


gramtica, os escritores padro resolveram deixar isso
para a elaborao semntica verificar, ao invs de complicar uma gramtica j grande.

182 CAPTULO 4 Anlise sensvel ao contexto

FIGURA 4.16 Subconjunto da sintaxe de declarao da linguagem C.

4.4 Traduo ad hoc dirigida pela sintaxe 183

E AS GRAMTICAS SENSVEIS AO CONTEXTO?


Dada a progresso de ideias dos captulos anteriores, pode ser natural considerar o uso
de linguagens sensveis ao contexto para realizar verificaes sensveis ao contexto,
como a inferncia de tipos. Afinal, usamos linguagens regulares para realizar anlise
lxica e linguagens livres de contexto para a anlise sinttica. Uma progresso natural
poderia sugerir o estudo de linguagens sensveis ao contexto e suas gramticas. As
gramticas sensveis ao contexto podem expressar uma famlia maior de linguagens
do que as gramticas livres de contexto.
Porm, gramticas sensveis ao contexto no so a resposta certa por dois motivos
distintos. Primeiro, o problema de analisar uma gramtica sensvel ao contexto
P-Espeo completo. Assim, um compilador que usasse tal tcnica poderia executar
muito lentamente. Segundo, muitas das questes importantes so difceis, se no
impossveis de codificar em uma gramtica sensvel ao contexto. Por exemplo,
considere a questo da declarao antes do uso. Escrever esta regra em uma
gramtica sensvel ao contexto exigiria que a gramtica codificasse cada combinao
distinta de variveis declaradas. Com um espao de nomes suficientemente pequeno
(por exemplo, Dartmouth BASIC limitava o programador a nomes com nica letra, com
um nico dgito opcional) isto pode ser administrvel; em uma linguagem moderna,
com um grande espao de nomes, o conjunto de nomes muito grande para ser
codificado em uma gramtica sensvel ao contexto.

Para processar declaraes, o compilador precisa coletar os atributos a partir


dos qualificadores, acrescentar quaisquer atributos de indireo, dimenso ou
inicializao, e entrar com a varivel na tabela. O construtor de compiladores
pode preparar uma estrutura de propriedades cujos campos correspondem s
propriedades de uma entrada na tabela de smbolos. Ao final de uma Declarao,
ele pode inicializar os valores de cada campo na estrutura. medida que reduz
as diversas produes na sintaxe de declarao, ele pode ajustar os valores na estrutura de modo correspondente.
j

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

pode reiniciar os campos de propriedades que se relacionam ao nome especfico,


incluindo aqueles definidos pelas produes Ponteiro, Inicializador e DeclaradorDireto.

184 CAPTULO 4 Anlise sensvel ao contexto

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.

Calculadora de quatro funes

Dica: lembre-se de que uma gramtica


de atributos no especifica a ordem da
avaliao.

4.5 TPICOS AVANADOS


Este captulo vem apresentando as noes bsicas da teoria de tipos e as utilizou como
um exemplo motivador para os frameworks de gramtica de atributo e para a traduo
ad hoc dirigida pela sintaxe. Um tratamento mais profundo da teoria de tipos e suas
aplicaes poderia facilmente preencher um volume inteiro.
Esta primeira subseo estabelece algumas questes de projeto de linguagem
que afetam o modo como um compilador deve realizar a inferncia de tipos e
a verificao de tipos. A segunda examina um problema que surge na prtica:
rearranjar uma computao durante o processo de criao da representao intermediria para ela.

4.5 Tpicos avanados 185

4.5.1 Problemas mais difceis na inferncia de tipos


Linguagens fortemente tipadas verificadas estaticamente, podem ajudar o programador
a produzir programas vlidos detectando grandes classes de programas errneos. Os
mesmos recursos que expem erros podem melhorar a capacidade do compilador
de gerar cdigo eficiente para um programa, eliminando verificaes em tempo de
execuo e expondo onde o compilador pode construir um cdigo especial para alguma
construo, a fim de eliminar casos que no possam ocorrer em runtime. Estes fatos
explicam, em parte, o papel cada vez maior dos sistemas de tipos nas linguagens de
programao modernas.
Porm, nossos exemplos fizeram suposies que no se aplicam a todas as linguagens de
programao. Por exemplo, consideramos que variveis e procedimentos so declarados
o programador escreve uma especificao concisa e obrigatria para cada nome.
Variar essas suposies pode mudar radicalmente a natureza do problema de verificao
de tipos e das estratgias que o compilador pode usar para implementar a linguagem.
Algumas linguagens de programao, ou omitem declaraes ou as tratam como
informaes opcionais. Programas em Scheme no tm declaraes para variveis.
Programas em Smalltalk declaram classes, mas a classe de um objeto determinada
somente quando o programa constri o objeto. As linguagens que tm suporte para
compilao separada compilar procedimentos de forma independente e combin-los
em tempo de ligao para formar um programa podem no exigir declaraes para
procedimentos compilados desta forma.
Na ausncia de declaraes, a verificao de tipos mais difcil, pois o compilador
precisa contar com dicas contextuais para determinar o tipo apropriado para cada nome.
Por exemplo, se i usado como um ndice para algum array a, isso pode restringir i a
ter um tipo numrico. A linguagem poderia permitir somente subscritos inteiros; alternativamente, poderia permitir qualquer tipo que pudesse ser convertido para um inteiro.
Regras de tipos so especificadas pela definio da linguagem. Os detalhes especficos
dessas regras determinam quo difcil inferir um tipo para cada varivel, o que, por
sua vez, tem um efeito direto sobre as estratgias que um compilador pode usar para
implementar a linguagem.

Usos consistentes de tipo e tipos de funo constante


Considere uma linguagem livre de declarao que exige o uso consistente de variveis
e funes. Neste caso, o compilador pode atribuir a cada nome um tipo geral e estreit-lo examinando cada uso do nome no contexto. Por exemplo, uma instruo como
a b3.14159 fornece evidncias de que a e b so nmeros, e que a deve ter um
tipo que lhe permita armazenar um nmero decimal. Se b tambm aparece nos contextos
onde um inteiro esperado, como em uma referncia de array c(b), ento o compilador
deve escolher entre um nmero no inteiro (para b3.14159) e um inteiro {para
c(b)}. Qualquer que seja a escolha, ele precisar de uma converso para um dos usos.
Se as funes tiverem tipos de retorno que sejam conhecidos e constantes ou seja,
uma funo fee sempre retorna o mesmo tipo , ento o compilador pode resolver
o problema de inferncia de tipos com um algoritmo iterativo de ponto fixo operando
sobre uma grade de tipos.

Usos consistentes de tipo e tipos de funo desconhecidos


Se o tipo de uma funo variar com os argumentos da funo, ento o problema de
inferncia de tipo torna-se mais complexo. Esta situao surge na linguagem Scheme,

186 CAPTULO 4 Anlise sensvel ao contexto

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

Map tambm pode tratar de funes com mltiplos


argumentos. Para faz-lo, ele toma vrias listas
passadas como argumento e as trata como listas de
argumentos, em ordem.

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.

Mudanas dinmicas no tipo


Se o tipo de uma varivel puder mudar durante a execuo, outras estratgias so
exigidas para descobrir onde ocorrem as mudanas de tipo e inferir os tipos apropriados. Em princpio, um compilador pode renomear as variveis de modo que
cada local de definio corresponda a um nome exclusivo. Ele pode, ento, inferir
os tipos para esses nomes com base no contexto fornecido pela operao que define
cada nome.
Para inferir os tipos com sucesso, tal sistema precisa lidar com pontos no cdigo onde
definies distintas devem se mesclar em razo da convergncia de diferentes caminhos
do fluxo de controle, como ocorre com as funes no formato de atribuio nica
esttica (ver Sees 5.4.2 e 9.3). Se a linguagem incluir o polimorfismo paramtrico,
o mecanismo de inferncia de tipos precisa tratar disso tambm.
O mtodo clssico para implementar uma linguagem com tipos que mudam dinamicamente recorrer interpretao. Lisp, Scheme, Smalltalk e APL possuem problemas
semelhantes. A prtica de implementao padro para essas linguagens envolve a
interpretao dos operadores, marcando os dados com seus tipos e verificando erros
de tipo em tempo de execuo.
Em APL, o programador pode facilmente escrever um programa no qual ab multiplica inteiros na primeira vez que executado e multiplica arrays multidimensionais
de nmeros de ponto flutuante na prxima vez. Isso levou a muitos trabalhos de
pesquisa sobre eliminao de verificao e mudana de verificao. Os melhores
sistemas APL evitaram a maior parte das verificaes que um interpretador simples
precisaria.

4.5.2 Mudana de associatividade


Como vimos na Seo 3.5.4, a associatividade pode fazer diferena na computao
numrica. De maneira semelhante, ela pode mudar o modo como as estruturas de
dados so criadas. Podemos usar aes dirigidas pela sintaxe para criar representaes
que reflitam uma associatividade diferente daquela que a gramtica naturalmente
produziria.

4.5 Tpicos avanados 187

Produo

Aes

Produo

Aes

List -> List elt


| elt

{$$ <- L ($1 . $2)}


{$$ <- $1}

List -> elt List


| elt

{$$ <- L ($1 . $2)}


{$$ <- $1}

FIGURA 4.17Recurso versus associatividade.

Em geral, as gramticas recursivas esquerda naturalmente produzem associatividade


esquerda, enquanto gramticas recursivas direita, naturalmente produzem associatividade
direita. Para ver isto, considere gramticas de lista recursivas esquerda e direita,
aumentadas com aes dirigidas pela sintaxe para criar listas, apresentadas no alto da
Figura4.17. As aes associadas a cada produo constroem uma representao de lista.
Suponha que L(x,y) seja um construtor de lista; que pode ser implementado como
MakeNode2 (cons,x,y). A parte inferior da figura mostra o resultado da aplicao
dos dois esquemas de traduo a uma entrada consistindo em cinco elts.
Ambas as rvores so, de vrias maneiras, equivalentes. Uma travessia em ordem de ambas
visita os ns folha na mesma ordem. Se acrescentarmos parnteses para refletir a estrutura
de rvore, a rvore recursiva esquerda ((((elt1,elt2),elt3),elt4),elt5),
enquanto a rvore recursiva direita (elt1,(elt2,(elt3,(elt4,elt5)))).
A ordenao produzida pela recurso esquerda corresponde ordenao clssica da esquerda para a direita para operadores algbricos; a ordenao produzida pela recurso
direita, corresponde noo de uma lista encontrada em Lisp e Scheme.
Por vezes, conveniente usar diferentes direes para recurso e associatividade. Para
criar a rvore recursiva direita a partir da gramtica recursiva esquerda, poderamos
usar um construtor que acrescenta elementos sucessivos ao final da lista. Uma implementao direta desta ideia teria que percorrer a lista a cada reduo, fazendo o
construtor tomar um tempo O(n2), onde n o comprimento da lista. Para evitar este
overhead, o compilador pode criar um n de cabealho de lista que contm ponteiros
para o primeiro e ltimo ns na lista, introduzindo assim um n extra na lista. Se o
sistema construir muitas listas curtas, o overhead pode ser um problema.
Uma soluo que achamos particularmente atraente usar um n de cabealho de lista
durante a construo e descart-lo aps a lista ter sido criada. Isto pode ser feito de
forma elegante, reescrevendo a gramtica para usar uma -produo.
Gramtica
List
Quux

Aes

List elt
List

{ $$ MakeListHeader ( ) }
{ $$ AddToEnd($1, $2) }
{ $$ RemoveListHeader($1) }

Uma reduo com a -produo cria o n de cabealho de lista temporrio; com um


parser shift-reduce essa reduo ocorre primeiro. A produo List List elt chama

188 CAPTULO 4 Anlise sensvel ao contexto

um construtor que conta com a presena do n de cabealho temporrio. Quando List


reduzida no lado direito de qualquer outra produo, a ao correspondente chama
uma funo que descarta o cabealho temporrio e retorna o primeiro elemento da lista.
Este mtodo permite que o parser reverta a associatividade ao custo de um pequeno
overhead constante em espao e em tempo. Isto exige uma reduo a mais por lista
para a -produo. A gramtica revisada admite uma lista vazia, enquanto a gramtica
original no. Para resolver este problema, RemoveListHeader pode verificar explicitamente o caso da lista vazia e relatar o erro.

4.6 RESUMO E PERSPECTIVA


Nos Captulos2 e3, vimos que grande parte do trabalho no front end de um compilador
pode ser automatizada. As expresses regulares funcionam bem para a anlise lxica, e
as gramticas livres de contexto, para a anlise sinttica. Neste captulo, examinamos
duas formas de realizar a anlise sensvel ao contexto: o formalismo da gramtica de
atributo e uma tcnica ad hoc. Para a anlise sensvel ao contexto, diferentemente das
anlises lxica e sinttica, o formalismo no substituiu o mtodo ad hoc.
A tcnica formal, usando gramticas de atributo, oferece a expectativa de escrever especificaes de alto nvel que produzem executveis razoavelmente eficientes. Embora
estas gramticas no sejam a soluo para todos os problemas na anlise sensvel ao
contexto, encontraram aplicao em diversos domnios, variando desde provadores
de teorema at anlise de programa. Para problemas em que o fluxo de atributos
principalmente local, elas funcionam bem. Os problemas que podem ser totalmente
formulados em termos de uma espcie de atributo, seja ele herdado ou sintetizado,
em geral produzem solues limpas, intuitivas, quando moldados como gramticas
de atributo. Quando o problema de direcionar o fluxo de atributos pela rvore com
regras de cpia chega a dominar a gramtica, provavelmente o momento de sair do
paradigma funcional dessas gramticas e introduzir um repositrio central para fatos.
A tcnica ad hoc, traduo dirigida pela sintaxe, integra trechos arbitrrios de cdigo
ao parser e permite que este sequencie as aes e passe valores entre elas, e tem sido
bastante adotada por causa da sua flexibilidade e sua incluso na maioria dos sistemas geradores de parser. Ela evita os problemas prticos que surgem do fluxo de
atributos no locais e da necessidade de gerenciar o armazenamento de atributos. Os
valores fluem em um sentido ao longo da representao interna de seu estado (valores
sintetizados para parsers bottom-up e herdados para parsers top-down). Esses esquemas
utilizam estruturas de dados globais para passar informaes no outro sentido e para
lidar com o fluxo de atributos no locais.
Na prtica, o construtor de compiladores normalmente tenta resolver vrios problemas
ao mesmo tempo, como a criao de uma representao intermediria, a inferncia de
tipos e a atribuio de locais de armazenamento. Isto costuma criar fluxos de atributos
significativos nos dois sentidos, levando o implementador para uma soluo ad hoc,
que usa algum repositrio central para fatos, como uma tabela de smbolos. Em geral,
a justificativa para resolver muitos problemas em um passo a eficincia em tempo de
compilao. Porm, a soluo dos problemas em passos separados normalmente pode
produzir solues que so mais fceis de entender, implementar e manter.
Este captulo introduziu as ideias por trs dos sistemas de tipo como um exemplo do
modelo de anlise sensvel ao contexto que um compilador precisa realizar. O estudo
da teoria de tipos e o projeto do sistema de tipos so uma atividade significativamente
erudita, com extensa literatura especializada. Aqui, apenas arranhou-se a superfcie da
inferncia e da verificao de tipos, mas um tratamento mais profundo dessas questes

4.6 Resumo e perspectiva 189

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.

190 CAPTULO 4 Anlise sensvel ao contexto

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) }

4. Use o paradigma de gramtica de atributo para escrever um interpretador para


a gramtica de expresso clssica. Suponha que cada nome tenha um atributo
value e um atributo lexeme. Suponha, tambm, que todos os atributos j estejam definidos e que todos os valores sempre tero o mesmo tipo.
5. Escreva uma gramtica para descrever todos os nmeros binrios que sejam
mltiplos de quatro. Acrescente regras de atribuio gramtica que anotaro
o smbolo inicial de uma rvore sinttica com um atributo value que contm o
valor decimal do nmero binrio.
6. Usando a gramtica definida no exerccio anterior, crie a rvore sinttica
para o nmero binrio 11100.
a. Mostre todos os atributos na rvore com seus valores correspondentes.
b. Desenhe o grafo de dependncia para a rvore sinttica e classifique todos
os atributos como sendo sintetizados ou herdados.
Seo 4.4
7. Um programa em Pascal pode declarar duas variveis inteiras a e b com a
sintaxe
var a, b: int
Esta declarao poderia ser descrita com a seguinte gramtica:

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:

4.6 Resumo e perspectiva 191

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.

Dica: o scanner retornou um nico tipo de token para


qualquer um dos valores de ClasseArmazenamento e
outro tipo de token para qualquer um dos EspecificadoresTipo.

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

194 CAPTULO 5 Representaes intermedirias

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.

O smbolo na ILOC no tem outra finalidade seno


melhorar a legibilidade.

5.1.1 Uma taxonomia de representaes intermedirias


Os compiladores tm usado muitos tipos de IR. Vamos organizar esta discusso ao longo
de trs eixos: organizao estrutural, nvel de abstrao e disciplina de nomeao. Em
geral, estes trs atributos so independentes; e a maioria das combinaes entre eles
j foi usada em algum compilador.

5.1 Introduo 195

Em linhas gerais, as IRs encaixam-se em trs categorias estruturais:


j

IRs grficas codificam o conhecimento do compilador em um grafo. Os algoritmos


so expressos em termos de objetos grficos: ns, arestas, listas ou rvores. As rvores sintticas usadas para representar derivaes no Captulo3 so uma IR grfica.
j IRs lineares so semelhantes ao pseudocdigo para alguma mquina abstrata.
Osalgoritmos percorrem sequncias de operaes lineares, simples. O cdigo
ILOC usado neste livro uma forma de IR linear.
j IRs hbridas combinam elementos das IRs grficas e lineares, em uma tentativa
de capturar seus pontos fortes e evitar os fracos. Uma representao hbrida
comum usa uma IR linear de baixo nvel para representar blocos de cdigo emlinha e um grafo para representar o fluxo de controle entre esses blocos.
A organizao estrutural de uma IR tem forte impacto sobre como o construtor de
compiladores pensa a respeito da anlise, otimizao e gerao de cdigo. Por exemplo,
IRs do tipo rvore levam naturalmente a passos estruturados como alguma forma de
percurso em rvore. De modo semelhante, IRs lineares levam naturalmente a passos
que percorrem as operaes em ordem.
O segundo eixo de nossa taxonomia de IR o nvel de abstrao pelo qual a IR representa operaes. A IR pode variar de uma representao quase fonte, em que um
nico n poderia representar um acesso a array ou uma chamada de procedimento,
at uma de baixo nvel, em que vrias operaes de IR precisam ser combinadas para
formar uma nica operao na mquina-alvo.
Para ilustrar as possibilidades, suponha que A[1...10, 1...10] seja um array de
elementos de quatro bytes armazenados em ordem de linha, e considere como o compilador
poderia representar a referncia de array A[i,j] em uma rvore de nvel fonte e em ILOC.

Na rvore de nvel fonte, o compilador pode facilmente reconhecer a computao como


uma referncia de array; o cdigo ILOC oculta este fato muito bem. Em um compilador
que tenta determinar quando duas referncias diferentes podem tocar no mesmo local
da memria, a rvore facilita encontrar e comparar referncias. Ao contrrio, o cdigo
ILOC torna essas tarefas difceis. A otimizao s torna a situao pior; no cdigoILOC, a otimizao poderia mover partes do clculo do endereo para outro lugar.
O n da rvore permanecer intacto sob otimizao.
Por outro lado, se o objetivo otimizar o cdigo da mquina-alvo gerado para o acesso
ao array, o cdigo ILOC permite que o compilador melhore detalhes que permanecem implcitos na rvore de nvel fonte. Para esta finalidade, uma IR de baixo nvel pode ser melhor.
Nem todas as IRs baseadas em rvore utilizam um nvel de abstrao prximo do cdigofonte. Por certo, rvores sintticas so implicitamente relacionadas ao cdigo-fonte,

196 CAPTULO 5 Representaes intermedirias

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

Rn Programming Environment montava uma rvore sinttica abstrata para


oFORTRAN. Os ns na rvore ocupavam 92 bytes cada. O parser montava uma
mdia de onze ns por linha-fonte, para um tamanho de pouco mais de 1.000
bytes por linha de cdigo-fonte.
j O compilador de pesquisa MSCP usava uma implementao completa da ILOC.
(ILOC, neste livro, um simples subconjunto.) As operaes ILOC ocupam de 23
a 25 bytes. O compilador gera uma mdia de aproximadamente 15 operaes ILOC
por linha de cdigo-fonte, ou cerca de 375 bytes. A otimizao reduz o tamanho
parapouco mais de trs operaes por linha de cdigo-fonte, ou menos de 100 bytes.
Finalmente, o construtor de compiladores deve considerar a expressividade da IR sua
capacidade de acomodar todos os fatos que o compilador precisa registrar. A IR para um
procedimento pode incluir o cdigo que o define, os resultados de anlise esttica, os
dados de perfil de execues anteriores e mapeamentos para permitir que o depurador
entenda o cdigo e seus dados. Todos estes requisitos devem ser expressos de modo
que torne claro seu relacionamento com pontos especficos na IR.

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.

5.2.1 rvores relacionadas sintaxe


As rvores sintticas mostradas no Captulo3 so grafos que representam a forma de
cdigo-fonte do programa. E so uma forma especfica de IRs tipo rvore. Na maioria
destas IRs, a estrutura da rvore corresponde sintaxe do cdigo-fonte.

5.2 IRs grficas 197

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.

Pequenas transformaes na gramtica, conforme descritas na Seo 3.6.1, podem eliminar


algumas das etapas na derivao e seus ns correspondentes da rvore sinttica. Uma
tcnica mais eficiente remover aqueles ns que no tm finalidade no restante do compilador, o que leva a uma verso simplificada da rvore, chamada rvore sinttica abstrata.
rvores sintticas so usadas principalmente em discusses de anlise sinttica, e
nos sistemas de gramtica de atributo, onde elas so a IR principal. Na maioria das
outras aplicaes em que uma rvore de nvel fonte necessria, os construtores de
compilador costumam usar uma das alternativas mais concisas, descritas no restante
desta subseo.

rvores sintticas abstratas


A rvore sinttica abstrata (AST Abstract Syntax Tree) retm a estrutura essencial da
rvore de derivao, mas elimina os ns irrelevantes. A precedncia e o significadoda
expresso permanecem, mas os ns irrelevantes desapareceram. Aqui est a AST
para a2+a2b:

rvore sinttica abstrata


AST uma contrao da rvore de derivao, que omite
a maioria dos ns para smbolos no terminais.

198 CAPTULO 5 Representaes intermedirias

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.

5.2 IRs grficas 199

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.

Grafos acclicos direcionados


Embora a AST seja mais concisa do que uma rvore sinttica, ela retm fielmente
a estrutura do cdigo-fonte original. Por exemplo, a AST para a2+a2b
contm duas cpias distintas da expresso a2. Um grafo acclico direcionado
(DAG Directed Acyclic Graph) uma contrao da AST que evita esta duplicao.
Em um DAG, os ns podem ter mltiplos pais, e subrvores idnticas so reutilizadas.
Tal compartilhamento torna o DAG mais compacto do que a AST correspondente.

Grafo acclico direcionado


DAG uma AST com compartilhamento. Subrvores
idnticas ocorrem apenas uma vez, com mltiplos pais.

Para expresses sem atribuio, expresses textualmente idnticas devem produzir


valores idnticos. O DAG para a2+a2b, mostrado ao lado, reflete este fato
compartilhando uma nica cpia de a2. Ele codifica uma dica explcita para avaliar a
expresso. Se o valor de a no puder mudar entre os dois usos de a, ento o compilador
deve gerar cdigo para avaliar a2 uma vez e usar o resultado duas vezes. Esta estratgia pode reduzir o custo da avaliao. Porm, o compilador precisa provar que o
valor de a no pode mudar. Se a expresso no contm atribuio nem chama outros
procedimentos, a prova fcil. Como uma atribuio ou uma chamada de procedimento
podem mudar o valor associado a um nome, o algoritmo de construo do DAG precisa
invalidar as subrvores medida que os valores de seus operandos mudarem.
DAGs so usados em sistemas reais por dois motivos. Se as restries de memria
limitarem o tamanho dos programas que o compilador pode tratar, seu uso pode ajudar,
reduzindo o requisito de memria. Outros sistemas utilizam DAGs para expor redundncias. Aqui, o benefcio est no melhor cdigo compilado. Esses sistemas mais novos
tendem a usar o DAG como uma IR derivativa montando o DAG, transformando a
IR definitiva para refletir as redundncias e descartando-o.

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.

200 CAPTULO 5 Representaes intermedirias

FIGURA 5.2 rvores sintticas abstratas com diferentes nveis de abstrao.

mudar, em parte porque o compilador precisaria traduzir fatos implcitos de formas


diferentes, especficas da ocorrncia. Por exemplo, para personalizar o cdigo gerado
para uma referncia de array, o compilador precisa reescrever as expresses IR relacionadas. Em um programa real, diferentes referncias de array so otimizadas de
diferentes maneiras, cada uma de acordo com o contexto ao redor. Para o compilador
ajustar essas referncias, ele precisa ser capaz de escrever as melhorias na IR.
Como ltimo ponto, observe que as representaes para as referncias de varivel na
rvore de baixo nvel refletem as diferentes interpretaes que ocorrem nos lados direito
e esquerdo da atribuio. No lado esquerdo, w avaliado como um endereo, enquanto
tanto a quanto b so avaliados como valores, em razo do operador .

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.

Grafo de fluxo de controle


Bloco bsico
Sequncia de tamanho mximo do cdigo sem desvios.
Comea com uma operao rotulada e termina com um
desvio, salto ou operao predicada.

A unidade de fluxo de controle mais simples em um programa o bloco bsico


sequncia de tamanho mximo de cdigo direto livre de ramificaes uma srie de
operaes que sempre executada junta, a menos que uma operao dispare uma exceo.
O controle sempre entra em um bloco bsico em sua primeira operao e sai na ltima.

Grafo de fluxo de controle


CFG tem um n para cada bloco bsico e uma aresta
para cada transferncia de controle possvel entre blocos.
Usamos o acrnimo CFG tanto para gramtica livre
decontexto quanto para grafo de fluxo decontrole. O
significado fica claro pelo contexto.

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,

5.2 IRs grficas 201

mas o compilador pode facilmente acrescentar um nf exclusivo, assim conectando a


ele cada uma das sadas reais.
O CFG oferece uma representao grfica dos possveis caminhos do fluxo de controle
de execuo, diferente das IRs orientadas pela sintaxe, como uma AST, em que as
arestas mostram a estrutura gramatical. Considere o seguinte CFG para um lao while:

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,

Blocos de nica instruo


Bloco de cdigo que corresponde a uma nica instruo
de nvel fonte.

202 CAPTULO 5 Representaes intermedirias

FIGURA 5.3 Um bloco bsico ILOC e seu grafo de dependncia.

Grafo de dependncia de dados


Grafo que modela o fluxo de valores desde suas
definies at seus usos em um fragmento de cdigo.

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.

FIGURA 5.4 Interao entre fluxo de controle e grafo de dependncia.

5.2 IRs grficas 203

Sem uma anlise sofisticada das expresses de subscrito, o compilador no consegue


diferenciar as referncias aos elementos individuais do array.
Esse grafo de dependncia mais complexo do que o do exemplo anterior. Os ns 5
e 6 dependem de si mesmos; usam valores que podem ter definido em uma iteraoanterior. O n 6, por exemplo, pode assumir o valor de i a partir de 2 (na iterao
inicial) ou de si mesmo (em qualquer iterao subsequente). Os ns 4 e 5 tambm
possuem duas fontes distintas para o valor de i: os ns 2 e 6.
Grafos de dependncia de dados so frequentemente utilizados como uma IR secundria
construda a partir da IR definitiva para uma tarefa especfica, usada e depois descartada. Eles
desempenham um papel central no escalonamento de instrues (Captulo12). E encontram
aplicao em diversas otimizaes particularmente transformaes que reordenam laos
para expor paralelismo e melhorar o comportamento da memria , que normalmente
exigem anlise sofisticada de subscritos de array para determinar mais precisamente os
padres de acesso aos arrays. Em aplicaes mais sofisticadas do grafo de dependncia de
dados, o compilador pode realizar uma anlise extensiva dos valores de subscrito de array
para determinar quando as referncias ao mesmo array podem se sobrepor.

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

Compilao separada. A prtica de compilar pequenos subconjuntos


de um programa de forma independente, limita a capacidade do compilador
de montar um grafo de chamada e realizar anlise interprocedimental
e otimizao. Alguns compiladores constroem grafos de chamada parciais
para todos os procedimentos em uma unidade de compilao e realizam anlise
e otimizao por este conjunto. Para analisar e otimizar o programa inteiro
em tal sistema, o programador precisa apresent-lo ao compilador por inteiro,
de uma s vez.
j Parmetros produzidos por procedimentos. Tanto os parmetros de entrada
comoos valores de retorno, complicam a construo do grafo de chamada,
introduzindo locais de chamada ambguos. Se fee usa um argumento produzido
por um procedimento e o chama, esse local tem o potencial de chamar
um procedimento diferente acada chamada de fee. O compilador precisa realizar
uma anlise interprocedimental para limitar o conjunto de arestas que tal chamada
induz no grafo de chamada.
j Programas orientados a objeto com herana normalmente criam chamadas
deprocedimento ambguas, que s podem ser resolvidas com informao de tipo
adicional. Em algumas linguagens, a anlise interprocedimental da hierarquia
de classes pode fornecer a informao necessria para retirar a ambiguidade
dessas chamadas. Em outras linguagens, essa informao no pode ser conhecida
antes da execuo. A resoluo de chamadas ambguas em tempo de execuo
impe um problema srio para a construo do grafo de chamada; e tambm cria
overheads significativos de runtime na execuo das chamadas ambguas.

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.

204 CAPTULO 5 Representaes intermedirias

A Seo 9.4 discute tcnicas prticas para a construo do grafo de chamada.


REVISO DA SEO
As IRs grficas apresentam uma viso abstrata do cdigo que est sendo compilado, e
diferem no significado imputado a cada n e cada aresta.
j
j

Em uma rvore sinttica, os ns representam elementos sintticos na gramtica


dalinguagem-fonte, enquanto as arestas ligam esses elementos a uma derivao.
Em uma rvore sinttica abstrata ou em um DAG, os ns representam itens concretos do programa na linguagem-fonte, e as arestas ligam esses ns de um modo
que indica os relacionamentos de fluxo de controle e o fluxo de dados.
Em um grafo de fluxo de controle, os ns representam blocos de cdigo,
e as arestas, transferncias de controle entre os blocos. A definio de um bloco
pode variar, de um nico comando at um bloco bsico.
Em um grafo de dependncia, os ns representam computaes, e as arestas,
ofluxo de valores desde as definies at os usos; desta forma, as arestas tambm
implicam uma ordem parcial nas computaes.
Em um grafo de chamada, os ns representam procedimentos individuais,
e as arestas, locais de chamada individuais. Cada local de chamada tem uma aresta
distinta para fornecer uma representao para o conhecimento especfico do local
de chamada, como vnculos de parmetros.

IRs grficas codificam relacionamentos que podem ser difceis de representar


emuma IR linear, e podem fornecer ao compilador um modo eficiente de se mover
entre pontos logicamente conectados no programa, como a definio de uma varivel
e seu uso, ou a origem de um desvio condicional e seu destino.

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

5.3 IRs lineares 205

programa. O fluxo de controle em uma IR linear normalmente modela a implementao


deste fluxo sobre a mquina-alvo. Assim, cdigos lineares normalmente incluem desvios
condicionais e saltos. O fluxo de controle demarca os blocos bsicos em uma IR linear;
os blocos terminam nos desvios, em saltos ou exatamente antes de operaes rotuladas.
Na ILOC usada no decorrer deste livro, inclumos um desvio ou salto ao final de
cada bloco. Nela, as operaes de desvio especificam um label tanto para o caminho
tomado como para o caminho no tomado. Isto elimina quaisquer caminhos de falha
(fall-through) ao final de um bloco. Juntas, essas estipulaes tornam mais fcil encontrar blocos bsicos e reorden-los.

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.

Muitos tipos de IRs lineares tm sido usadas nos compiladores.


j

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.

5.3.1 Cdigo de mquina de pilha


O cdigo de mquina de pilha, um formato de cdigo de um endereo, assume a presena de uma pilha de operandos. A maioria das operaes retira seus operandos da pilha
e coloca seus resultados de volta na pilha. Por exemplo, uma operao de subtrao
de inteiros removeria os dois elementos do topo da pilha e colocaria sua diferena na
pilha. A disciplina de pilha cria a necessidade para algumas operaes novas. IRs de
pilha normalmente incluem uma operao swap que troca os dois elementos do topo
da pilha. Vrios computadores baseados em pilha tm sido criados; esta IR parece ter
aparecido em resposta s demandas de compilao para essas mquinas. O cdigo de
mquina de pilha para a expresso a-2 x b aparece ao lado.

push 2
push b
multiply
push a
subtract

Cdigo de mquina de pilha

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.

206 CAPTULO 5 Representaes intermedirias

forma compacta do programa para distribuio e um esquema razoavelmente simples para transportar a linguagem para uma nova mquina-alvo (implementando o
interpretador).

5.3.2 Cdigo de trs endereos


t1 2
t2 b
t3 t1t2
t4 a
t5 t4 t3

Cdigo de trs endereos

Neste cdigo, a maioria das operaes tem a forma i j op k, com um operador


(op), dois operandos (j e k) e um resultado (i). Alguns operadores, como um load
imediato e um salto, precisaro de menos argumentos. s vezes, uma operao com
mais de trs endereos necessria. O cdigo de trs endereos para a - 2b aparece
ao lado. ILOC um exemplo deste cdigo.
O cdigo de trs endereos atraente por vrios motivos. Primeiro, razoavelmente
compacto. A maioria das operaes consiste em quatro itens: uma operao e trs
nomes. Tanto a operao quanto os nomes so retirados de conjuntos limitados. Asoperaes normalmente exigem 1 ou 2 bytes. Os nomes, em geral, so representadospor
inteiros ou ndices de tabela; de qualquer forma, 4 bytes usualmente so suficientes.
Segundo, nomes distintos para os operandos e o resultado do ao compilador liberdade
para controlar a reutilizao de nomes e valores; este tipo de cdigo no possui operaes destrutivas. Ele introduz um novo conjunto de nomes gerados pelo compilador
nomes que armazenam os resultados das diversas operaes. Um espao de nomes
cuidadosamente escolhido pode revelar novas oportunidades para melhorar o cdigo.
Finalmente, como muitos processadores modernos implementam operaes de trs
endereos, este cdigo modela bem suas propriedades.
Dentro destes cdigos, o conjunto de operadores especficos que so aceitos e seus
nveis de abstrao podem variar bastante. Normalmente, uma IR de trs endereos
ter principalmente operaes de baixo nvel, como saltos, desvios e operaes de
memria simples, junto com operaes mais complexas, que encapsulam fluxo decontrole, como max ou min. A representao direta dessas operaes complexas as torna
mais fceis de analisar e otimizar.
Por exemplo, mvcl (move characters long) toma um endereo de origem, outro de
destino e um contador de caracteres. Ele copia o nmero especfico de caracteres da
memria a partir do endereo de origem para a memria comeando no endereo de
destino. Algumas mquinas, como a IBM 370, implementam esta funcionalidade em
uma nica instruo (mvcl um cdigo de operao da 370). Em mquinas que no
implementam a operao em hardware, podem ser necessrias muitas operaes para
realizar tal cpia.
A incluso de mvcl ao cdigo de trs endereos permite que o compilador use uma
representao compacta para esta operao complexa, possibilitando que o compilador
analise, otimize e mova a operao sem se preocupar com seu funcionamento interno.
Se o hardware admitir este tipo de operao, ento a gerao de cdigo mapear a construo da IR diretamente para a operao do hardware. Se o hardware no a admitir,
ento o compilador pode traduzir mvcl para uma sequncia de operaes de IR de
nvel inferior ou para uma chamada de procedimento antes da otimizao final e da
gerao de cdigo.

5.3.3 Representando cdigos lineares


Muitas estruturas de dados tm sido usadas para implementar IRs lineares. As escolhas
que um construtor de compiladores faz afetam os custos das diversas operaes sobre
o cdigo IR. Como um compilador gasta a maior parte do seu tempo manipulando o
formato IR do cdigo, esses custos merecem alguma ateno. Embora esta discusso se

5.3 IRs lineares 207

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

FIGURA 5.5 Implementaes do cdigo de trs endereos para a 2b.

t1 2
t2 b
t3 t1t2
t4 a
t5 t4 t3

Cdigo de trs endereos

208 CAPTULO 5 Representaes intermedirias

parte do atraso, o escalonador de instrues poderia mover os loads de b e a para a


frente do load imediato de 2.
No esquema de array simples, mover o load de b para antes do load imediato exige
salvar os quatro campos da primeira operao, copiar os campos correspondentes
do segundo slot para o primeiro, e sobrescrever os campos no segundo slot com os
valores salvos para o load imediato. O array de ponteiros exige a mesma tcnica de
trs etapas, exceto que somente os valores de ponteiro precisam ser alterados. Assim,
o compilador salva o ponteiro para o load imediato, copia o ponteiro para o load de b
no primeiro slot do array e sobrescreve o segundo slot no array com o ponteiro salvo
para o load imediato. Para a lista encadeada, as operaes so semelhantes, exceto que
o compilador precisa salvar estado suficiente para permitir que ele atravesse a lista.
Agora, considere o que acontece no front endereo quando gera a rodada inicial da IR.
Com a forma de array simples e o array de ponteiros, o compilador precisa selecionar
um tamanho para o array ou seja, o nmero de qudruplas que ele espera em um
bloco. Ao gerar as qudruplas, preenche o array. Se o array for muito grande, desperdia espao. Se for muito pequeno, o compilador precisa realoc-lo para obter um
array maior, copiar o contedo do array muito pequeno para o novo array, maior, e
desalocar o array pequeno. A lista encadeada, porm, evita esses problemas. A expanso
da lista s exige alocar uma nova qudrupla e definir o ponteiro apropriado na lista.

REPRESENTAES INTERMEDIRIAS EM USO REAL


Na prtica, os compiladores usam uma variedade de IRs. Os compiladores FORTRAN
legendrios de ontem, como os FORTRAN H da IBM, usavam uma combinao
dequdruplas e grafos de fluxo de controle para representar o cdigo para otimizao.
Como o FORTRAN H foi escrito em FORTRAN, ele mantinha a IR em um array.
Por muito tempo, o GCC (GNU Compiler Collection) contou com uma IR de muito
baixo nvel, chamada linguagem de transferncia de registrador (RTL Register
Transfer Language). Em anos recentes, GCC passou para uma srie de IRs. Os parsers
inicialmente produzem uma rvore de nvel quase fonte; essas rvores podem ser
especficas da linguagem, mas so exigidas para implementar partes de uma interface
comum. Essa interface inclui uma facilidade para reduzir as rvores para a segunda
IR, GIMPLE. Conceitualmente, o GIMPLE consiste em uma estrutura independente
dalinguagem, tipo rvore, para construes de fluxo de controle, anotada com
cdigo de trs endereos para expresses e atribuies. Ela projetada, em parte,
para simplificar a anlise. Grande parte do novo otimizador do GCC usa GIMPLE;
porexemplo, o GCCmonta o formato de atribuio nica esttica em cima do GIMPLE.
Porfim, o GCC traduz GIMPLE para RTL, para a otimizao e gerao de cdigo final.
O compilador LLVM utiliza uma nica IR de baixo nvel; na verdade o nome
LLVM significa Low-Level Virtual Machine. A IR do LLVM um cdigo linear de trs
endereos. A IR totalmente tipada e possui suporte explcito para endereos dearray
e estrutura. Ela oferece suporte para dados e operaes de vetor ou SIMD (Single
Instruction, Multiple Data). Os valores escalares so mantidos em formato SSA por todo
o compilador. O ambiente LLVM utiliza front ends GCC, de modo que a IR do LLVM
produzida por um passo que realiza a traduo de GIMPLE para LLVM.
O compilador Open64, de fonte aberto para a arquitetura IA-64, usa umafamlia
decinco IRs relacionadas, chamadas WHIRL. A traduo inicial no parser produz
umWHIRL quase de nvel fonte. As fases subsequentes do compilador introduzem
mais detalhes ao programa WHIRL, reduzindo o nvel de abstrao para o cdigo
de mquina real, permitindo que o compilador use uma AST de nvel fonte
paratransformaes baseadas em dependncia no texto fonte e uma IR de baixo nvel
paraos estgios posteriores de otimizao e gerao de cdigo.

5.3 IRs lineares 209

Um compilador multipasso pode usar diferentes implementaes para representar a


IR em diferentes pontos no processo de compilao. No front end, onde o foco est na
gerao da IR, uma lista encadeada poderia tanto simplificar a implementao quanto
reduzir o custo geral. Em um escalonador de instrues, com foco na rearrumao
das operaes, qualquer uma das implementaes de array pode fazer mais sentido.
Observe que alguma informao est faltando da Figura5.5. Por exemplo, nenhum rtulo
aparece, pois estes so uma propriedade do bloco, ao invs de uma qudrupla individual
qualquer. O armazenamento de uma lista de rtulos com o bloco economiza espao em
cada qudrupla; e tambm torna explcita a propriedade de que os rtulos s ocorrem na
primeira operao em um bloco bsico. Com rtulos conectados a um bloco, o compilador
pode ignor-los ao reordenar operaes dentro do bloco, evitando mais uma complicao.

5.3.4 A criao de um grafo de fluxo de controle a partir


deum cdigo linear
Os compiladores normalmente precisam converter entre diferentes IRs; em geral,
diferentes estilos de IRs. Uma converso de rotina construir um CFG a partir de uma
IR linear como ILOC. Os recursos essenciais de um CFG so: identifica o incio e o
final de cada bloco bsico e conecta os blocos resultantes com arestas que descrevem as
possveis transferncias de controle entre os blocos. Normalmente, o compilador deve
construir um CFG a partir de uma IR simples, linear, que representa um procedimento.
Como primeiro passo, o compilador precisa encontrar o incio e o final de cada bloco
bsico na IR linear. Vamos chamar a operao inicial de um bloco de guia. Uma operao uma guia se for a primeira operao no procedimento, ou se tiver um rtulo que
seja, potencialmente, o destino de algum desvio. O compilador pode identificar guias
em uma nica passada pela IR, mostrada na Figura5.6a, que percorre as operaes
no programa, em ordem, encontra as instrues rotuladas e as registra como guias.
Se a IR linear tiver rtulos que no so usados como destinos de desvio, ento trat-los
como guias pode dividir blocos desnecessariamente. O algoritmo poderia rastrear quais
rtulos so destinos de salto. Porm, se o cdigo tiver quaisquer saltos ambguos, ento
precisa tratar todas as instrues rotuladas como guias de qualquer forma.

FIGURA 5.6 Criao de um grafo de fluxo de controle.

Salto ambguo
Desvio ou salto cujo destino no pode ser determinado
em tempo de compilao; normalmente, um salto
paraum endereo em um registrador.

210 CAPTULO 5 Representaes intermedirias

COMPLICAES NA CONSTRUO DO CFG


Algumas caractersticas da IR, da mquina de destino e da linguagem-fonte podem
complicar a construo do CFG.
Saltos ambguos podem forar o compilador a introduzir arestas que nunca so
viveisem tempo de execuo. O construtor de compiladores pode melhorar esta
situao incluindo recursos na IR que registram os destinos de salto em potencial.
AILOC inclui a pseudo-operao tbl para permitir que o compilador registre os destinos
em potencial de um salto ambguo. Sempre que o compilador gera um jump, deve
segui-lo com um conjunto de operaes tbl que registrem os possveis destinos
dedesvio. A construo do CFG pode usar essas dicas para evitar arestas falsas.
Se o compilador construir um CFG a partir do cdigo da mquina de destino,
osrecursos da arquitetura de destino podem complicar o processo. O algoritmo
naFigura5.6 considera que todas as guias, exceto a primeira, so rotuladas. Se a mquina
de destino tiver desvios de falha (fall-through), o algoritmo precisa ser estendido
parareconhecer instrues no rotuladas que recebem controle em um caminho
de falha. Os desvios relativos ao contador de programa causam um conjunto
deproblemas semelhante.
Slots de atraso de desvio (branch delay slots) introduzem vrios problemas. Uma
instruo rotulada que se situa em um destes slots um membro de dois blocos
distintos. O compilador pode resolver este problema por replicao criando cpias
novas (no rotuladas) das operaes nos slots de atraso. Slots de atraso tambm
complicam a descoberta do final de um bloco. O compilador precisa colocar
asoperaes localizadas em slots de atraso no bloco que precede o desvio ou salto.
Se um desvio ou salto puder ocorrer em um slot de atraso de desvio, o construtor do
CFG precisa andar para a frente a partir da guia para encontrar o desvio de
final de bloco o primeiro desvio que encontrar. Os desvios no slot de atraso de um
desviode final de bloco podem, por si ss, estar pendentes na entrada para o blocode
destino. Eles podem dividir o bloco de destino e forar a criao de novos blocos e
novas arestas. Este tipo de comportamento complica seriamente a construo do CFG.
Algumas linguagens permitem saltos para rtulos fora do procedimento atual.
Noprocedimento contendo o desvio, o destino pode ser modelado com um novo
n do CFG criado para esta finalidade. A complicao surge na outra ponta do desvio.
Ocompilador precisa saber que o rtulo de destino o destino de um desvio no local,
ou ento uma anlise subsequente poder produzir resultados enganosos. Por este
motivo, linguagens como Pascal ou Algol restringiram os gotos no locais para rtulos
nos escopos lxicos externos visveis. C exige o uso das funes setjmp e longjmp
para expor essas transferncias.

O segundo passo, mostrado na Figura5.6b, encontra cada operao de final de


bloco, porque considera que cada bloco termina com um desvio ou um salto, e que
os desvios especificam rtulos para ambos os resultados um rtulo de desvio
tomado e um de desvio no tomado. Isto simplifica o tratamento deblocos e permite que o back end do compilador escolha qual caminho ser o caso fall-through
de um desvio. (Por um momento, suponha que os desvios no tenham slots de
atraso.)
Para encontrar o final de cada bloco, o algoritmo percorre os blocos, em ordem de
aparecimento no array Leader. Ele caminha para a frente pela IR at encontrar a
guia do prximo bloco. A operao imediatamente antes desta guia termina o bloco
atual. O algoritmo registra o ndice desta operao em Last[i], de modo que o par
Leader[i],Last[i] descreve o bloco i, e acrescenta arestas ao CFG conforme
a necessidade.

5.4 Mapeamento de valores para nomes 211

Por diversos motivos, o CFG deve ter um nico n de entrada n0 e um nico n de


sada nf. O cdigo subjacente deve ter esta forma. Se no tiver, um ps-passo simples
pelo grafo pode criar n0 e nf.

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.

5.4 MAPEAMENTO DE VALORES PARA NOMES


A escolha de uma IR especfica e um nvel de abstrao ajuda a determinar quais
operaes o compilador pode manipular e otimizar. Por exemplo, uma AST de nvel
fonte facilita a localizao de todas as referncias a um array x. Ao mesmo tempo,
oculta os detalhes dos clculos de endereo exigidos para acessar um elemento dex.
Ao contrrio, uma IR de baixo nvel, linear, como a ILOC, expe os detalhes do
clculo de endereo, ao custo de obscurecer o fato de que uma referncia especfica
se relaciona a x.
De modo semelhante, a disciplina que o compilador usa para atribuir nomes internos
aos diversos valores calculados durante a execuo tem efeito sobre o cdigo que ele
pode gerar. Um esquema de nomeao pode expor oportunidades para otimizao
ou obscurec-las. O compilador precisa inventar nomes para muitos, se no todos,
resultados intermedirios que o programa produz quando executado. As escolhas que
faz com relao aos nomes determina, em grande parte, quais computaes podem ser
analisadas e otimizadas.

5.4.1 Nomeao de valores temporrios


O formato IR de um programa normalmente contm mais detalhes do que a verso
fonte. Alguns destes detalhes esto implcitos no cdigo-fonte; outros resultam deescolhas deliberadas na traduo. Para ver isto, considere o bloco de cdigo-fonte de
quatro linhas apresentado na Figura5.7a. Suponha que os nomes se refiram a valores
distintos.

212 CAPTULO 5 Representaes intermedirias

FIGURA 5.7 A nomeao leva a diferentes tradues.

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.

5.4 Mapeamento de valores para nomes 213

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.

5.4.2 Forma de atribuio nica esttica


Forma de atribuio nica esttica (SSA Static Single-Assignment) uma disciplina
de nomeao que muitos compiladores modernos utilizam para codificar informaes
sobre os fluxos de controle e de valores de dados no programa. Na forma SSA, nomes
correspondem exclusivamente a pontos de definio especficos no cdigo; cada nome
definido por uma operao, da a denominao atribuio nica esttica. Como
corolrio, cada uso de um nome como um argumento em alguma operao codifica
informaes sobre onde o valor foi originado; o nome textual refere-se a um ponto de
definio especfico. Para reconciliar esta disciplina de nomeao de atribuio nica
com os efeitos do fluxo de controle, a forma SSA insere operaes especiais, chamadas
funes , em pontos onde os caminhos do fluxo de controle se encontram.
Um programa est na forma SSA quando atende a duas restries: (1) cada definio
tem um nome distinto; e (2) cada uso refere-se a uma nica definio. Para transformar
um programa IR para a forma SSA, o compilador insere funes em pontos onde
diferentes caminhos do fluxo de controle se juntam e depois renomeia variveis para
manter a propriedade de atribuio nica
Para esclarecer o impacto dessas regras, considere o pequeno lao mostrado no lado esquerdo da Figura5.9. A coluna da direita mostra o mesmo cdigo na forma SSA. Os nomes
de varivel incluem subscritos para criar um nome distinto para cada definio. Funes
foram inseridas em pontos onde diversos valores distintos podem alcanar o incio de
um bloco. Finalmente, a construo while foi reescrita com dois testes distintos, para
refletir o fato de que o teste inicial se refere a x0, enquanto o teste de fim de loop a x2.
O comportamento da funo depende do contexto. Este define seu nome SSA de destino com o valor do seu argumento que corresponde aresta ao longo da qual o controle

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.

214 CAPTULO 5 Representaes intermedirias

FIGURA 5.9 Um pequeno lao em formato SSA.

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.

5.4 Mapeamento de valores para nomes 215

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.

216 CAPTULO 5 Representaes intermedirias

Para renomear os registradores virtuais da ILOC, o compilador pode processar


osblocos em profundidade. Para cada registrador virtual, ele mantm um contador.
Quando encontra uma definio de ri, incrementa o contador para ri, digamos
parak, e reescreve a definio com o nome rik. medida que o compilador percorre
o bloco, reescreve cada uso de ri como rik at que encontre outra definio de ri.
(Esta definio aumenta o contador para k+1.) Ao final de um bloco, o compilador
examina cada aresta de fluxo de controle e reescreve o parmetro da funo
apropriado para ri em cada bloco que possui mltiplos predecessores.
Aps a renomeao, o cdigo est em conformidade com as duas regras da forma
SSA. Cada definio cria um nome exclusivo. Cada uso refere-se a uma nica definio.
Existem diversos algoritmos melhores de construo SSA, que inserem menos
funes do que esta tcnica simples.

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.

5.4.3 Modelos de memria


Assim como o mecanismo para nomear valores temporrios afeta as informaes que
podem ser representadas em uma verso IR de um programa, o mesmo acontece na escolha, pelo compilador, de um local de armazenamento para cada valor. O compilador
precisa determinar, para cada valor calculado no cdigo, onde este residir. Para o
cdigo ser executado, precisa atribuir um local especfico, como o registrador r13ou
16 bytes a partir do rtulo L0089. Porm, antes dos estgios finais da gerao de cdigo,
ele pode usar endereos simblicos que codificam um nvel na hierarquia de memria,
por exemplo, registradores ou memria, mas no um local especfico dentro desse nvel.
Considere os exemplos de ILOC usados no decorrer deste livro. Um endereo de
memria simblico indicado prefixando-o com o caractere @. Assim, @x o deslocamento de x a partir do incio da rea de armazenamento que o contm. Como
rarp mantm o ponteiro de registro de ativao, uma operao que use @x e rarp para
calcular um endereo depende, implicitamente, da deciso de armazenar a varivel x
na memria reservada para o registro de ativao do procedimento atual.
Em geral, os compiladores funcionam a partir de um dos dois modelos de memria.
1. Registrador para registrador. Sob este modelo, o compilador mantm valores
nos registradores agressivamente, ignorando quaisquer limitaes impostas pelo
tamanho do conjunto de registradores fsicos da mquina. Qualquer valor que
possa legalmente ser mantido em um registrador para a maior parte do seu tempo
de vida nele mantido. Os valores so armazenados na memria somente quando
a semntica do programa assim exige por exemplo, em uma chamada deprocedimento, qualquer varivel local cujo endereo passado como umparmetro

5.4 Mapeamento de valores para nomes 217

ao procedimento chamado precisa ser armazenada de volta na memria. Um


valor que no pode ser mantido em um registrador pela maior parte do seu tempo
devida armazenado na memria. O compilador gera cdigo para armazenar seu
valor toda vez que ele calculado e para carregar seu valor a cada uso.
2. Memria para memria. Sob este modelo, o compilador assume que
todososvalores so mantidos em locais da memria, e se movem da memria
paraumregistrador imediatamente antes de serem usados. Os valores se movem de um
registrador para a memria imediatamente aps serem definidos. O nmero
deregistradores nomeados na verso IR do cdigo pode ser pequeno em comparao com o modelo registrador-para-registrador. Neste, o projetista poder achar
vantajoso incluir operaes de memria-para-memria, como umadd dememria para memria, na IR.
A escolha do modelo de memria em geral ortogonal escolha da IR. O construtor de compiladores pode criar uma AST de memria-para-memria ou uma
verso de memria-para-memria da ILOC to facilmente quanto as verses de
registrador-para-registrador de qualquer uma dessas IRs. (Os cdigos de mquina
depilha e para uma mquina de acumulador poderiam ser excees, porque contm
seus prprios modelos de memria exclusivos.)

A HIERARQUIA DE OPERAES DE MEMRIA EM ILOC 9X


A ILOC usada neste livro abstrada de uma IR chamada ILOC 9X, usada em um projeto
de pesquisa sobre compiladores na Rice University. ILOC 9X inclui uma hierarquia
deoperaes de memria que o compilador usa para codificar o conhecimento sobre
valores. Na parte mais baixa da hierarquia, o compilador tem pouco ou nenhum
conhecimento sobre o valor; no topo da hierarquia, ele conhece o valor real. Essas
operaes so as seguintes:
Operao

Significado

Load imediato

Carrega um valor constante conhecido em um


registrador.
Carrega um valor que no muda durante a
execuo. O compilador no conhece o valor,
mas pode provar que ele no definido por uma
operao do programa.
Operam sobre um valor escalar, no um elemento
de array, um elemento de estrutura ou um valor
baseado em ponteiro.
Operam sobre um valor que pode ser um elemento
de array, um elemento de estrutura ou um valor
baseado em ponteiro. Este o caso geral da operao.

Load no varivel

Load & store escalares

Load & store gerais

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.

218 CAPTULO 5 Representaes intermedirias

A escolha do modelo de memria tem impacto sobre o restante do compilador. Com


um modelo registrador-para-registrador, o compilador normalmente usa mais registradores do que a mquina de destino oferece. Assim, o alocador de registradores
precisa mapear o conjunto de registradores virtuais usados no programa IR para os
registradores fsicos fornecidos pela mquina-alvo. Isto normalmente exige a insero
de operaes load, store e copy extras, tornando o cdigo mais lento e maior. Porm,
com um modelo memria para memria, em geral a verso IR do cdigo usa menos
registradores do que um processador moderno oferece. Aqui, o alocador de registradores procura valores baseados em memria que possa manter em registradores por
perodos de tempo maiores. Nesse modelo, o alocador torna o cdigo mais rpido e
menor, removendo loads e stores.
Compiladores para mquinas RISC tendem a usar o modelo registrador-para-registrador por dois motivos. Primeiro, este modelo reflete mais de perto os conjuntos
deinstrues das arquiteturas RISC. Estas mquinas no tm um complemento total de
operaes memria-para-memria; ao invs disso, consideram implicitamente que os
valores podem ser mantidos em registradores. Segundo, este modelo permite que o
compilador codifique diretamente na IR alguns dos fatos sutis que deriva. O fato de
um valor ser mantido em um registrador significa que o compilador, em algum ponto
anterior, teve uma prova de que mant-lo assim seguro. A menos que ele codifique
este fato na IR, o compilador precisar prov-lo, mais e mais vezes.
Entrando em mais detalhes, se o compilador pode provar que somente um nome
fornece acesso a um valor, pode manter este valor em um registrador. Se vrios
nomes puderem existir, o compilador precisa se comportar de modo conservador
e manter o valor na memria. Por exemplo, uma varivel local x pode ser mantida
em um registrador, a menos que possa ser referenciada em outro escopo. Em uma
linguagem que admite escopos aninhados, como Pascal ou Ada, esta refernciapode
ocorrer em um procedimento aninhado. Em C, isto pode ocorrer se o programa
apanhar o endereo de x, &x e acessar o valor por meio deste endereo. Em Algol
ou PL/I, o programa pode passar x como um parmetro de chamada por referncia
para outro procedimento.

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.

5.5 Tabelas de smbolos 219

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?

5.5 TABELAS DE SMBOLOS


Como parte da traduo, um compilador obtm informaes sobre as diversas entidades
manipuladas pelo programa que est sendo traduzido. Ele deve descobrir e armazenar
muitos tipos distintos de informao, e encontra uma grande variedade de nomes
variveis, constantes definidas, procedimentos, funes, rtulos, estruturas e arquivos.
Conforme discutimos na seo anterior, o compilador tambm gera muitos nomes. Para
uma varivel, precisa de um tipo de dados, sua classe de armazenamento, o nome e
nvel lxico de seu procedimento de declarao e um endereo de base e deslocamento
(offset) na memria. Para um array, tambm precisa do nmero de dimenses e os
limites superior e inferior para cada dimenso. Para registros ou estruturas, precisa de
uma lista dos campos, juntamente com as informaes relevantes para cada campo.
Para funes e procedimentos, precisa do nmero de parmetros e seus tipos, alm dos
tipos de quaisquer valores retornados; uma traduo mais sofisticada poderia registrar
informaes sobre quais variveis um procedimento pode referenciar ou modificar.
O compilador precisa, ou registrar essa informao na IR, ou recalcul-la por demanda. Por questo de eficincia, a maioria dos compiladores registra fatos ao invs
de recalcul-los. Esses fatos podem ser registrados diretamente na IR. Por exemplo,
o compilador que constri uma AST poderia registrar informaes sobre variveis
como anotaes (ou atributos) do n que representa a declarao de cada varivel. A
vantagem desta tcnica que ela usa uma nica representao para o cdigo sendo
compilado, o que fornece um mtodo de acesso uniforme e uma nica implementao.
Sua desvantagem que o mtodo de acesso nico pode ser ineficiente navegar pela
AST para encontrar a declarao apropriada tem seus prprios custos. Para eliminar
esta ineficincia, o compilador pode encadear a IR de modo que cada referncia tenha
um vnculo de volta para a declarao correspondente. Isso aumenta o espao da IR e
o overhead para o criador da IR.
A alternativa, como vimos no Captulo4, criar um repositrio central para esses fatos
e fornecer acesso eficiente a ele. Esse repositrio central, chamado tabela de smbolos,
torna-se uma parte integral da IR do compilador. Esta tabela localiza informaesobtidas de partes potencialmente distintas do cdigo-fonte; disponibiliza tais informaes
de modo fcil e eficiente, e simplifica o projeto e a implementao de qualquer
cdigo que deva se referir s informaes sobre variveis obtidas anteriormente na compilao. Isto evita o custo de pesquisar a IR para encontrar a parte que representaadeclarao de uma varivel; o uso de uma tabela de smbolos normalmente eliminaa
necessidade de representar as declaraes diretamente na IR. (Uma exceo ocorre
na traduo de fonte para fonte. O compilador pode construir uma tabela de smbolos
por eficincia e preservar a sintaxe da declarao na IR de modo que possa produzir
um programa de sada que seja o mais prximo possvel do de entrada.) Isso elimina
o overhead de fazer com que cada referncia contenha um ponteiro para a declarao,
e substitui ambos por um mapeamento computado a partir do nome textual para a
informao armazenada. Assim, de certa forma, a tabela de smbolos simplesmente
um truque de eficincia.

Quando o compilador grava a IR em disco, pode ser


mais barato recalcular fatos do que escrev-los
e depois l-los.

220 CAPTULO 5 Representaes intermedirias

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.

5.5.1 Tabelas hash


Um compilador acessa sua tabela de smbolos com frequncia. Portanto, eficincia
uma questo-chave no projeto desta tabela. Como as tabelas hash fornecem pesquisas
esperadas em tempo constante, so o mtodo escolhido para esta implementao. As
tabelas hash so conceitualmente elegantes; usam uma funo hash, h, para mapear
nomes a inteiros pequenos, e usam o inteiro pequeno para indexar a tabela. Com uma
tabela hash como tabela de smbolos, o compilador armazena todas as informaes
que obtm sobre o nome n no slot h(n) da tabela. A figura ao lado mostra uma tabela
hash simples com dez slots, que um vetor de registros, cada um mantendo a descrio
gerada pelo compilador de um nico nome. Os nomes a, b e c j foram inseridos. O
nome d est sendo inserido, em h(d)=2.
O motivo principal para usar tabelas hash fornecer uma pesquisa em tempo esperado
constante usando um nome textual como chave. Para conseguir isto, o clculo de h
no pode ser dispendioso. Dada uma funo apropriada h, o acesso ao registro para n
exige calcular h(n) e indexar na tabela em h(n). Se h mapear dois ou mais smbolos
para o mesmo inteiro pequeno, ocorre uma coliso. (Na figura ao lado, isto ocorreria
se h(d)=3.) A implementao precisa tratar desta situao de modo controlado,
preservando tanto a informao quanto o tempo de pesquisa. Nesta seo, consideramos
que h uma funo hash perfeita, ou seja, nunca produz uma coliso. Alm do mais,
consideramos que o compilador sabe, antecipadamente, o tamanho que a tabela ter.
O Apndice B4 descreve a implementao da tabela hash com mais detalhes, incluindo
funes hash, tratamento de coliso e esquemas para expandir esta tabela.
Tabelas hash podem ser usadas como uma representao eficiente para grafos esparsos.
Dados dois ns, x e y, uma entrada para a chave xy indica que a aresta (x, y) existe.
(Este esquema exige uma funo hash que gere uma boa distribuio a partir de um par
depequenos inteiros; as funes hash multiplicativas e universais descritas no Apndice
B4.1 funcionam bem.) Uma tabela hash bem implementada pode fornecer insero
rpida e um teste rpido para a presena de uma aresta especfica. Informaes adicionais so necessrias para responder a perguntas como que ns so adjacentes a x?.

5.5.2 Construo de uma tabela de smbolos


A tabela de smbolos define duas rotinas de interface para o restante do compilador.
1. LookUp(nome) retorna o registro armazenado na clula h(nome) da tabela, se
existir. Caso contrrio, retorna um valor indicando que nome no foi encontrado.
2. Insert(nome, r) armazena a informao disponvel em r na clula h(nome)
da tabela. Esta operao pode expandir a tabela para acomodar o registro para nome.

5.5 Tabelas de smbolos 221

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.

5.5.3 Tratamento de escopos aninhados


Poucas linguagens de programao fornecem um nico espao de nomes unificado. A
maioria delas permite que um programa declare nomes em mltiplos nveis. Cada um
desses nveis tem um escopo, ou uma regio no texto do programa onde o nome pode
ser usado. Cada um desses nveis tem um tempo de vida, ou um perodo em tempo de
execuo em que o valor preservado.

222 CAPTULO 5 Representaes intermedirias

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

FIGURA 5.10 Exemplo de escopo lxico simples em C.

5.5 Tabelas de smbolos 223

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.

224 CAPTULO 5 Representaes intermedirias

FIGURA 5.11 Implementao simples do feixe de tabelas.

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

Ao entrar em cada escopo, o compilador chama InitializeScope, e acrescenta


cada nome na tabela usando Insert. Quando sai de determinado escopo, chama

5.5 Tabelas de smbolos 225

FinalizeScope para descartar as declaraes para este escopo. Para o comando


de atribuio, pesquisa cada um dos nomes conforme encontrados. (A ordem das
chamadas de LookUp variar dependendo de como o comando de atribuio
percorrido.)
Se FinalizeScope retiver na memria as tabelas de smbolos para os nveis
finalizados, o resultado final dessas chamadas ser a tabela de smbolos mostrada na Figura5.12. O ponteiro do nvel atual definido como um valor nulo.
As tabelas para todos os nveis so deixadas na memria e ligadas para refletir
o aninhamento lxico. O compilador pode fornecer aos passos subsequentes do
compilador o acesso informao relevante da tabela de smbolos armazenando
um ponteiro para a tabela apropriada na IR no incio de cada novo nvel. Comoalternativa, os identificadores na IR podem apontar diretamente para suas entradas
na tabela de smbolos.

FIGURA 5.12 Tabela final para o exemplo.

5.5.4 Os muitos usos para as tabelas de smbolos


A discusso anterior focalizou uma tabela de smbolos central, apesar de poder ser
composta de vrias tabelas. Na realidade, os compiladores montam vrias destas tabelas
que utilizam para diferentes propsitos.

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.

226 CAPTULO 5 Representaes intermedirias

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.

Tabelas vinculadas para a resoluo de nomes em uma linguagem


orientada a objeto
Em uma linguagem orientada a objetos, as regras de escopo de nome so controladas
pela estrutura dos dados tanto quanto pela estrutura do cdigo. Isto cria um conjunto
deregras mais complicado, e tambm leva a um conjunto mais complicado de tabelas de
smbolos. Java, por exemplo, precisa de tabelas para o cdigo que est sendo compilado,
para quaisquer classes externas que sejam conhecidas e referenciadas no cdigo, e para
a hierarquia de herana acima da classe contendo o cdigo.
Uma implementao simples vincula uma tabela de smbolos a cada classe, com duas
hierarquias de aninhamento: uma para a definio do escopo lxico dentro dos mtodos
individuais e a outra seguindo a hierarquia de herana para cada classe. Como uma nica
classe pode servir como superclasse para vrias subclasses, esta ltima hierarquia
mais complicada do que pode sugerir o desenho simples do feixe de tabelas. Entretanto,
isto facilmente administrado.

5.5 Tabelas de smbolos 227

Para resolver um nome fee ao compilar um mtodo m na classe C, o compilador


primeiro consulta a tabela de smbolos com escopo lxico para m. Se no encontrar
fee nesta tabela, procura os escopos para as diversas classes na hierarquia de herana,
comeando com C e prosseguindo para cima na cadeia de superclasses a partir de C. Se
essa pesquisa no conseguir encontrar fee, a busca ento verifica a tabela de smbolos
global para uma classe ou tabela de smbolos desse nome. Esta tabela global precisa
conter informaes sobre o pacote atual e quaisquer outros pacotes que tenham sido
usados.
Assim, o compilador precisa de uma tabela com escopo lxico para cada mtodo,
construda enquanto ele compila os mtodos; precisa de uma tabela de smbolos
para cada classe, com vnculos para cima por meio da hierarquia de herana; precisa
de vnculos para as outras classes em seu pacote e para uma tabela de smbolos
para variveis em nvel de pacote; e precisa de acesso s tabelas de smbolos para
cada classe utilizada. O processo de procura (lookup) mais complexo, pois deve
seguir esses links na ordem correta e examinar apenas nomes que sejam visveis.
Porm, os mecanismos bsicos exigidos para implementar e manipular as tabelas
j so conhecidos.

5.5.5 Outros usos para a tecnologia de tabela


de smbolos
As ideias bsicas que esto por trs da implementao da tabela de smbolos tm
aplicao generalizada, tanto dentro de um compilador quanto em outros domnios.
Tabelas hash so usadas para implementar estruturas de dados esparsas; por exemplo,
um array esparso pode ser implementado construindo uma chave hash a partir dos
ndices e apenas armazenando valores diferentes de zero. Sistemas de runtime para
linguagens tipo LISP tm reduzido seus requisitos de armazenamento fazendo com
que o operador cons realize o hashing de seus argumentos efetivamente impondo
uma regra de que objetos textualmente idnticos compartilhem uma nica ocorrncia
na memria. Funes puras, aquelas que sempre retornam os mesmos valores para
os mesmos parmetros de entrada, podem usar uma tabela hash para produzir uma
implementao que se comporta como uma funo memo.

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.

228 CAPTULO 5 Representaes intermedirias

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?

5.6 RESUMO E PERSPECTIVA


A escolha de uma representao intermediria tem impacto importante sobre o projeto,
implementao, velocidade e eficcia de um compilador. Nenhuma das formas intermedirias descritas neste captulo , definitivamente, a resposta certa para todos
os compiladores ou todas as tarefas em determinado compilador. O projetista precisa
considerar os objetivos gerais de um projeto de compilador ao selecionar uma forma
intermediria, projetando sua implementao e acrescentando estruturas de dados
auxiliares, como tabelas de smbolos e rtulos.
Os sistemas de compilador contemporneos utilizam todas as formas de representaes
intermedirias, variando desde rvores sintticas, rvores sintticas abstratas (normalmente
usadas em sistemas de fonte a fonte) at cdigos lineares de nvel inferior ao de mquina
(usadas, por exemplo, nos sistemas de compilador Gnu). Muitos compiladores utilizam
vrias IRs criando uma segunda ou terceira para realizar uma anlise ou transformao
em particular, depois modificando a original, e definitiva, para refletir o resultado.

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

5.6 Resumo e perspectiva 229

trabalho de gerenciar detalhes, como aqueles necessrios para a gerao de cdigo,


mas tem menos transformaes (ou nenhuma) que exigem conhecimento tipo fonte,
como o bloqueio de lao para melhorar o comportamento da hierarquia de memria.

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:

poderia ser representado em uma rvore sinttica abstrata, em um grafo de fluxo


de controle e em qudruplas. Discuta as vantagens de cada representao.
Paraquais aplicaes uma representao seria prefervel s outras?
4. Examine o fragmento de cdigo mostrado na Figura5.13. Desenhe seu CFG e
mostre sua forma SSA como um cdigo linear.
5. Mostre como a expresso x 2y poderia ser traduzida em uma rvore
sinttica abstrata, cdigo de um endereo, cdigo de dois endereos e cdigo
detrs endereos.
6. Dada uma lista linear de operaes ILOC, desenvolva um algoritmo que encontre
os blocos bsicos no cdigo ILOC. Estenda seu algoritmo para construir um
grafo de fluxo de controle para representar as conexes entre blocos.
Seo 5.4
7. Para o cdigo mostrado na Figura5.14, encontre os blocos bsicos e construa
oCFG.
8. Considere os trs procedimentos em C mostrados na Figura5.15.
a. Suponha que um compilador use um modelo de memria registrador
-para-registrador. Quais variveis nos procedimentos A, B e C o compilador
seria forado a armazenar na memria? Justifique suas respostas.
b. Suponha que um compilador use um modelo de memria-para-memria.
Considere a execuo das duas instrues que esto na clusula if
daconstruo if-else. Se o compilador tem dois registradores disponveis
neste ponto da computao, quantos loads e stores ele precisaria emitir a
fimde carregar valores nos registradores e armazen-los de volta na memriadurante a execuo dessas duas instrues? E se o compilador tivesse trs
registradores disponveis?

230 CAPTULO 5 Representaes intermedirias

FIGURA 5.14 Fragmento de cdigo para o Exerccio 7.


FIGURA 5.13 Fragmento de cdigo
para o Exerccio 4.

FIGURA 5.15 Cdigo para o Exerccio 8.

5.6 Resumo e perspectiva 231

9. Em FORTRAN, duas variveis podem ser foradas a comear no mesmo local


dearmazenamento com uma instruo equivalence. Por exemplo,
ainstruo a seguir fora a e b a compartilharem o armazenamento:

O compilador pode manter uma varivel local em um registrador por todo


oprocedimento se esta varivel aparecer em uma instruo equivalence?
Justifique sua resposta.
Seo 5.5
10. Alguma parte do compilador precisa ser responsvel por incluir cada identificador na tabela de smbolos.
a. O scanner ou o parser devem incluir identificadores na tabela de smbolos?
Cada um tem uma oportunidade para fazer isto.
b. Existe uma interao entre essa questo, regras de declarar antes de usar,e
remoo da ambiguidade de subscritos a partir de chamadas de funo
emuma linguagem com a ambiguidade do FORTRAN 77?
11. O compilador precisa armazenar informaes na verso IR do programa,
que permite que ele retorne entrada da tabela de smbolos para cada nome.
Entreasopes abertas ao construtor de compiladores esto ponteiros para as
strings de caracteres originais e subscritos na tabela de smbolos. Naturalmente,
oimplementador inteligente pode descobrir outras opes. Quais so asvantagens e desvantagens de cada uma dessas representaes para um nome?
Comovoc o representaria?

FIGURA 5.16 Programa para o Exerccio 12.

232 CAPTULO 5 Representaes intermedirias

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

234 CAPTULO 6 A abstrao de procedimento

tambm exigem suporte em tempo de compilao na forma de algoritmos e estruturas


de dados que so executados dentro do compilador.
Este captulo explica as tcnicas usadas para implementar procedimentos e chamadas de
procedimento. Especificamente, examina a implementao de controle, de nomeao e
da interface de chamada. Essas abstraes encapsulam muitos dos recursos que tornam
as linguagens de programao usveis e que permitem a construo de sistemas em
larga escala.
Chamador
Em uma chamada de procedimento, referimo-nos
ao procedimento que chamou como chamador.
Chamado
Em uma chamada de procedimento, referimo-nos
ao procedimento que invocado como chamado.

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.

1. Abstrao de chamada de procedimento. As linguagens procedimentais admitem


uma abstrao para chamadas de procedimento. Cada linguagem tem um mecanismo padro para chamar um procedimento e mapear um conjunto de argumentos,
ou parmetros, do espao de nomes do chamador para o espao de nomes do
chamado. Esta abstrao normalmente inclui um mecanismo para retornar o controle ao chamador e continuar a execuo no ponto imediatamente aps a chamada.
A maioria das linguagens permite que um procedimento retorne um ou mais
valores ao chamador. O uso de convenes de ligao padro, s vezes chamadas
sequncias de chamada, permite que o programador chame cdigo escrito e
compilado por outras pessoas e em outras ocasies, o que, por sua vez, permite
que a aplicao chame rotinas de biblioteca e servios de sistema.
2. Espao de nomes. Na maioria das linguagens, cada procedimento cria um espao
de nomes novo e protegido. O programador pode declarar novos nomes, como
variveis e rtulos, sem se preocupar com o contexto ao redor. Dentro do procedimento, essas declaraes locais tm precedncia sobre quaisquer declaraes
anteriores para os mesmos nomes. O programador pode criar parmetros para
o procedimento, que permitem que o chamador mapeie valores e variveis no
espao de nomes do chamador para parmetros formais no espao de nomes
do chamado. Como o procedimento possui um espao de nomes conhecido e
separado, pode funcionar correta e consistentemente quando chamado a partir
de diferentes contextos. A execuo de uma chamada cria o espao de nomes do
chamado. A chamada deve criar armazenamento para os objetos declarados
pelo chamado. Esta alocao deve ser automtica e eficiente uma consequncia
de chamar o procedimento.
3. Interface externa. Os procedimentos definem as interfaces crticas entre as
partes de grandes sistemas de software. A conveno de ligao define regras

6.2 Chamadas de procedimento 235

que mapeiam nomes a valores e locais, que preservam o ambiente de runtime do


chamador e criam o ambiente do chamado, e transferem o controle do chamador para o chamado e de volta. Ela cria um contexto em que o programador
pode chamar com segurana o cdigo escrito por outras pessoas. A existncia
de sequncias de chamada uniformes permite o desenvolvimento e o uso de
bibliotecas e chamadas de sistema. Sem uma conveno de ligao, tanto o
programador quanto o compilador precisariam ter conhecimento detalhado sobre
a implementao do chamado em cada chamada de procedimento.
Assim, o procedimento , de vrias maneiras, a abstrao fundamental que est por
trs de linguagens como Algol. uma fachada elaborada, criada de modo colaborativo
pelo compilador e o hardware subjacente, com auxlio do sistema operacional. Procedimentos criam variveis nomeadas e as mapeiam para endereos virtuais; o sistema
operacional mapeia endereos virtuais a endereos fsicos. Procedimentos estabelecem
regras para visibilidade de nomes e endereabilidade; o hardware normalmente fornece
diversas variantes de operaes load e store. Procedimentos nos permitem decompor
grandes sistemas de software em componentes; ligadores (linkers) e carregadores
(loaders) juntam esses componentes para formar um programa executvel, que o
hardware pode executar avanando seu contador de programa e seguindo desvios.
UMA PALAVRA SOBRE TEMPO
Este captulo lida tanto com mecanismos em tempo de compilao quanto em tempo
de execuo. A distino entre os eventos que ocorrem em tempo de compilao e
aqueles que ocorrem em tempo de execuo pode ser confusa. O compilador gera
todo o cdigo que executado em runtime. Como parte do processo de compilao,
ele analisa o cdigo-fonte e contri estruturas de dados que codificam os resultados
da anlise. (Lembre-se da discusso de tabelas de smbolos com escopo lxico, na
Seo 5.5.3.) O compilador determina grande parte do layout de armazenamento que
o programa usar em tempo de execuo. Depois, gera o cdigo necessrio para criar
esse layout, mant-lo durante a execuo e para acessar os objetos de dados e cdigo
na memria. Quando o cdigo compilado executado, acessa objetos de dados e
chama procedimentos ou mtodos. Todo cdigo gerado em tempo de compilao;
todos os acessos ocorrem em tempo de execuo.

Grande parte da tarefa do compilador colocar no lugar o cdigo necessrio para


realizar os diversos segmentos da abstrao de procedimento. O compilador precisa
ditar o layout de memria e codific-lo no programa gerado. Como pode compilar
os diferentes componentes do programa em momentos diferentes, sem conhecer
seusrelacionamentos uns com os outros, este layout de memria e todas as convenes
que isso induz precisam ser padronizados e uniformemente aplicados. O compilador
tambm precisa usar as diversas interfaces fornecidas pelo sistema operacional, lidar
com entrada e sada, gerenciar a memria e comunicar-se com outros processos.
Este captulo se concentra no procedimento como uma abstrao e nos mecanismos
que o compilador usa para estabelecer sua abstrao de controle, espao de nomes, e
interface com o mundo exterior.

6.2 CHAMADAS DE PROCEDIMENTO


Em linguagens como Algol (ALLs Algol-like languages), os procedimentos possuem
uma disciplina de chamada/retorno simples e clara. Uma chamada de procedimentotransfere o controle do local de chamada no chamador para o incio do procedimento
chamado; na sada do chamado, o controle retorna ao ponto no chamador que vem

236 CAPTULO 6 A abstrao de procedimento

FIGURA 6.1 Programa no recursivo em Pascal e seu histrico de execuo.

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

6.2 Chamadas de procedimento 237

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.

O mecanismo de pilha tambm trata da recurso. O mecanismo de chamada, efetivamente


desdobra o caminho cclico pelo grafo de chamadas e cria uma ativao distinta para cada
chamada a um procedimento. Desde que a recurso termine, este caminho ser finito, e
a pilha de endereos de retorno capturar corretamente o comportamento do programa.
Para tornar isto concreto, considere a computao recursiva para fatorial mostrada na
Figura6.2. Quando chamada para calcular (fact 5), gera uma srie de chamadas
recursivas: (fact 5) chama (fact 4) que chama (fact 3) que chama (fact 2) que
chama (fact 1). Neste ponto, a instruo cond executa a clusula para (<= k1), terminando a recurso. A recurso retorna na ordem contrria, com a chamada a (fact 1)
retornando o valor 1 para (fact 2). Esta, por sua vez, retorna o valor 2 para (fact 3)
, que retorna 6 para (fact 4). Finalmente, (fact 4) retorna 24 para (fact 5),
que multiplica 24 por 5 para retornar a resposta 120. O programa recursivo exibe um
comportamento do tipo ltimo a entrar, primeiro a sair (LIFO Last In First Out), de
modo que o mecanismo de pilha rastreia corretamente todos os endereos de retorno.

FIGURA 6.2 Programa recursivo de fatorial em Scheme.

Fluxo de controle em linguagens orientadas a objeto


Do ponto de vista de chamadas e retornos de procedimento, as linguagens orientadas
a objeto (OOLs) so semelhantes s ALLs. As principais diferenas entre elas esto
no mecanismo usado para nomear o procedimento chamado e nos mecanismos usados
para localizar o chamado em tempo de execuo.

238 CAPTULO 6 A abstrao de procedimento

Fecho (Closure)
Um procedimento e o contexto de execuo que define
suas variveis livres.

Fluxo de controle mais complexo


Seguindo Scheme, muitas linguagens de programao permitem que um programa
encapsule um procedimento e seu contexto de execuo em um objeto chamado fecho
(closure). Quando o fecho invocado, o procedimento executado no contexto de
execuo encapsulado. Uma pilha simples inadequada para implementar esta abstrao de controle. Ao invs disso, a informao de controle deve ser salva em alguma
estrutura mais geral, que possa representar o relacionamento de fluxo de controle
mais complexo. Problemas semelhantes surgem se a linguagem permitir referncias a
variveis locais que durem mais do que a ativao de um procedimento.

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.

6.3 ESPAOS DE NOMES


Escopo
Em uma linguagem como Algol, escopo refere-se a um
espao de nomes. O termo normalmente usado em
discusses sobre a visibilidade de nomes.

Na maioria das linguagens procedimentais, um programa completo ter vrios


espaos de nomes. Cada um destes espaos, ou escopo, mapeia um conjunto de
nomes a um conjunto de valores e procedimentos sobre algum conjunto de instrues no cdigo. Essa faixa de instrues poderia ser o programa inteiro, alguma
coleo de procedimentos, um nico procedimento ou um pequeno conjunto de
instrues. O escopo pode herdar alguns nomes de outros escopos. Dentro de um
escopo, o programador pode criar nomes que so inacessveis fora dele. A criao
de um nome, fee, dentro de um escopo pode obscurecer as definies de fee

6.3 Espaos de nomes 239

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.

6.3.1 Espaos de nomes de linguagens como Algol


A maioria das linguagens de programao herda muitas das convenes que foram
definidas para Algol 60. Isto particularmente verdade para as regras que controlam
a visibilidade de nomes. Esta seo explora a noo de nomeao que prevalece nas
ALLs, com nfase em particular s regras de escopo hierrquicas que se aplicam a
tais linguagens.

Escopos lxicos aninhados


A maioria das ALLs permite que o programador aninhe escopos um dentro do outro. Os
limites de um escopo so marcados por smbolos terminais especficos da linguagem
de programao. Normalmente, cada novo procedimento define um escopo que cobre
sua definio inteira. Pascal demarcava escopos com begin no incio e end no final.
C usa chaves, { e } , para comear e terminar um bloco; cada bloco define um novo
escopo.

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.

Pascal popularizou os procedimentos aninhados. Cada procedimento define um novo


escopo, e o programador pode declarar novas variveis e procedimentos em cada escopo. A linguagem usa a disciplina de definio de escopo mais comum, chamada
escopo lxico, cujo princpio geral simples:
Em determinado escopo, cada nome refere-se sua definio lexicamente
mais prxima.
Assim, se s for usado no escopo atual, refere-se ao s declarado neste escopo, se houver.
Se no, refere-se declarao de s que ocorre no escopo que o engloba mais prximo.
O escopo mais externo contm variveis globais.
Para tornar o escopo lxico mais claro, considere o programa em Pascal mostrado
na Figura6.3, que contm cinco escopos distintos, um correspondente ao programa Main e um para cada um dos procedimentos Fee, Fie, Foe e Fum. Cada
procedimento declara algum conjunto de variveis retirado do conjunto de nomes
x, y e z.
A figura mostra cada nome com um subscrito que indica seu nmero de nvel. Os
nomes declarados em um procedimento sempre tm um nvel uma unidade superior
ao nvel do nome do procedimento. Assim, se Main tem nvel 0, conforme mostrado,
os nomes declarados diretamente em Main, como x, y, z, Fee e Fie,tm todos
nvel 1.
Para representar nomes em uma linguagem com escopo lxico, o compilador pode
usar a coordenada esttica para cada nome. A coordenada esttica um par n, d,
onde n o nvel de aninhamento lxico do nome e d o deslocamento na rea de dados
para este nvel. Para obter n, o front end usa uma tabela de smbolos com escopo
lxico, conforme descrito na Seo 5.5.3. O deslocamento, d, deve ser armazenado
com o nome e seu nvel na tabela de smbolos. (Deslocamentos podem ser atribudos
quandoas declaraes so processadas durante a anlise sensvel ao contexto.) A tabela
no lado direito da Figura6.3 mostra a coordenada esttica para cada nome de varivel
em cada procedimento.

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.

240 CAPTULO 6 A abstrao de procedimento

FIGURA 6.3 Escopos lxicos aninhados em Pascal.

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.

6.3 Espaos de nomes 241

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.

Regras de escopo em diferentes linguagens


As regras de escopo de linguagens de programao variam de maneira caracterstica
conforme a linguagem. O construtor de compiladores precisa entender as regras especficas de uma linguagem fonte e adaptar os esquemas de traduo gerais para trabalhar
com essas regras especficas. A maioria das ALLs tem regras de escopo semelhantes.
Considere as regras para as linguagens FORTRAN, C e Scheme:
j

FORTRAN tem um espao de nomes simples. Um programa FORTRAN cria


um nico escopo global junto com um escopo local para cada procedimento
ou funo. As variveis globais so agrupadas em um bloco common; cada
bloco common consiste em um nome e uma lista de variveis. O escopo global
mantm os nomes dos procedimentos e blocos common. Nomes globais tm
tempos de vida que correspondem ao tempo de vida do programa. O escopo
de um procedimento mantm nomes de parmetro, variveis locais e rtulos.
Nomes locais obscurecem os nomes globais, se conflitarem. Os nomes no escopo
local tm, como padro, tempos de vida que correspondem a uma invocao do
procedimento. O programador pode dar a uma varivel local o tempo de vida de
uma varivel global, listando-a em uma instruo save.
j C tem regras mais complexas. Um programa C tem um escopo global para
nomes de procedimento e variveis globais. Cada procedimento tem um escopo
local para variveis, parmetros e rtulos. A definio da linguagem no permite
procedimentos aninhados, embora alguns compiladores tenham implementado
este recurso como uma extenso. Os procedimentos podem conter blocos
(delimitados por abre e fecha chaves) que criam escopos locais separados; os
blocos podem ser aninhados. Os programadores normalmente usam um escopo
em nvel de bloco para criar armazenamento temporrio para o cdigo gerado

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.

242 CAPTULO 6 A abstrao de procedimento

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

Scheme tem um conjunto simples de regras de escopo. Quase todos os objetos


em Scheme residem em um nico espao global. Os objetos podem ser dados
ou expresses executveis. As funes fornecidas pelo sistema, como cons,
convivem com o cdigo escrito pelo usurio e itens de dados. O cdigo,
que consiste de uma expresso executvel, pode criar objetos privados usando
uma expresso let. O aninhamento de expresses let umas dentro das outras
pode criar escopos lxicos aninhados de profundidade arbitrria.

6.3.2 Estruturas de runtime para dar suporte


a linguagens como Algol
Registro de ativao
Uma regio de armazenamento separada para manter
informaes de controle e armazenamento de dados
associados a uma nica ocorrncia de um nico
procedimento.

Para implementar as abstraes casadas de chamadas de procedimento e espaos de


nomes com escopo, a traduo precisa estabelecer um conjunto de estruturas de runtime.
A principal estrutura de dados envolvida no controle e na nomeao o registro de
ativao (AR Activation Record), um bloco de memria privado associado a uma
chamada especfica de um procedimento especfico. Em princpio, cada chamada de
procedimento faz surgir um novo AR.
j

O compilador precisa fazer com que cada chamada armazene o endereo de


retorno onde o procedimento chamado possa encontr-lo. Este endereo entra no
AR.
j O compilador precisa mapear os parmetros reais no local de chamada aos
nomes de parmetro formais pelos quais so conhecidos no procedimento
chamado. Para tanto, armazena informaes de parmetro ordenadas no AR.
j O compilador precisa criar espao de armazenamento para variveis declaradas
no escopo local do procedimento chamado. Como esses valores possuem tempos
de vida que correspondem ao tempo de vida do endereo de retorno, conveniente armazen-los no AR.
j O procedimento chamado precisa de outras informaes para conect-lo ao
programa ao seu redor, e para permitir que interaja com segurana com outros
procedimentos. O compilador consegue armazenar essa informao no AR do
procedimento chamado.
Como cada chamada cria um novo AR, quando vrias ocorrncias de um procedimento
esto ativas, cada uma tem seu prprio AR. Assim, a recurso faz surgir mltiplos
ARs, cada um mantendo o estado local para uma chamada diferente do procedimento
recursivo.
Ponteiro de registro de ativao
Para localizar o AR atual, o compilador mantm um
ponteiro para o AR, o ponteiro de registro de ativao,
em um registrador designado.

A Figura6.4 mostra como o contedo de um AR poderia ser disposto. O AR inteiro


endereado por meio de um ponteiro de registro de ativao (ARP Activation Record
Pointer), com diversos campos no AR encontrados em deslocamentos positivos e
negativos a partir do ARP. Os ARs na Figura6.4 possuem diversos campos.

6.3 Espaos de nomes 243

FIGURA 6.4 Registros de ativao tpicos.

j
j

j
j
j

A rea de parmetros mantm os parmetros reais do local de chamada, em uma


ordem que corresponde sua ordem de aparecimento na chamada.
A rea de salvamento de registradores contm espao suficiente para manter
registradores que o procedimento deve preservar devido a chamadas de procedimento.
O slot de valor de retorno fornece espao para comunicar dados do procedimento
chamado de volta ao chamador, se necessrio.
O slot de endereo de retorno mantm o endereo de runtime onde a execuo
deve reiniciar quando o procedimento chamado terminar.
O slot de endereabilidade mantm informaes usadas para permitir que o
procedimento chamado acesse variveis nos escopos lxicos ao seu redor (no
necessariamente o chamador).
O slot no ARP do procedimento chamado armazena o ARP do chamador. O procedimento chamado precisa deste ponteiro para que possa restaurar o ambiente
do chamador quando terminar sua execuo.
A rea de dados local mantm variveis declaradas no escopo local do procedimento chamado.

Por questo de eficincia, algumas informaes mostradas na Figura6.4 podem ser


mantidas em registradores dedicados.

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

244 CAPTULO 6 A abstrao de procedimento

ARP. Os extremos do AR so reservados para reas de armazenamento cujos tamanhos


podem mudar de uma chamada para outra, normalmente um mantm o armazenamento
de parmetros, enquanto o outro, os dados locais.

Reserva de espao para dados locais


Cada item de dados local pode precisar de espao no AR. O compilador deve atribuir a
cada item deste tipo uma rea com tamanho apropriado e registrar o nvel lxico atual
e seu deslocamento a partir do ARP na tabela de smbolos. Esse par, nvel lxico e
deslocamento, torna-se a coordenada esttica do item. Ento, a varivel pode ser acessada usando uma operao como loadAO, com rarp, e o deslocamento como seus
argumentos, para fornecer acesso eficiente s variveis locais.
O compilador pode no conhecer os tamanhos de algumas variveis locais em tempo de
compilao. Por exemplo, o programa poderia ler o tamanho de um array a partirde uma
mdia externa, ou determin-lo pelo trabalho feito em uma fase anterior da computao.
Para tais variveis, o compilador pode deixar espao na rea de dados local para um
ponteiro para os dados reais ou para um descritor de array (ver Seo 7.5.3). O compilador aloca o espao de armazenamento real em outro lugar, em tempo de execuo, e
preenche o slot reservado com o endereo da memria alocada dinamicamente. Neste
caso, a coordenada esttica leva o compilador ao local do ponteiro, e o acesso real usa
o ponteiro diretamente ou usa o ponteiro para calcular um endereo apropriado na rea
de dados de tamanho varivel.

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.

Espao para valores salvos de registradores


Quando p chama q, um deles precisa salvar os valores de registrador que p precisar
depois da chamada. Pode ser necessrio salvar todos os valores de registrador; por
outro lado, um subconjunto pode ser suficiente. No retorno para p, esses valores
salvos devem ser restaurados. Como cada ativao de p armazena um conjunto distinto
devalores, faz sentido armazenar esses registradores salvos no AR de p ou de q, ou de
ambos. Se o procedimento chamado salva um registrador, seu valor armazenado na
rea de salvamento de registradores do procedimento chamado. De modo semelhante,
se o chamador salva um registrador, seu valor armazenado na rea de salvamento de
registradores do chamador. Para um chamador p, somente uma chamada dentro de p
pode estar ativa a cada vez. Assim, uma nica rea de salvamento de registradores no
AR de p suficiente para todas as chamadas que p pode fazer.

6.3 Espaos de nomes 245

Alocao de registros de ativao


Quando p chama q em tempo de execuo, o cdigo que implementa a chamada precisa
alocar um AR para q e inicializ-lo com os valores apropriados. Se todos os campos
mostrados na Figura6.4 estiverem armazenados na memria, ento o AR precisa estar
disponvel ao chamador, p, para que possa armazenar os parmetros reais, endereo de
retorno, ARP do chamador e informaes de endereabilidade. Isto fora a alocao
do AR de q em p, onde o tamanho de sua rea de dados local pode no ser conhecido.
Por outro lado, se esses valores forem passados em registradores, a alocao real do
AR pode ser executada no procedimento chamado, q. Isto permite que q aloque o AR,
incluindo qualquer espao exigido para a rea de dados local. Aps a alocao, ele pode
armazenar em seu AR alguns dos valores passados em registradores.
O construtor de compiladores tem vrias opes para alocar registros de ativao. A
escolha afeta tanto o custo das chamadas de procedimento quanto o custo de implementao de recursos avanados da linguagem, como a criao de um fecho (closure).
Afeta tambm, a quantidade total de memria necessria para os registros de ativao.

Alocao de registros de ativao em pilha


Em muitos casos, o contedo de um AR de interesse apenas durante o tempo de
vida do procedimento cuja ativao causa sua criao. Resumindo, a maior parte das
variveis no pode sobreviver fora do procedimento que as cria, assim como a maioria
das ativaes de procedimento em relao aos seus chamadores. Com essas restries,
chamadas e retornos so balanceados; e seguem uma disciplina do tipo ltimo a entrar,
primeiro a sair (LIFO). Uma chamada de p para q eventualmente retorna, e quaisquer
retornos que ocorram entre a camada de p para q e o retorno de q para p devem resultar
de chamadas feitas (direta ou indiretamente) por q. Neste caso, os registros de ativao
tambm seguem a ordenao LIFO; assim, podem ser alocados em uma pilha. Pascal,
C e Java normalmente so implementados com ARs alocados em pilha.
Manter registros de ativao em uma pilha tem muitas vantagens. A alocao e a
desalocao so pouco dispendiosas; cada uma exige uma operao aritmtica sobre o
valor que marca o topo da pilha. O chamador pode iniciar o processo de configurao
do AR do procedimento chamado. E pode alocar todo o espao at a rea de dados
local. O procedimento chamado pode estender o AR para incluir a rea de dados local,
incrementando o ponteiro do topo da pilha (TOS - Top-of-Stack). E pode usar o mesmo
mecanismo para estender o AR atual de modo incremental, a fim de manter objetos
de tamanho varivel, conforme mostra a Figura6.5. Aqui, o procedimento chamado
copiou o ponteiro do TOS para o slot da rea de dados local de A e depois incrementou
o ponteiro do TOS pelo tamanho de A. Finalmente, com ARs alocados em pilha, um
depurador pode percorrer a pilha do topo at a base para produzir um instantneo dos
procedimentos atualmente ativos.

Alocao de registros de ativao em heap


Se o procedimento puder sobreviver ao seu chamador, a disciplina de pilha para alocar
ARs perde sua validade. De modo semelhante, se um procedimento pode retornar um
objeto, como um fecho (closure), que inclui, explcita ou implicitamente, referncias s
suas variveis locais, a alocao em pilha imprpria, pois deixar para trs ponteiros
soltos. Nessas situaes, os ARs podem ser mantidos em heap (ver Seo6.6). As
implementaes de Scheme e ML normalmente utilizam ARs alocados em heap.
Um alocador de memria moderno pode manter baixo o custo da alocao em heap.
Com ARs alocados em heap, objetos de tamanho varivel podem ser alocados como
objetos separados no heap. Se os objetos do heap precisarem de desalocao explcita,

FIGURA 6.5 Alocao de pilha de um array


dimensionado dinamicamente.

246 CAPTULO 6 A abstrao de procedimento

ento o cdigo para o retorno do procedimento deve liberar o AR e suas extenses de


tamanho varivel. Com desalocao implcita (ver Seo6.6.2), o coletor de lixo os
libera quando no forem mais teis.
Procedimento folha
Procedimento que no contm chamadas.

Alocao esttica dos registros de ativao


Se um procedimento q no chama outros procedimentos, ento q nunca pode ter vrias
chamadas ativas. Chamamos q de procedimento folha, pois ele termina um caminho
por um grafo de possveis chamadas de procedimento. O compilador pode alocar estaticamente registros de ativao para procedimentos folha, o que elimina os custos de
runtime da alocao do AR. Se a conveno de chamada exigir que o chamador salve seus
prprios registradores, ento o AR de q no precisa de rea de salvamento de registradores.
Se a linguagem no permitir fechos, o compilador pode fazer melhor do que alocar
um AR esttico para cada procedimento folha. Em qualquer ponto durante a execuo,
somente um procedimento folha pode estar ativo. (Para ter dois ativos, o primeiro
precisaria chamar outro procedimento, de modo que no seria uma folha.) Assim, o
compilador pode alocar um nico AR esttico para ser usado por todos os procedimentos folha. O AR esttico deve ser grande o suficiente para acomodar qualquer um
desses procedimentos folha do programa. As variveis estticas declaradas em qualquer
um dos procedimentos folha podem ser dispostas juntas nesse nico AR. O uso de um
nico AR esttico para procedimentos folha reduz o overhead de espao dos ARs estticos separados para cada procedimento folha.

Aglutinao de registros de ativao


Se o compilador descobrir um conjunto de procedimentos que sempre so chamados em
uma sequncia fixa, pode ser capaz de combinar seus registros de ativao. Por exemplo,
se uma chamada de p para q sempre resultar em chamadas para r e s, o compiladorpodeachar lucrativo alocar os ARs para q, r e s ao mesmo tempo. A combinao de ARs
pode economizar nos custos de alocao; os benefcios variaro diretamente com os
custos de alocao. Na prtica, esta otimizao limitada pela compilao separada e o
uso de parmetros com valor de funo. Ambos limitam a capacidade do compilador de
determinar os relacionamentos de chamada que realmente ocorrem em tempo de execuo.

6.3.3 Espaos de nomes de linguagens orientadas a objeto


Muito tem sido escrito sobre projeto orientado a objeto, programao orientada a
objeto e linguagens orientadas a objeto. Linguagens como Simula, Smalltalk, C++
e Java admitem este tipo de programao. Muitas outras possuem extenses que lhes
fornecem recursos para dar suporte programao orientada a objetos. Infelizmente, o
termo orientado a objeto tem recebido tantos significados e implementaes diferentes,
que passou a significar uma grande gama de recursos de linguagem e paradigmas de
programao.
Compilador just-in-time
Esquemas que realizam algumas das tarefas
de um compilador tradicional em tempo de execuo
normalmente so chamados de compiladores
just-in-time, ou JITs.
Em um JIT, o tempo de compilao torna-se parte
do runtime, de modo que os JITs enfatizam a eficincia
em tempo de compilao.

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

6.3 Espaos de nomes 247

p rocedimento de uma ALL para uso dentro do cdigo procedimental. E aprimoram


esse esquema de nomeao clssico com um segundo conjunto de convenes para
nomeao, organizado em torno do layout de dados especificamente, as definies de
objetos. Essa disciplina de nomeao centrada nos dados leva a uma segunda hierarquia
de escopos e a um segundo mecanismo para resoluo de nomes ou seja, para mapear
um nome na linguagem-fonte ao endereo de runtime, de modo que o cdigo compilado
possa acessar os dados associados a esse nome.

TERMINOLOGIA PARA LINGUAGENS ORIENTADAS A OBJETO


A diversidade de linguagens orientadas a objeto tem causado alguma ambiguidade
nos termos que usamos para discutir a respeito delas. Para tornar a discusso neste
captulo mais concreta, usaremos os seguintes termos:
1. Objeto. Uma abstrao com um ou mais membros. Esses membros podem ser itens
de dados, cdigo que manipula esses itens de dados ou outros objetos. Um objeto
com membros de cdigo uma classe. Cada objeto possui um estado interno
dados cujos tempos de vida correspondem ao tempo de vida do objeto.
2. Classe. Uma coleo de objetos com a mesma estrutura abstrata e caractersticas.
Uma classe define o conjunto de membros de dados em cada ocorrncia da classe
e define os membros de cdigo (mtodos) que so locais a esta classe. Alguns
mtodos so pblicos, ou visveis externamente, outros, privados, ou invisveis
fora da classe.
3. Herana. Refere-se a um relacionamento entre classes que define uma ordem
parcial sobre os escopos de nome das classes. Cada classe pode ter
uma superclasse, da qual ela herda tanto membros de cdigo quanto membros
de dados. Se a a superclasse de b, ento b uma subclasse de a. Algumas
linguagens permitem que uma classe tenha vrias superclasses.
4. Receptor. Mtodos so invocados em relao a algum objeto, denominado receptor
do mtodo. Dentro do mtodo, o receptor conhecido por um nome designado,
como this ou self.
A complexidade e o poder de uma OOL surgem, em grande parte, das possibilidades
organizacionais apresentadas por seus mltiplos espaos de nomes.

A herana impe um relacionamento de ancestral sobre as classes de uma aplicao.


Cada classe tem, por declarao, uma ou mais classes pai, ou superclasses. A herana
muda tanto o espao de nomes da aplicao quanto o mapeamento de nomes de mtodo
a implementaes. Se a uma superclasse de b, ento b uma subclasse de a, e
qualquer mtodo definido em a deve operar corretamente em um objeto da classe b,
se estiver visvel em b. O contrrio no verdadeiro; um mtodo declarado na classe
b no pode ser aplicado a um objeto de sua superclasse a, pois o mtodo de b pode
precisar de campos presentes em um objeto da classe b que estejam ausentes em um
objeto da classe 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.

A sintaxe e a terminologia usadas para especificar


subclasses variam entre as linguagens. Em Java, uma
subclasse estende sua superclasse, enquanto em C++
uma subclasse derivada de sua superclasse.

248 CAPTULO 6 A abstrao de procedimento

Para tornar essas questes concretas, considere o exemplo simplificado apresentado na


Figura6.6, que define uma classe, Point, de objetos com campos inteiros x e y e mtodos
draw (desenhar) e move (mover). ColorPoint uma subclasse de Point que a estende com um campo adicional c do tipo Color. Ela usa o mtodo dePoint para move,
sobrescreve o mtodo para draw e define um novo mtodo test que realiza alguma
computao e depois chama draw. Finalmente, a classe C define os campos e mtodos
locais e usa ColorPoint.
Agora, considere os nomes que esto visveis dentro do mtodo m da classe C. O mtodo
m mapeia x e y s suas declaraes em C. E referencia expressamente os nomes de
classe Point e ColorPoint. A atribuio y = p.x toma seu lado direito do campo
x do objeto p, que p tem por herana da classe Point. O lado esquerdo refere-se
varivel local y de m. A chamada a graw mapeia o mtodo definido em ColorPoint.
Assim, m refere-se s definies de todas as trs classes no exemplo.
Para traduzir este exemplo, o compilador precisa rastrear a hierarquia de nomes e escopos estabelecida tanto pelas regras de escopo dentro dos mtodos e classes quanto
pela hierarquia de classes e superclasses estabelecida pelos extends. A resoluode
nomes nesse ambiente depende tanto dos detalhes das definies de cdigo quanto da
estrutura de classes das definies de dados. Para traduzir uma OOL, o compilador
precisa modelar tanto o espao de nomes do cdigo quanto os espaos de nomes associados com a hierarquia de classes. A complexidade desse modelo depende dos
detalhes da OOL especfica.
Para acrescentar uma complicao final, algumas OOLs fornecem atributos para
nomes individuais que mudam sua visibilidade. Por exemplo, um nome em Java
pode ter os atributos public ou private. De modo semelhante, algumas OOLs
fornecem um mecanismo para referenciar nomes obscurecidos pelo aninhamento.

FIGURA 6.6 Definies para Point e ColorPoint.

6.3 Espaos de nomes 249

Em C++, o operador :: permite que o cdigo nomeie um escopo, enquanto em Java


o programador pode usar um nome totalmente qualificado.

Em Java, public torna um nome visvel em


qualquer lugar, enquanto private o torna visvel
apenas dentro de sua prpria classe.

Nomeao na hierarquia de classes


A hierarquia de classes define um conjunto de escopos de nomes aninhados, assim como
faz um conjunto de procedimentos e blocos aninhados em uma ALL. Em uma ALL, a
posio lxica define o relacionamento entre esses escopos de nome se o procedimento
d declarado dentro do procedimento c, ento o espao de nomes de d aninhado
dentro do espao de nomes de c. Em uma OOL, as declaraes de classe podem ser
lexicamente disjuntas e a relao de subclasse especificada por declaraes explcitas.
Para encontrar a declarao de um nome, o compilador deve pesquisar a hierarquia
lxica, a hierarquia de classes e o espao de nomes global. Para um nome x em um
mtodo m, o compilador primeiro procura os escopos lxicos que englobam a referncia
em m. Se essa pesquisa falhar, procura na hierarquia de classes a classe que contmm.
Conceitualmente, ele pesquisa a classe declarada de m, depois a superclasse direta
dem, a superclasse direta dessa classe, e assim por diante, at encontrar o nome ouesgotar a hierarquia de classes. Se o nome no for encontrado na hierarquia lxica ou na
hierarquia de classes, o compilador procura no 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.

Estrutura de classes fechada


Se a estrutura de classes de uma aplicao for fixa
em tempo de compilao, a OOL tem uma hierarquia
fechada.
Estrutura de classes aberta
Se uma aplicao puder mudar sua estrutura de classes
em tempo de execuo, tem uma hierarquia aberta.

250 CAPTULO 6 A abstrao de procedimento

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.

Dado um mtodo m, o compilador pode mapear um nome que aparece em m para


uma declarao em algum escopo aninhado de m, ou para a definio de classe que
contm m. Se o nome for declarado em uma superclasse, a capacidade do compilador
de determinar qual superclasse declara o nome depender se a estrutura de classes
aberta ou fechada. Se fechada, o compilador tem a hierarquia de classes completa, de
modo que pode resolver todos os nomes de volta s suas declaraes e, com estruturas
de runtime apropriadas para dar suporte nomeao, pode gerar cdigo para acessar
qualquer nome. Se aberta, o compilador pode no conhecer a estrutura de classes
antes do tempo de execuo. Essas linguagens exigem mecanismos de runtime para
resolver nomes na hierarquia de classes; este requisito, por sua vez, normalmente leva a
implementaes que contam com interpretao ou compilao em tempo de execuo.
Situaes semelhantes podem surgir das converses explcita ou implcita em uma
linguagem com estrutura de classes fechada; por exemplo, funes virtuais em C++
podem exigir suporte em tempo de execuo.

6.3.4 Estruturas de runtime para dar suporte s linguagens


orientadas a objeto
Assim como linguagens como Algol precisam de estruturas de runtime para dar suporte
aos seus espaos de nomes lxicos, tambm as linguagens orientadas a objeto precisam
destas estruturas para dar suporte s suas hierarquias lxica e de classes. Algumas dessas
estruturas so idnticas quelas encontradas em uma ALL. Por exemplo, a informao
de controle para os mtodos, bem como o armazenamento para nomes locais ao mtodo,
so armazenados em ARs. Outras estruturas so projetadas para enfrentar problemas especficos introduzidos pela OOL. Por exemplo, os tempos de vida de objetos no precisam
corresponder chamada de qualquer mtodo em particular, de modo que seu estado
persistente no pode ser armazenado em algum AR. Assim, cada objeto precisa do seu
prprio registro de objeto (OR Object Record) para manter seu estado. Os ORs de classes
utilizam a hierarquia de herana, e desempenham papel crtico na traduo e na execuo.
A quantidade de suporte em runtime que uma OOL precisa depende muito das caractersticas da OOL. Para explicar a gama de possibilidades, comearemos com as
estruturas que poderiam ser geradas para as definies na Figura6.6, considerando
uma linguagem com herana nica e estrutura de classes aberta. A partir deste caso
bsico, exploraremos as simplificaes e as otimizaes permitidas por uma estrutura
de classe fechada.
A Figura6.7 mostra as estruturas de runtime que poderiam resultar da criao de trs
objetos, usando as definies da Figura6.6. SimplePoint utiliza Point, enquanto

FIGURA 6.7 Estrutura de runtime para o exemplo ColorPoint.

6.3 Espaos de nomes 251

tanto LeftCorner quanto RightCorner utilizam ColorPoint. Cada objeto


tem seu prprio OR, assim como as classes Point e ColorPoint. Para ser completo, o diagrama mostra um OR para a classe class. Dependendo da linguagem,
uma implementao pode evitar a representao de alguns desses campos, vetores de
mtodos e ponteiros.
O OR para um objeto simples, como LeftCorner, contm um ponteiro para a classe
que definiu LeftCorner, um ponteiro para o vetor de mtodos para esta classe, e
espao para seus campos, x, y e c. Observe que os campos herdados em um ColorPoint e em seu vetor de mtodos tm o mesmo deslocamento que teriam na classe
de base Point. O OR para ColorPoint literalmente estende o OR para Point. A
consistncia resultante permite que um mtodo de superclasse, como Point.move,
opere corretamente sobre um objeto de subclasse, como LeftCorner.
OR para uma classe contm um ponteiro para sua classe, class, um ponteiro para
o vetor de mtodos para class e seus campos locais, que incluem superclass
e class methods. Na figura, todos os vetores de mtodos so desenhados como
vetores de mtodos completos ou seja, incluem todos os mtodos para a classe, tanto
locais quanto herdados. O campo superclass registra a hierarquia de herana, que
pode ser necessria em uma estrutura de classe aberta. O campo class methods
aponta para o vetor de mtodos usado pelos membros da classe.
Para evitar um emaranhado confuso de linhas na figura, simplificamos os vetores de
mtodos de vrias maneiras. O desenho mostra vetores de mtodos separados, ao
invs de ponteiros para uma cpia compartilhada dos vetores de mtodos da classe. As
cpiasso desenhadas em cinza. A classe class tem ponteiros nulos para seus campos
methods e class methods. Em uma implementao real, estes provavelmente
teriam alguns mtodos que, por sua vez, causariam ponteiros no nulos no campo
methods de Point e ColorPoint.

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

252 CAPTULO 6 A abstrao de procedimento

resultado desejado um objeto da classe x chama a implementao de um mtodo que


visvel dentro da definio para esta classe. O esquema alternativo representaria apenas
os mtodos definidos localmente de ColorPoint em seu vetor class methods,
e localizaria um mtodo herdado perseguindo ORs pela cadeia de superclasses acima
de maneira semelhante aos links de acesso para o escopo lxico e ARs.

Implementao pode ser um compilador, um interpretador ou um JIT. O problema do layout o mesmo.

Layout de registro de objeto


Um ponto sutil no exemplo que uma implementao precisa manter deslocamentos
consistentes, por nome, para cima e para baixo na hierarquia de superclasses. Campos,
como x e y, precisam aparecer no mesmo deslocamento em um OR da classe Point e
ColorPoint para um mtodo, como move, operar corretamente sobre ORs da sua
classe ou suas superclasses. Pelo mesmo motivo, os mtodos precisam aparecer nos
mesmos deslocamentos nos vetores de mtodo de classes relacionadas.
Sem herana, o compilador pode atribuir deslocamentos em uma ordem qualquer para
os campos e os mtodos da classe. Ele compila esses deslocamentos diretamente no cdigo. O cdigo usa o ponteiro do receptor (por exemplo, this) e os deslocamentos para
localizar qualquer campo desejado no OR ou qualquer mtodo no vetor de mtodos.
Com herana nica, o layout do OR simples. Como cada classe tem apenas uma
superclasse direta, o compilador anexa os novos campos ao final do layout do OR da
superclasse, estendendo-o. Esta tcnica, chamada prefixao, garante deslocamentos
consistentes para cima e para baixo na hierarquia de superclasse. Quando um objeto
convertido para uma de suas superclasses, os campos no OR esto em seus locais
esperados. Os ORs na Figura6.7 seguem este esquema.
Em uma linguagem com estrutura de classes fechada, o layout do registro de objeto
pode ser feito em tempo de compilao, assim que todas as superclasses forem conhecidas. J em uma linguagem com estrutura de classes aberta, o layout do registro de
objeto precisa ser feito entre o momento em que a estrutura da superclasse conhecida
e o momento em que os ORs so alocados. Se a estrutura de classes for desconhecida
em tempo de compilao, mas no puder mudar em tempo de execuo, essas questes podem ser resolvidas em tempo de ligao ou no incio da execuo. Mas, se a
estrutura de classes puder mudar em tempo de execuo, como em Java ou Smalltalk,
ento o ambiente de runtime precisa estar preparado para ajustar layouts de objeto e
a hierarquia de classes.
j

Em Java, por exemplo, as classes s mudam quando


o carregador de classes executado. Portanto, o
carregador de classes pode disparar o processo de
recriao.

Se as classes mudarem com pouca frequncia, o overhead para ajustar layouts


de registro de objeto pode ser pequeno. O ambiente de runtime, seja um interpretador ou um JIT e um interpretador, pode calcular os layouts de registro de
objeto e construir vetores de mtodo para cada classe afetada quando a estrutura
de classes mudar.
j Se as classes mudarem com frequncia, o compilador ainda precisa calcular os
layouts de registro de objeto e ajust-los. Porm, pode ser mais eficiente para a
implementao usar vetores de mtodo incompletos e pesquisar, ao invs de recriar os vetores de mtodo de classe a cada mudana. (Veja a prxima subseo.)
Como ltima questo, considere o que acontece se a linguagem permitir mudanas
na estrutura de uma classe que tenha objetos j criados. Neste caso, a incluso de um
campo ou um mtodo necessita de visitao a esses objetos, criando-se para eles novos
ORs e conectando estes ORs de volta ao ambiente de runtime de forma transparente.
(Normalmente, este ltimo requisito exige um nvel extra de indireo nas referncias
aos ORs.) Para evitar essas complicaes, a maioria das linguagens probe mudanas
nas classes que j tm objetos criados.

6.3 Espaos de nomes 253

Despachos esttico e dinmico


As estruturas de runtime apresentadas na Figura6.7 sugerem que cada chamada de
mtodo exige uma ou mais operaes load para localizar a implementao do mtodo.
Em uma linguagem com estrutura de classes fechada, o compilador pode evitar este
overhead para a maioria das chamadas. Em C++, por exemplo, o compilador pode
resolver qualquer mtodo para uma implementao concreta em tempo de compilao,
a menos que o mtodo seja declarado como virtual significando, basicamente, que o
programador quer localizar a implementao relativa classe do receptor.
Com um mtodo virtual, o despacho feito por meio do vetor de mtodos apropriado.
O compilador emite cdigo para localizar a implementao do mtodo em tempo de
execuo usando o vetor method do objeto, um processo chamado despacho dinmico.
Porm, se o compilador C++ puder provar que alguma chamada de mtodo virtual
tem uma classe receptora invariante conhecida, pode gerar uma chamada direta, s
vezes chamada despacho esttico.
Linguagens com estruturas de classe abertas podem precisar contar com o despacho
dinmico. Se a estrutura de classes puder mudar em tempo de execuo, o compilador
no poder resolver nomes de mtodo para implementaes; ao invs disso, ele precisa
adiar esse processo para o runtime. As tcnicas usadas para resolver este problema
variam desde reclculo de vetores de mtodos a cada mudana na hierarquia de classes
at resoluo de nomes e busca na hierarquia de classes em tempo de execuo.
j

Se a hierarquia de classes mudar com pouca frequncia, a implementao pode


simplesmente recriar os vetores de mtodos para as classes afetadas aps cada
mudana. Neste esquema, o sistema de runtime precisa percorrer a hierarquia
da superclasse para localizar implementaes de mtodo e criar vetores
de mtodos de subclasse.
j Se a hierarquia de classes mudar com frequncia, o implementador pode escolher
manter vetores de mtodos incompletos em cada classe registrando apenas
os mtodos locais. Neste esquema, a chamada para um mtodo de superclasse
dispara uma busca em runtime na hierarquia de classes para o primeiro mtodo
desse nome.

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.

254 CAPTULO 6 A abstrao de procedimento

Naturalmente, a criao de uma nova entrada pode forar a remoo de alguma


outra entrada da cache. As polticas padro de substituio de cache, como pela
entrada usada menos recentemente ou por rodzio, podem selecionar o mtodo a
ser removido. Caches maiores retm mais informaes, mas exigem mais memriae
podem levar mais tempo para pesquisar. Quando a estrutura de classes muda, a
implementao pode limpar a cache de mtodo para impedir resultados incorretos
em pesquisas futuras.
Para capturar a localidade de tipo em chamadas individuais, algumas implementaes
usam a cache de mtodo em linha, uma nica entrada de cache localizada no local
real da chamada. A cache armazena a classe do receptor e o ponteiro de mtodo
apartir da ltima chamada neste local. Se a classe de receptor atual combinar coma
classe de receptor anterior, a chamada usa o ponteiro do mtodo em cache. Uma
mudana na hierarquia de classes precisa invalidar a cache, seja alterando sua tag
ou sobrescrevendo as tags de classe em cada cache em linha. Se a classe atual no
combinar com a classe em cache, uma pesquisa completa usada, escrevendo seus
resultados na cache em linha.

Qualquer um desses esquemas exige que o sistema de runtime da linguagem retenha


tabelas de pesquisa com nomes de mtodo seja nomes em nvel de fonte ou chaves
de busca derivadas desses nomes. Cada classe precisa de um pequeno dicionrio em
seu OR. A resoluo de nomes em tempo de execuo procura o nome do mtodo
nos dicionrios por meio da hierarquia, de maneira semelhante cadeia de tabelas de
smbolos descrita na Seo 5.5.3.
As implementaes de OOL tentam reduzir o custo do despacho dinmico por meio de
uma de duas estratgias gerais. Podem realizar anlise para provar que uma determinada
chamada de mtodo sempre use um receptor da mesma classe conhecida, quando podem
substituir o despacho dinmico pelo esttico. Para chamadas onde no conseguemdescobrir a classe do receptor, ou onde a classe varia em tempo de execuo, podem manter
em cache os resultados da busca, para melhorar o desempenho. Neste esquema, a busca
consulta uma cache de mtodos antes de consultar a hierarquia de classes. Se a cache de
mtodos tiver o mapeamento para a classe do receptor e o nome do mtodo, a chamada
usa o ponteiro do mtodo em cache e evita a pesquisa.

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

6.4 Comunicao de valores entre procedimentos 255

de forma semelhante. O OR para a divide-se em quatro sees lgicas: o OR para b,


o OR para g, o OR para d e o espao para os campos declarados em a. Os mtodos
declarados em a so acrescentados ao vetor de mtodos para a primeira seo. Os
ponteiros de classe sombra e vetores de mtodos, cujos rtulos aparecem em cinza,
existem para permitir que esses mtodos de superclasse recebam o ambiente que esperam o layout do OR da superclasse correspondente.
A complicao restante envolvida na herana mltipla encontra-se no fato de o ponteiro
do OR ter de ser ajustado quando um mtodo da superclasse chamado usando um dos
ponteiros de classe sombra e vetores de mtodos. A chamada precisa ajustar o ponteiro
pela distncia entre o ponteiro de classe no topo do OR e o ponteiro de classe sombra.
O mecanismo mais simples para realizar este ajuste inserir uma funo trampolim
entre o vetor de mtodos e o mtodo real, que ajusta o ponteiro do OR, chama o mtodo
com todos os parmetros e reajusta o ponteiro do OR no retorno.

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?

6.4 COMUNICAO DE VALORES ENTRE PROCEDIMENTOS


A noo central por trs do conceito de um procedimento a abstrao. O programador
abstrai operaes comuns relativas a um pequeno conjunto de nomes, ou parmetros
formais, e encapsula essas operaes em um procedimento. Para usar o procedimento,
o programador o chama com uma vinculao apropriada de valores, ou parmetros

256 CAPTULO 6 A abstrao de procedimento

reais, para aqueles parmetros formais. O procedimento chamado executado usando


os nomes de parmetros formais para acessar os valores passados como parmetros
reais. Se o programador quiser, o procedimento pode retornar um resultado.

6.4.1 Passagem de parmetros


A vinculao de parmetros mapeia os parmetros reais em um local de chamada aos
formais do procedimento chamado, permitindo que o programador escreva um procedimento sem conhecimento dos contextos em que ele ser chamado. Permite ainda,
que o programador chame o procedimento de muitos contextos distintos sem expor
detalhes da operao interna do procedimento a cada chamador. Assim, a vinculao
de parmetros desempenha um papel crtico em nossa capacidade de escrever cdigo
abstrato, modular.
A maioria das linguagens de programao modernas utiliza uma de duas convenes
para mapear parmetros reais a parmetros formais: vinculao de chamada por
valor e vinculao de chamada por referncia. Essas tcnicas diferem em seus comportamentos. A distino entre elas pode ser melhor explicada entendendo-se suas
implementaes.
Chamada por valor
Conveno pela qual o chamador avalia os parmetros
reais e passa seus valores ao procedimento chamado.
Qualquer modificao de um parmetro passado
por valor no procedimento chamado no visvel no
chamador.

Chamada por valor


Considere o procedimento a seguir, escrito em C, e os vrios locais de chamada que
o invocam:

VINCULAO DE PARMETROS EM CHAMADA POR NOME


Algol introduziu outro mecanismo de vinculao de parmetros, chamada por
nome. Neste tipo de vinculao, uma referncia a um parmetro formal comporta-se
exatamente como se o parmetro real tivesse sido substitudo textualmente
em seu lugar, com o nome devidamente trocado. Essa regra simples pode levar
a um comportamento complexo. Considere o seguinte exemplo artificial
em Algol 60:

6.4 Comunicao de valores entre procedimentos 257

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.

Com a passagem de parmetros com chamada por valor, como em C, o chamador


copia o valor de um parmetro real no local apropriado para o parmetro formal
correspondente um registrador ou um slot de parmetro no AR do procedimento
chamado. Somente um nome se refere a esse valor o nome do parmetro formal. Seu
valor uma condio inicial, determinada avaliando-se o parmetro real no momento
da chamada. Se o procedimento chamado mudar seu valor, esta mudana ser visvel
dentro do procedimento chamado, mas no no chamador.
As trs chamadas produzem os seguintes resultados quando invocadas usando a vinculao de parmetro com chamada por valor:

Chamada
por valor

in

out

in

out

Valor de
retorno

fee(2,3)

fee(a, b)

fee(a,a)

Com a chamada por valor, a vinculao simples e intuitiva.


Uma variao na vinculao de chamada por valor a vinculao de chamada por
valor-resultado. No esquema valor-resultado, os valores dos parmetros formais so
copiados de volta para os parmetros reais correspondentes como parte do processo
de retornar o controle do procedimento chamado ao chamador. A linguagem de programao Ada inclui parmetros de valor-resultado. O mecanismo valor-resultado
tambm satisfaz as regras da definio da linguagem FORTRAN 77.

Chamada por referncia


Com a passagem de parmetros do tipo chamada por referncia, o chamador armazena
um ponteiro no slot do AR para cada parmetro. Se o parmetro real for uma varivel,
armazena o endereo da varivel na memria; se for uma expresso, o chamador avalia
a expresso, armazena o resultado na rea de dados local do seu prprio AR e ento
armazena um ponteiro para esse resultado no slot de parmetro apropriado no AR
do procedimento chamado. As constantes devem ser tratadas como expresses para
evitar qualquer possibilidade de o procedimento chamado mudar seu valor. Algumas

Chamada por referncia


Conveno na qual o compilador passa um endereo
para o parmetro formal ao procedimento chamado.
Se o parmetro real for uma varivel (ao invs de uma
expresso), ento a alterao do valor do parmetro
formal tambm altera o valor do parmetro real.

258 CAPTULO 6 A abstrao de procedimento

linguagens probem a passagem de expresses como parmetros reais para parmetros


formais com chamada por referncia.
Dentro do procedimento chamado, cada referncia a um parmetro formal com chamada por referncia precisa de um nvel de indireo extra. A chamada por referncia
difere da chamada por valor de duas maneiras crticas. Primeiro, qualquer redefinio
de um parmetro formal por referncia refletida no parmetro real correspondente. Segundo, qualquer parmetro formal por referncia poderia estar ligado a uma
varivel que acessvel por outro nome dentro do procedimento chamado. Quando
isto acontece, dizemos que os nomes so pseudnimos (aliases), pois referem-se ao
mesmo local de armazenamento. A criao de pseudnimos pode criar comportamento
contraintuitivo.
Considere o exemplo anterior, reescrito em PL/I, que usa a vinculao de parmetros
com chamadas por referncia.

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.

Com a vinculao de parmetros com chamadas por referncia, o exemplo produz


resultados diferentes. A primeira chamada simples. A segunda chamada redefine
tanto a quanto b; essas mudanas seriam visveis no chamador. A terceira chamada faz
com que x e y se refiram ao mesmo local e, assim, ao mesmo valor. Este alias muda o
comportamento de fee. A primeira atribuio d o valor 4 para a. A segunda, ento,
d o valor 8 para a, e fee retorna 8, onde fee(2,2) retornaria 6.

Chamada
por valor

Valor de
retorno

fee(2,3)

in
-

out
-

in
-

out
-

fee(a, b)

fee(a,a)

Espao para os parmetros


O tamanho da representao para um parmetro tem impacto sobre o custo das chamadas de procedimento. Os valores escalares, como variveis e ponteiros, so armazenados
em registradores ou na rea de parmetros do AR do procedimento chamado. Com os
parmetros com chamada por valor, o valor real armazenado; com parmetros com
chamada por referncia, o endereo do parmetro armazenado. De qualquer forma,
o custo por parmetro pequeno.
Valores grandes, como arrays, registros ou estruturas, impem um problema para
a chamada por valor. Se a linguagem exigir que grandes valores sejam copiados, o
overhead de copi-los para a rea de parmetros do procedimento chamado aumentar
um custo significativo chamada de procedimento. (Neste caso, o programador pode
querer modelar a chamada por referncia e passar um ponteiro para o objeto, ao invs

6.4 Comunicao de valores entre procedimentos 259

do objeto.) Algumas linguagens permitem que a implementao passe esses objetos


por referncia. Outras incluem provises que permitem ao programador especificar que
a passagem de um parmetro em particular por referncia aceitvel; por exemplo,
o atributo const em C garante ao compilador que um parmetro com esse atributo
no ser modificado.

6.4.2 Retorno de valores


Para retornar um valor de uma funo, o compilador precisa reservar espao para isto.
Como o valor de retorno, por definio, usado depois que o procedimento chamado
termina, ele precisa de espao de armazenamento fora do AR do procedimento chamado. Se o construtor de compiladores puder garantir que este valor tem um tamanho fixo
pequeno, ento pode armazen-lo no AR do chamador ou em um registrador designado.

Com parmetros com chamada por valor, as convenes


de ligao normalmente designam o registrador
reservado para o primeiro parmetro como o registrador que armazena o valor de retorno.

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.

6.4.3 Estabelecendo a endereabilidade


Como parte da conveno de ligao, o compilador precisa garantir que cada procedimento possa gerar um endereo para cada varivel que precisa referenciar. Em uma
ALL, um procedimento pode se referir a variveis globais, variveis locais e qualquer
varivel declarada em um escopo lxico que o englobe. Em geral, o clculo de endereo
consiste em duas partes: encontrar o endereo de base da rea de dados apropriada
para o escopo que contm o valor, e encontrar o deslocamento correto dentro desta
rea de dados. O problema de encontrar endereos de base divide-se em dois casos:
reas de dados com endereos de base estticos e aquelas cujo endereo no pode ser
conhecido antes da execuo.

Variveis com endereos de base estticos


Os compiladores organizam reas de dados globais e reas de dados estticas de modo
a ter endereos de base estticos. A estratgia para gerar um endereo para tal varivel
simples: calcular o endereo de base da rea de dados em um registrador e somar

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.

260 CAPTULO 6 A abstrao de procedimento

seu deslocamento ao endereo de base. O IR do compilador normalmente incluir os


modos de endereo para representar este clculo; por exemplo, em ILOC, loadAI
representa um modo registrador+deslocamento imediato, e loadAO, um modo
registrador+registrador.
Para gerar o endereo em tempo de execuo de um endereo de base esttico, o
compilador conecta um rtulo simblico, em nvel de assembly, rea de dados.
Dependendo do conjunto de instrues da mquina-alvo, esse rtulo pode ser usado
em uma operao de load imediato ou para inicializar um local conhecido, caso em
que pode ser movido para um registrador com uma operao load padro.
Deformao de nome
O processo de construir uma string exclusiva a partir de
um nome na linguagem-fonte chamado deformao
de nome.
Se &fee. for muito grande para um load imediato,
o compilador pode ter que usar vrias operaes para
carregar o endereo.

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.

Variveis com endereos de base dinmicos


Conforme descrevemos na Seo 6.3.2, as variveis locais declaradas dentro de um
procedimento em geral so armazenadas no AR do procedimento. Assim, tm endereos
de base dinmicos. Para acessar esses valores, o compilador precisa de um mecanismo
para encontrar os endereos de diversos ARs. Felizmente, as regras de escopo lxico
limitam o conjunto de ARs que podem ser acessados a partir de qualquer ponto no
cdigo ao AR atual e aos ARs dos procedimentos que o englobam lexicamente.

Varivel local do procedimento atual


O acesso a uma varivel local do procedimento atual trivial. Seu endereo de base
simplesmente o do AR atual, que armazenado no ARP. Assim, o compilador pode emitir
cdigo que acrescenta seu deslocamento ao ARP e usa o resultado como o endereo
dovalor. (Esse deslocamento o mesmo valor do deslocamento na coordenada estticado
valor.) Em ILOC, o compilador pode usar um loadAI (uma operao endereo+deslocamento imediato) ou loadAO (uma operao endereo+deslocamento). A
maioria dos processadores fornece suporte eficiente para essas operaes comuns.

6.4 Comunicao de valores entre procedimentos 261

Em alguns casos, um valor no armazenado a um deslocamento constante a partir


do ARP. O valor poderia residir em um registrador, caso em que loads e stores no so
necessrios. Se a varivel tiver um tamanho imprevisvel ou mutvel, o compilador
o armazenar em uma rea reservada para objetos de tamanho varivel, seja no final
doAR ou em um heap. Neste caso, o compilador pode reservar espao no AR para um
ponteiro para o local real da varivel e gerar um load adicional para acessar a varivel.

Variveis locais de outros procedimentos


Para acessar uma varivel local de algum escopo lxico que a engloba, o compilador
deve se preparar para a construo de estruturas de dados de runtime que mapeiem uma
coordenada esttica, produzida usando-se uma tabela de smbolos com escopo lxico
no parser, de um endereo de runtime.
Por exemplo, suponha que o procedimento fee, de nvel lxico m, referencia a varivel
a do ancestral lxico de fee, fie, de nvel n. O parser converte essa referncia em uma
coordenada esttica n, d, onde d o deslocamento de a no AR para fie. O compilador
pode calcular o nmero de nveis lxicos entre fee e fie como mn. (A coordenada
mn, d s vezes chamada coordenada de distncia esttica da referncia.)
O compilador precisa de um mecanismo para converter n, d em um endereo de
runtime. Em geral, este esquema usar estruturas de dados de runtime para encontrar
o ARP do procedimento de nvel n mais recente, e usar este ARP como o endereo de
base em seu clculo. Ele soma o deslocamento d a esse endereo de base para produzir
um endereo de runtime para o valor cuja coordenada esttica n, d. A complicao
est na criao e travessia das estruturas de dados de runtime para encontrar o endereo
de base. As subsees seguintes examinam dois mtodos comuns: uso de links de
acesso e de um display global.

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

FIGURA 6.8 Usando links de acesso.

262 CAPTULO 6 A abstrao de procedimento

varivel local de outro procedimento que seja visvel ao atual armazenado em um


AR na cadeia de links de acesso que comea no procedimento atual.
Para acessar um valor n, d a partir de um procedimento de nvel m, o compilador
emite cdigo para percorrer a cadeia de links e encontrar o ARP de nvel n. Em
seguida, emite um load que usa o AR de nvel n e d. Para melhor entender, considere
o programa representado pela Figura6.8. Suponha que m seja 2 e que o link de
acesso seja armazenado em um deslocamento de 4 a partir do ARP. A tabela a
seguir mostra um conjunto de trs coordenadas estticas diferentes ao lado do
cdigo ILOC que um compilador poderia gerar para elas. Cada sequncia deixa
o resultado em r2.

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

6.4 Comunicao de valores entre procedimentos 263

FIGURA 6.9 Usando o display global.

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

264 CAPTULO 6 A abstrao de procedimento

chama (direta ou indiretamente), onde q est aninhado dentro do escopo de p. Assim,


qualquer p que no chame um procedimento aninhado dentro de si mesmo no precisa
atualizar o display, o que elimina todas as atualizaes nos procedimentos folha, bem
como muitas outras atualizaes.

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?

6.5 LIGAES PADRONIZADAS


A ligao de procedimentos um contrato entre o compilador, o sistema operacional
e a mquina-alvo que claramente divide a responsabilidade pela nomeao, alocao
de recursos, endereabilidade e proteo. A ligao de procedimentos garante a interoperabilidade de procedimentos entre o cdigo do usurio, conforme traduzido pelo
compilador, e o cdigo de outras fontes, incluindo bibliotecas do sistema, bibliotecas de
aplicao, e cdigo escrito em outras linguagens de programao. Normalmente, todos
os compiladores para determinada combinao de mquina-alvo e sistema operacional
utilizam a mesma ligao ao mximo possvel.
A conveno de ligao isola cada procedimento dos diferentes ambientes encontrados nos locais de chamada que o chamam. Suponha que o procedimento p tem um
parmetro inteiro x. Diferentes chamadas a p poderiam vincular x a uma varivel local
armazenada no frame de pilha do chamador, a uma varivel global, a um elemento
de algum array esttico ou ao resultado da avaliao de uma expresso inteira,
como y+2. Uma vez que a conveno de ligao especifica como avaliar o parmetro
real e armazenar seu valor, alm de como acessar x no procedimento chamado, o
compilador pode gerar cdigo para o procedimento chamado que ignora as diferenas

6.5 Ligaes padronizadas 265

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.

FIGURA 6.10 Uma ligao de procedimento padro.


j

Sequncia de pr-chamada. Inicia o processo de construo do ambiente


do procedimento chamado. Avalia os parmetros reais, determina o endereo
de retorno e, se necessrio, o endereo do espao reservado para manter um
valor de retorno. Se um parmetro real de chamada por referncia for alocado
a um registrador, esta sequncia precisa armazen-lo no AR do chamador,
de modo que possa passar o endereo desse local ao procedimento chamado.
j Muitos dos valores mostrados nos diagramas do AR podem ser passados ao
procedimento chamado em registradores. O endereo de retorno, um endereo
para o valor de retorno e o AR do chamador so candidatos bvios.
Os primeiros k parmetros reais tambm podem ser passados nos registradores
um valor tpico para k poderia ser 4. Se a chamada tiver mais de k parmetros,
os parmetros reais restantes precisam ser armazenados no AR do procedimento
chamado ou do chamador.
j Sequncia de ps-retorno. Desfaz as aes da sequncia de pr-chamada. Ela
deve restaurar quaisquer parmetros de chamada-por-referncia e chamada-por-valor-resultado que precisam ser retornados aos registradores, e restaura quaisquer registradores da rea de salvamento de registradores salvos pelo chamador,
e, ainda, pode ter que desalocar todo ou parte do AR do procedimento chamado.
j Sequncia de prlogo. O prlogo para um procedimento completa a tarefa
de criar o ambiente de runtime do procedimento chamado. Ele pode
criar espao no AR do procedimento chamado para armazenar alguns dos valores
passados pelo chamador em registradores. Ele deve criar espao para variveis
locais e inicializ-las, conforme a necessidade. Se o procedimento chamado
referenciar uma rea de dados estticos especfica do procedimento,
o prlogo pode ter que carregar o rtulo para essa rea de dados
em um registrador.

266 CAPTULO 6 A abstrao de procedimento

Sequncia de eplogo. O eplogo para um procedimento inicia o processo


de desmontar o ambiente do procedimento chamado e reconstruir o ambiente
do chamador. Ele pode participar na desalocao do AR do procedimento
chamado. Se o procedimento retornar um valor, o eplogo poder ser responsvel
por armazenar o valor no endereo especificado pelo chamador. (Alternativamente, o cdigo gerado para uma instruo de retorno pode realizar esta tarefa.)
Finalmente, o eplogo restaura o ARP do chamador e salta para o endereo
de retorno.

Esse framework fornece orientao geral para a criao de uma conveno de


ligao. Muitas das tarefas podem ser deslocadas entre os procedimentos chamador
e chamado. Em geral, mover o trabalho para o cdigo de prlogo e de eplogo
produz um cdigo mais compacto. As sequncias de pr-chamada e de ps-retorno
so geradas para cada chamada, enquanto o prlogo e o eplogo ocorrem uma vez
por procedimento. Se os procedimentos so chamados mais de uma vez em mdia,
ento existem menos sequncias de prlogo e eplogo do que de pr-chamada e
ps-retorno.

MAIS SOBRE TEMPO


Em um sistema tpico, a conveno de ligao negociada entre os implementadores
do compilador e os implementadores do sistema operacional em um estgio inicialdo
desenvolvimento do sistema. Assim, questes como a distino entre registradoresde
salvamentos do chamador e do chamado so decididas em tempo de projeto.
Quando o compilador executado, ele precisa emitir sequncias de prlogo e eplogo
de procedimento para cada procedimento, junto com sequncias de pr-chamada
e ps-retorno para cada local de chamada. Esse cdigo executado em tempo de
execuo. Assim, o compilador no consegue saber o endereo de retorno que deve
armazenar no AR do procedimento chamado. (E nem pode saber, em geral,
o endereo desse AR.) Porm, pode incluir um mecanismo que gerar o endereo de
retorno em tempo de ligao (usando um rtulo realocvel de linguagem assembly)
ou em tempo de execuo (usando algum deslocamento a partir do contador de
programa) e armazen-lo no local apropriado no AR do procedimento chamado.
De modo semelhante, em um sistema que usa um display para fornecer
endereabilidade para variveis locais de outros procedimentos, o compilador no
pode saber os endereos em tempo de execuo do display ou do AR. Apesar disso,
emite cdigo para manter o display. O mecanismo que consegue isto exige duas
informaes: o nvel de aninhamento lxico do procedimento atual e o endereo
do display global. O primeiro conhecido em tempo de compilao; o outro pode
ser determinado em tempo de ligao usando um rtulo realocvel de linguagem
assembly. Assim, o prlogo pode simplesmente carregar a entrada de display
atual para o nvel do procedimento (usando um loadAO a partir do endereo de
display) e armazen-la no AR (usando um storeAO relativo ao ARP). Finalmente, o
compilador armazena o endereo do novo AR no slot do display para o nvel lxico do
procedimento.

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

6.5 Ligaes padronizadas 267

registradores, pode evitar o salvamento de valores de registradores que no utiliza;


novamente, este conhecimento pode resultar em menos valores salvos.
Em geral, o compilador pode usar seu conhecimento do procedimento que est sendo
compilado para otimizar o comportamento de salvamento de registradores. Para qualquer diviso especfica de trabalho entre chamador e chamado, podemos construir
programas para os quais essa diviso funcione bem e programas para os quais no
funcione bem. A maioria dos sistemas modernos segue um caminho intermedirio e
designa uma parte do conjunto de registradores para serem salvos pelo chamadore
uma parte para serem salvos pelo chamado. Na prtica, isto parece funcionar bem,
pois encoraja o compilador a colocar valores de vida longa em registradores salvos
pelo procedimento chamado, onde sero armazenados somente se o procedimento
chamado realmente precisar do registrador, e estimula o compilador a colocar valores
de vida curta em registradores salvos pelo chamador, podendo assim evitar salv-los
em uma chamada.

Alocao do registro de ativao


No caso mais geral, tanto o chamador quanto o procedimento chamado precisam
acessar o AR do chamado. Infelizmente, o chamador no tem como saber, em geral, o
tamanho que o AR do procedimento chamado precisa ter (a menos que o compilador
e o linker possam planejar para que o linker cole os valores apropriados em cada local
de chamada).
Com ARs alocados em pilha, um meio-termo possvel. Como a alocao consiste em
incrementar o ponteiro de topo da pilha, o chamador pode iniciar a criao do AR do
procedimento chamado removendo o topo da pilha e armazenando valores nos locais
apropriados. Quando o controle passa para o procedimento chamado, pode estender o
AR parcialmente criado, incrementando o topo da pilha para criar espao para dados
locais. A sequncia de ps-retorno pode ento reiniciar o ponteiro do topo da pilha,
realizando a desalocao inteira em uma nica etapa.
Com ARs alocados em heap, pode no ser possvel estender o AR do procedimento
chamado de forma incremental. Nesta situao, o construtor de compiladores tem
duas escolhas.
1. O compilador pode passar os valores que precisa armazenar no AR do procedimento chamado em registradores; a sequncia de prlogo pode ento alocar um AR de
tamanho apropriado e armazenar os valores passados nele. Neste esquema, o construtor de compiladores reduz o nmero de valores que o chamador passa ao chamado,
organizando-se para armazenar os valores de parmetro no AR do chamador. O acesso
a esses parmetros usa a cpia do ARP do chamador que est armazenada no AR do
procedimento chamado.
2. O construtor de compiladores pode dividir o AR em mltiplas partes distintas, uma
para manter o parmetro e as informaes de controle geradas pelo chamador e as outraspara manter o espao necessrio pelo procedimento chamado, mas desconhecido
do chamador. O chamador no pode, em geral, saber o tamanho da rea de dados local.
O compilador pode armazenar esse nmero para cada procedimento chamado usando
rtulos deformados; o chamador pode, ento, carregar o valor e us-lo. Alternativamente,
o procedimento chamado pode alocar sua prpria rea de dados local e manter seu
endereo de base em um registrador ou em um slot no AR criado pelo chamador.
ARS alocados em heap aumentam o custo de overhead de uma chamada de procedimento. O cuidado na implementao da sequncia de chamada e do alocador pode
reduzir esses custos.

Registradores salvos pelo chamado


Os registradores designados para salvamento pelo
procedimento chamado so registradores salvos pelo
chamado.

268 CAPTULO 6 A abstrao de procedimento

Gerenciamento de displays e links de acesso


Qualquer mecanismo para gerenciar acesso no local exige algum trabalho na sequncia
de chamada. Usando um display, a sequncia de prlogo atualiza o registro do display
para o seu prprio nvel e a sequncia de eplogo o restaura. Se o procedimento nunca
chama um procedimento mais profundamente aninhado, ele pode pular esta etapa.
Usando links de acesso, a sequncia de pr-chamada precisa localizar o primeiro
link de acesso apropriado para o procedimento chamado. A quantidade de trabalho
varia com a diferena no nvel lxico entre o procedimento chamador e o chamado.
Desde que o chamado seja conhecido em tempo de compilao, qualquer esquema
razoavelmente eficiente, mas, se for desconhecido (se for, por exemplo, um parmetro
com valor de funo), o compilador pode precisar emitir cdigo especial para realizar
as etapas apropriadas.

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?

6.6 TPICOS AVANADOS


O compilador precisa organizar a alocao de espao para manter as diversas estruturas
de runtime discutidas na Seo 6.3. Para algumas linguagens, essas estruturas tm
tempos de vida que no se encaixam bem na disciplina primeiro a entrar, primeiro a
sair de uma pilha. Nesses casos, a implementao da linguagem aloca espao no heap
de runtime regio da memria separada para tais objetos gerenciada por rotinas de
uma biblioteca de suporte de runtime. O compilador tambm precisa se organizar para
o armazenamento de outros objetos que tm tempos de vida no relacionados ao fluxo
de controle, como muitas listas em um programa Scheme ou muitos objetos em Java.
Vamos considerar uma interface simples para o heap, a saber, uma rotina
allocate(size), e uma rotina free(address). A rotina allocate usa um

6.6 Tpicos avanados 269

argumento inteiro size e retorna o endereo de um bloco de espao no heap que


contm pelo menos size bytes. A rotina free toma o endereo de um bloco de espao alocado anteriormente no heap e o retorna ao pool de espao livre. As questes
crticas que surgem no projeto de algoritmos para gerenciar o heap explicitamente so
as velocidades de allocate e free e em que extenso o pool de espao livre se
torna fragmentado em pequenos blocos.
Esta seo esboa os algoritmos envolvidos na alocao e na reivindicao de espao em
um heap de runtime. A Seo6.6.1 focaliza as tcnicas para o gerenciamento explcito
do heap, e descreve como implementar free para cada um dos esquemas. A Seo6.6.2
examina a desalocao implcita tcnicas que evitam a necessidade de free.

6.6.1 Gerenciamento explcito de heap


A maioria das implementaes de linguagens inclui um sistema de runtime que oferece
funes de suporte para o cdigo gerado pelo compilador. Este sistema normalmente
inclui proviso para o gerenciamento de um heap em runtime. As rotinas reais que
implementam o heap podem ser especficas da linguagem, como em um interpretador
Scheme ou em uma mquina virtual Java, ou podem fazer parte do sistema operacional
subjacente, como nas implementaes POSIX de malloc e free.
Embora muitas tcnicas tenham sido propostas para implementar allocate e
free, a maioria compartilha estratgias e ideias comuns. Esta seo explora uma
estratgia simples, alocao de primeiro encaixe, que expe a maioria das questes, e, a seguir, mostra como este tipo de estratgia usada para implementar um
alocador moderno.

Alocao de primeiro encaixe


O objetivo de um alocador de primeiro encaixe alocar e liberar espao no heap
rapidamente. O primeiro encaixe enfatiza mais a velocidade do que a utilizao de
memria. Cada bloco no heap tem um campo oculto que armazena seu tamanho. Em
geral, o campo de tamanho est localizado na palavra anterior ao endereo retornado
por allocate, como mostra a Figura6.11a. Os blocos disponveis para alocao
residem em uma lista chamada lista livre. Alm do campo de tamanho obrigatrio, os
blocos nesta lista possuem campos adicionais, como mostra a Figura6.11b. Cada bloco
livre tem um ponteiro para o prximo bloco na lista livre (definido como nulo no ltimo
bloco) e um ponteiro para o prprio bloco na ltima palavra do bloco. Para inicializar o
heap, o alocador cria uma lista livre que contm um nico bloco grande e no alocado.
Uma chamada allocate(k) causa a seguinte sequncia de eventos: a rotina
allocate percorre a lista livre at descobrir um bloco com tamanho maior ou igual a k
mais uma palavra para o campo size. Suponha que ele encontre um bloco apropriado,
bi. Neste caso, remove-o da lista livre. Se bi for maior que o necessrio, allocate cria
um novo bloco livre a partir do espao em excesso no final de bi e coloca esse bloco
na lista livre. A rotina allocate retorna um ponteiro para a segunda palavra de bi.
Se allocate no encontrar um bloco grande o suficiente, ele tenta estender o heap.Se
tiver sucesso nesta extenso, retorna um bloco de tamanho apropriado a partir dessa
parte recm-alocada do heap. Se a extenso do heap falhar, allocate relata a falha
(normalmente retornando um ponteiro nulo).
Para desalocar um bloco, o programa chama free com o endereo do bloco, bj. A
implementao mais simples de free acrescenta bj ao incio da lista livre e retorna.
Isto produz uma rotina free rpida. Infelizmente, isto leva a um alocador que, com
o tempo, fragmenta a memria em pequenos blocos.

270 CAPTULO 6 A abstrao de procedimento

FIGURA 6.11 Blocos em um alocador de primeiro encaixe.

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.

ALOCAO BASEADA EM ARENA


Dentro do prprio compilador, o construtor de compiladores pode achar lucrativo
usar um alocador especializado. Os compiladores tm atividades orientadas por fases,
o que serve muito bem para um esquema de alocao baseado em arena (uma rea
projetada para acomodar um grande nmero de objetos).
Com um alocador baseado em arena, o programa cria uma arena no incio de uma
atividade. E a usa para manter objetos alocados que esto relacionados ao seu uso. As
chamadas para alocar objetos na arena so satisfeitas em um padro tipo pilha; uma
alocao envolve incrementar um ponteiro para o marcador de mximo j alocado
daarena e retornar um ponteiro para o bloco recm-alocado. Nenhuma chamada
usada para desalocar objetos individuais; estes so liberados quando a arena que os
contm for desalocada.
O alocador baseado em arena um meio-termo entre os alocadores tradicionais e os
de coleta de lixo. Com este tipo de alocador, as chamada a allocate podem ser
leves (como no alocador moderno). Nenhuma chamada de liberao necessria;
o programa libera a arena inteira em uma nica chamada quando terminar a atividade
para a qual a arena foi criada.

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

6.6 Tpicos avanados 271

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.

Alocadores de pools mltiplos


Alocadores modernos so derivados da alocao de primeiro encaixe, mas simplificados
por algumas observaes sobre o comportamento dos programas. medida que os
tamanhos de memria cresceram no incio da dcada de 1980, tornou-se razovel desperdiar algum espao se isso levasse alocao mais rpida. Ao mesmo tempo, os
estudos de comportamento de programa sugeriram que programas reais frequentemente
alocam memria em alguns tamanhos comuns, e com pouca frequncia em tamanhos
grandes ou incomuns.
Os alocadores modernos usam pools de memria separados para vrios tamanhos
comuns. Normalmente, tamanhos selecionados so potncias de dois, comeando com
um pequeno tamanho de bloco (como 16 bytes) e aumentando at o de uma pgina
da memria virtual (normalmente, 4096 ou 8192 bytes). Cada pool tem apenas um
tamanho de bloco, de modo que allocate pode retornar o primeiro bloco na lista
livre apropriada, e free simplesmente acrescentar o bloco ao incio da lista livre
apropriada. Para solicitaes maiores do que uma pgina, um alocador do primeiro
ajuste separado utilizado. Alocadores baseados nessas ideias so rpidos, e funcionam
particularmente bem para alocao em heap de registros de ativao.
Essas mudanas simplificam allocate e free. A rotina allocate precisa verificar uma lista livre vazia e acrescentar uma nova pgina lista livre, se estiver vazia.
A rotina free insere o bloco liberado no incio da lista livre para o seu tamanho.
Uma implementao cuidadosa poderia determinar o tamanho de um bloco liberado
verificando seu endereo em relao aos segmentos de memria alocados para cada
pool. Esquemas alternativos incluem o uso de um campo de tamanho como antes e, seo
alocador colocar todo o armazenamento de uma pgina em um nico pool, armazenar
o tamanho dos blocos em uma pgina na primeira palavra da pgina.

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.

272 CAPTULO 6 A abstrao de procedimento

Por um modesto overhead adicional, o software de gerenciamento de heap pode oferecer


ajuda adicional. Encadeando blocos alocados, o alocador pode criar um ambiente de
ferramentas de depurao de alocao de memria. Uma ferramenta de estado de sistema
(snapshot) pode percorrer a lista de blocos alocados. A marcao de blocos pelo local de
chamada que os criou permite que a ferramenta exponha vazamentos de memria. Suasinformaes de tempo (timestamping) permitem que a ferramenta fornea ao programador
informaes detalhadas sobre o uso da memria. Ferramentas deste tipo podem oferecer
uma ajuda valiosa na localizao de blocos que nunca so desalocados.
Coleta de lixo
Desalocao implcita de objetos que residem no heap
de runtime.

6.6.2 Desalocao implcita


Muitas linguagens de programao admitem a desalocao implcita de objetos de heap.
A implementao desaloca objetos da memria automaticamente quando no esto mais
em uso. Isto exige algum cuidado na implementao tanto do alocador como do cdigo
compilado. Para realizar a desalocao implcita, ou coleta de lixo, o compilador e o sistemade runtime devem incluir um mecanismo para determinar quando um objeto no
mais de interesse, ou est morto, e um mecanismo para reivindicar e reciclar o espao morto.
O trabalho associado coleta de lixo pode ser realizado de forma incremental, por
instrues individuais, ou como uma tarefa em lote (batch), executada por demanda,
quando o pool de espao livre esgotado. A contagem de referncia um modo clssico
de realizar a coleta de lixo incremental. A coleta marcar-varrer um mtodo clssico de
realizar a coleta orientada por lotes.

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

6.6 Tpicos avanados 273

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.

Identificao de dados vivos


Alocadores de coleta descobrem objetos vivos usando um algoritmo de marcao.
O coletor precisa de um bit para cada objeto no heap, chamado bit de marcao,
que pode ser armazenado no cabealho do objeto, junto com informaes de tag
usadas para registrar locais de ponteiro ou tamanho de objeto. Como alternativa, o
coletor pode criar um denso mapa de bits para o heap quando necessrio. A etapa
inicial apaga todos os bits de marcao e monta uma lista de trabalho que contm
todos os ponteiros armazenados nos registradores e em variveis acessveis aos
procedimentos atuais ou pendentes. A segunda fase do algoritmo caminha para a
frente a partir desses ponteiros e marca cada objeto que alcanvel a partir desse
conjunto de ponteiros visveis.

Se o coletor no puder liberar qualquer espao, ento


precisa solicitar espao adicional do sistema. Se no
houver algum disponvel, a alocao falha.

274 CAPTULO 6 A abstrao de procedimento

FIGURA 6.12 Um algoritmo de marcao simples.

A Figura6.12 apresenta um esboo de alto nvel de um algoritmo de marcao. Ele


uma computao simples de ponto fixo que termina porque o heap finito e as
marcaes impedem que um ponteiro contido no heap entre na worklist mais
de uma vez. O custo da marcao , na pior das hipteses, proporcional ao nmero de
ponteiros contidos nas variveis de programa e temporrios mais o tamanho do heap.
O algoritmo de marcao pode ser preciso ou conservador. A diferena est em como ele
determina que um valor de dado especfico um ponteiro na linha final do lao while.
j

Em um coletor preciso, o compilador e o sistema de runtime conhecem o tipo


e o layout de cada objeto. Essa informao pode ser registrada nos cabealhos
de objeto, ou ser conhecida implicitamente a partir do sistema de tipos.
De qualquer forma, a fase de marcao segue apenas ponteiros reais.
j Em uma fase de marcao conservadora, o compilador e o sistema de runtime
podem ser inseguros a respeito do tipo e do layout de alguns objetos. Assim,
quando um objeto marcado, o sistema considera cada campo que pode
ser um possvel ponteiro. Se seu valor puder ser um ponteiro, tratado como
um ponteiro. Qualquer valor que no represente um endereo alinhado
em palavra pode ser excludo, assim como os valores que ficam fora
dos limites conhecidos do heap.
Coletores conservadores tm limitaes; falham ao reivindicar alguns objetos que um
coletor preciso encontraria. Apesar disso, tm sido aperfeioados com sucesso em implementaes para linguagens, como C, que normalmente no admitem coleta de lixo.
Quando o algoritmo de marcao termina, qualquer objeto no marcado deve ser
inalcanvel a partir do programa. Assim, a segunda fase do coletor pode tratar esse
objeto como morto. Alguns objetos marcados como vivos tambm podem estar mortos.Porm, o coletor permite que sobrevivam, pois no pode provar que estomortos.
Quando a segunda fase percorre o heap para coletar o lixo, ela pode reiniciar os campos
de marcao para desmarcado. Isso permite que o coletor evite a travessia inicial do
heap na fase de marcao.

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.

6.6 Tpicos avanados 275

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.

276 CAPTULO 6 A abstrao de procedimento

Em aplicaes que no podem tolerar um overhead imprevisvel, como controladores


em tempo real, o sistema de runtime deve incrementar o processo, como feito pelo
esquema de contagem de referncia amortizado. Esses coletores so denominados
coletores de tempo real.

6.7 RESUMO E PERSPECTIVA


A razo principal para ir alm da linguagem assembly oferecer um modelo de programao mais abstrato, e, assim, elevar a produtividade do programador e a inteligibilidade dos programas. Cada abstrao que uma linguagem de programao admite
precisa de uma traduo para o conjunto de instrues da mquina-alvo. Este captulo
explorou as tcnicas comumente utilizadas para traduzir algumas dessas abstraes.
A programao procedimental foi inventada cedo na histria da programao. Alguns
dos primeiros procedimentos foram rotinas de depurao escritas para os primeiros
computadores; a disponibilidade dessas rotinas previamente escritas permitiu que os
programadores entendessem o estado de runtime de um programa com erro. Sem tais
rotinas, as tarefas que agora aceitamos sem questionar, como examinar o contedo
de uma varivel ou solicitar o rastreamento de uma pilha de chamadas, exigiam que
o programador entrasse com longas sequncias de linguagem de mquina sem erro.
A introduo do escopo lxico em linguagens como Algol 60 influenciou o projeto
de linguagens durante dcadas. A maioria das linguagens de programao modernas
aproveita parte da filosofia do Algol na nomeao e na endereabilidade. Tcnicas
desenvolvidas para dar suporte ao escopo lxico, como links de acesso e displays,
reduziram o custo de runtime dessa abstrao. Essas tcnicas ainda so usadas hoje.
As linguagens orientadas a objeto tomam os conceitos de escopo das ALLs e os
reorientam de maneiras direcionadas a dados. O compilador para uma linguagem
orientada a objeto utiliza estruturas em tempo de compilao e em tempo de execuo
inventadas para o escopo lxico a fim de implementar a disciplina de nomeao imposta
pela hierarquia de herana de um programa especfico.
As linguagens modernas acrescentaram alguns novos caprichos. Transformando
procedimentos em objetos de primeira classe, linguagens como Scheme criaram novos
paradigmas de fluxo de controle, que exigem variaes nas tcnicas de implementao
tradicionais por exemplo, alocao de registros de ativao em heap. De modo
semelhante, a aceitao cada vez maior da desalocao implcita exige ocasionalmente
tratamento conservador de um ponteiro. Se o compilador puder exercer um pouco mais
de cuidado e liberar o programador de sequer desalocar o armazenamento novamente,
isto parece ser uma boa escolha. (Geraes de experincias sugerem que os programadores no so eficazes na liberao de todo o armazenamento que alocam. Eles
tambm liberam objetos aos quais retm ponteiros.)
medida que surgem novos paradigmas de programao, sero introduzidas novas
abstraes que exigem pensamento e implementao cuidadosos. Estudando as tcnicas
bem-sucedidas do passado e entendendo as restries e os custos envolvidos nas
implementaes reais, os construtores de compilador desenvolvero estratgias que
diminuem a penalidade de runtime de usar nveis mais altos de abstrao.

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

6.7 Resumo e perspectiva 277

linguagem. Esses documentos so uma parte necessria da biblioteca de um construtor


de compiladores.
Os procedimentos apareceram nas linguagens de alto nvel mais antigas ou seja,
linguagens que eram mais abstratas do que a linguagem assembly. FORTRAN [27] e
Algol 60 [273] tinham procedimentos com a maior parte dos recursos encontrados nas
linguagens modernas. As linguagens orientadas a objeto apareceram no final da dcada
de 1960 com SIMULA 67 [278], seguida por Smalltalk 72 [233].
O escopo lxico foi introduzido em Algol 60, e persiste at os dias atuais. Os primeiros
compiladores Algol introduziram a maior parte dos mecanismos de suporte descritos
neste captulo, incluindo registros de ativao, links de acesso e tcnicas de passagem de parmetros. Grande parte do material das Sees 6.3 a 6.5 estava presente
nesses primeiros sistemas [293]. As otimizaes apareceram rapidamente, como
armazenamento para um escopo de bloco no registro de ativao do procedimento que
o contm. As convenes de ligao do IBM 370 reconheceram a diferena entre os
procedimentosfolha e outros; elas evitavam alocar uma rea de salvamento de registrador para rotinas folha. Murtagh usou uma tcnica mais completa e sistemtica para
agrupar registros de ativao [272].
A referncia clssica sobre esquemas de alocao de memria o livro Art of Computer
Programming [231, 2.5], de Knuth. Os modernos alocadores de mltiplos pools
apareceram no incio da dcada de 1980. A contagem de referncia data do incio da
dcada de 1960, e tem sido usada em muitos sistemas [95,125]. Cohen e mais tarde
Wilson, fornecem amplos estudos da literatura sobre coleta de lixo [92,350]. Coletores
conservadores foram introduzidos por Boehm e Weiser [44,46,120]. Coletores de
cpia apareceram em resposta aos sistemas de memria virtual [79,144]; e levaram,
de modo um tanto natural, aos coletores de gerao muito utilizados hoje [247,337].
Hanson introduziu a noo de alocao baseada em arena [179].

EXERCCIOS
Seo 6.2
1. Mostre a rvore de chamadas e o histrico de execuo para o seguinte programa
em C:

278 CAPTULO 6 A abstrao de procedimento

2. 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:

6.7 Resumo e perspectiva 279

a. Construa uma tabela de coordenadas estticas, semelhante da Figura6.3.


b. Construa um grafo para mostrar os relacionamentos de aninhamento no programa.
c. Construa um grafo para mostrar os relacionamentos de chamada no programa.
4. Algumas linguagens de programao permitem que o programador use funes
na inicializao de variveis locais, mas no na inicializao de variveis
globais.
a. Existe uma razo de implementao para explicar essa aparentemente
peculiaridade da definio da linguagem?
b. Que mecanismos seriam necessrios para permitir a inicializao
de uma varivel global com o resultado de uma chamada de funo?
5. O construtor de compiladores pode otimizar a alocao de ARs de vrias
maneiras. Por exemplo, o compilador poderia:
a. Alocar ARs para os procedimentos folha estaticamente.
b. Combinar os ARs para procedimentos que sempre so chamados juntos.
(Quando a chamado, sempre chama b.)
c. Usar um alocador no estilo arena no lugar da alocao em heap dos ARs.
Para cada esquema, considere as seguintes questes:
a. Que frao das chamadas poderia se beneficiar? Na melhor das hipteses?
Na pior?
b. Qual o impacto sobre a utilizao de espao em tempo de execuo?
6. Desenhe as estruturas que o compilador precisaria criar para dar suporte a um
objeto do tipo Dumbo, definido da seguinte forma:

7. Em uma linguagem de programao com estrutura de classes aberta, o nmero


de chamadas de mtodo que precisam de resoluo de nomes em tempo de execuo, ou despacho dinmico, pode ser muito grande. Uma cache de mtodos,
conforme descrito na Seo 6.3.4, pode reduzir o custo de runtime dessa pesquisa realizando um curto-circuito delas. Como uma alternativa para uma cache
de mtodos global, a implementao poderia manter uma cache de mtodos de
entrada nica em cada local de chamada uma cache de mtodos em linha que
registra o endereo do mtodo despachado mais recentemente a partir desse
local, junto com sua classe.
Desenvolva o pseudocdigo para usar e manter essa cache de mtodo em linha.
Explique a inicializao das caches de mtodos em linha e quaisquer modificaes na rotina de pesquisa de mtodo geral exigidas para dar suporte s caches
de mtodo em linha.

280 CAPTULO 6 A abstrao de procedimento

FIGURA 6.13 Programa para o Problema 8.

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:

Se no houver overflow ou underflow durante as operaes aritmticas:


a. Que resultado mystery produz quando chamado com duas variveis
distintas, a e b?
b. Qual seria o resultado esperado se mystery fosse chamado com uma nica
varivel a passada aos dois parmetros? Qual o resultado real neste caso?

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.

6.7 Resumo e perspectiva 281

FIGURA 6.14 Programa para o Problema 10.

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:

282 CAPTULO 6 A abstrao de procedimento

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.

Desenhe o conjunto de ARs que esto na pilha de runtime quando o programa


alcana a linha 7 no procedimento p1.

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

284 CAPTULO 7 Forma de cdigo

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

FIGURA 7.1 Formas de cdigo alternativas x+y+z.

7.2 Atribuio de locais de armazenamento 285

o impacto da ordem de avaliao. As trs verses direita da Figura 7.1 mostram


trs ordens de avaliao possveis, tanto como cdigo de trs endereos quanto como
rvores sintticas abstratas. (Consideramos que cada varivel est em um registrador
de nome apropriado e que a linguagem fonte no especifica a ordem de avaliao para
tal expresso.) Como a adio de inteiros comutativa e associativa, todas as ordens
so equivalentes; o compilador precisa escolher uma para implementar.
A associatividade esquerda produziria a primeira rvore binria, que parece natural,
porque a associatividade esquerda corresponde ao nosso estilo de leitura da esquerda
para a direita. Considere o que acontece se substituirmos y pela constante literal 2
e z por 3. Naturalmente, x+2+3 equivalente a x+5. O compilador deve detectar o
clculo de 2+3, avali-lo e usar o resultado diretamente no cdigo. Porm, na forma
associativa esquerda, nunca ocorre. A ordem x+z+y tambm a oculta. A verso associativa direita expe a oportunidade para melhoria. Para cada rvore em perspectiva,
entretanto, existe uma atribuio de variveis e constantes a x, y e z que no expe a
expresso constante para otimizao.
Assim como o comando switch, o compilador no pode escolher a melhor forma
para essa expresso sem entender o contexto em que ela aparece. Se, por exemplo, a
expresso x+y tiver sido calculada recentemente e nenhum dos valores de x nem de
y tiver mudado, ento o uso da forma mais esquerda permitiria que o compilador
substitusse a primeira operao, r1 rx + ry , por uma referncia ao valor calculado
anteriormente. Em geral, a melhor ordem de avaliao depende do contexto do cdigo
ao redor.
Este captulo explora as questes de forma de cdigo que surgem na implementao
de muitas construes comuns de linguagem-fonte. Focaliza o cdigo que deve ser
gerado para construes especficas, ignorando em grande parte os algoritmos exigidos
para escolher instrues especficas de linguagem assembly. As questes de seleo
de instruo, alocao de registrador e escalonamento de instruo so tratadas separadamente, em outros captulos.

7.2 ATRIBUIO DE LOCAIS DE ARMAZENAMENTO


Como parte da traduo, o compilador precisa atribuir um local de armazenamento
para cada valor produzido pelo cdigo, e entender o tipo do valor, seu tamanho, sua
visibilidade e seu tempo de vida. Precisa, ainda, levar em conta o layout da memria em
tempo de execuo, quaisquer restries da linguagem fonte sobre o layout das reas e
estruturas de dados, e quaisquer restries do processador-alvo sobre o posicionamento
ou uso dos dados. O compilador resolve essas questes definindo e seguindo um
conjunto de convenes.
Um procedimento tpico calcula muitos valores. Alguns deles, como variveis em uma
linguagem como Algol, tm nomes explcitos no cdigo-fonte. Outros valores tm
nomes implcitos, como o valor i 3 na expresso A[i 3, j+ 2] .
j

O tempo de vida de um valor nomeado definido por regras da linguagem-fonte


e o uso real no cdigo. Por exemplo, o valor de uma varivel esttica precisa ser
preservado por vrias chamadas de seu procedimento de definio, enquanto
uma varivel local do mesmo procedimento s necessria a partir de sua
primeira definio at seu ltimo uso em cada chamada.
j Ao contrrio, o compilador tem mais liberdade no modo como trata valores
no nomeados, como i 3. E precisa tratar deles de formas coerentes com o
significado do programa, mas tem grande liberdade em determinar onde esses
valores residem e por quanto tempo ret-los.

286 CAPTULO 7 Forma de cdigo

As opes de compilao podem, ainda, afetar o posicionamento; por exemplo, o cdigo


compilado, para funcionar com um depurador, deve preservar todos os valores que o
depurador pode nomear normalmente, variveis nomeadas.
O compilador tambm precisa decidir, para cada valor, se dever mant-lo em um
registrador ou na memria. Em geral, os compiladores adotam um modelo de
memria um conjunto de regras para gui-lo na escolha de locais para valores.
Duas polticas comuns so os modelos de memria-para-memria e de registrador-para-registrador. A escolha entre eles tem impacto importante sobre o cdigo
que o compilador produz.
Registrador fsico
Registrador nomeado na ISA de destino.

Com o modelo de memria-para-memria, o compilador assume que todos os valores


residem na memria. Valores so carregados em registradores conforme a necessidade,
mas o cdigo os armazena de volta para a memria aps cada definio. Neste modelo,
a IR normalmente usa nomes de registrador fsico. O compilador garante que, a cada
instruo, a demanda por registradores no excede a quantidade existente.

Registrador virtual
Nome simblico usado na IR no lugar do nome de um
registrador fsico.

No modelo registrador-para-registrador, o compilador assume que tem registradores


suficientes para expressar o clculo. Ele inventa um nome distinto, um registrador
virtual, para cada valor que pode residir legalmente em um registrador. O cdigo
compilado armazenar o valor de um registrador virtual na memria somente quando
for absolutamente necessrio, como quando passado como parmetro ou valor de
retorno, ou quando o alocador de registradores o colocar para fora.
A escolha do modelo de memria tambm afeta a estrutura do compilador. Por exemplo, em um modelo de memria-para-memria, o alocador de registradores uma
otimizao que melhora o cdigo. J em um modelo de registrador-para-registrador, o
alocador de registradores uma fase obrigatria que reduz a demanda por registradores
e mapeia os nomes de registradores virtuais para nomes de registradores fsicos.

7.2.1 Posicionamento de estruturas de dados em tempo


de execuo
Para realizar a atribuio de espao de armazenamento, o compilador precisa entender
as convenes no nvel de sistema sobre alocao e uso de memria. O compilador,
o sistema operacional e o processador cooperam para garantir que vrios programas
possam ser executados com segurana em uma base intercalada (fatiada no tempo).
Assim, muitas das decises sobre como dispor, manipular e gerenciar o espao de
endereos de um programa esto fora do alcance do construtor de compiladores. Porm,
as decises tm forte impacto sobre o cdigo que o compilador gera. Assim, o construtor de compiladores precisa ter grande conhecimento dessas questes.
O compilador pode criar reas de dados estticas
adicionais para manter valores constantes, tabelas de
salto, e informaes de depurao.

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

FIGURA 7.2 Layout lgico do espao de endereos.

7.2 Atribuio de locais de armazenamento 287

extremo inferior do espao de endereos. O cdigo situa-se na parte inferior do espao


de endereos; a regio adjacente, rotulada Esttico, mantm as reas de dados esttica e
global, juntamente com qualquer dado de tamanho fixo criado pelo compilador. A regio
acima dessas reas de dados estticas dedicada s reas de dados que se expandem
e se contraem. Se o compilador puder alocar ARs em pilha, precisar de uma pilha de
runtime. Na maioria das linguagens, tambm precisar de um heap para as estruturas
de dados alocadas dinamicamente. Para permitir utilizao eficiente de espao, o heap
e a pilha devem ser colocadas em extremos opostos do espao aberto e crescer um em
direo ao outro. Na figura, o heap cresce para endereos mais altos, enquanto a pilha,
para os mais baixos. O arranjo oposto funciona igualmente bem.
Do ponto de vista do compilador, este espao de endereos lgico a imagem
completa. Porm, os modernos sistemas de computao normalmente executam
muitos programas em um padro intercalado. O sistema operacional mapeia vrios
espaos de endereo lgicos no nico espao de endereos fsico admitido pelo
processador. A Figura7.3 mostra esta imagem maior. Cada programa isolado em
seu prprio espao de endereos lgico; cada um pode se comportar como se tivesse
sua prpria mquina.
Um nico espao de endereos lgico pode ocupar pginas disjuntas no espao de
endereos fsico; assim, os endereos 100.000 e 200.000 no espao de endereos lgico
do programa no precisam estar 100.000 bytes afastados na memria fsica. De fato,
o endereo fsico associado ao endereo lgico 100.00 pode ser maior que o endereo
fsico associado ao endereo lgico 200.000. O mapeamento de endereos lgicos
para endereos fsicos mantido de forma cooperativa pelo hardware e pelo sistema
operacional. Isto est, em quase todos os aspectos, fora do alcance do compilador.

FIGURA 7.3 Vises diferentes do espao de endereos.

7.2.2 Layout para rea de dados


Por convenincia, o compilador agrupa o armazenamento para valores com os mesmos
tempos de vida e visibilidade; e cria reas de dados distintas para eles. O posicionamento dessas reas de dados depende das regras de linguagem sobre tempos de vida e
visibilidade de valores. Por exemplo, o compilador pode posicionar o armazenamento
automtico de procedimento local dentro do registro de ativao do procedimento,
exatamente porque os tempos de vida dessas variveis correspondem ao do AR. Em
contraste, precisa posicionar o armazenamento esttico ao procedimento local onde

Pgina
Unidade de alocao em um espao de endereo
virtual.
O sistema operacional mapeia pginas virtuais em
frames de pgina fsica.

288 CAPTULO 7 Forma de cdigo

existir durante as chamadas na regio esttica da memria. A Figura7.4 mostra


um conjunto tpico de regras para atribuir uma varivel a uma rea de dados especfica.
As linguagens orientadas a objeto seguem regras diferentes, mas os problemas no so
mais complexos.

FIGURA 7.4 Atribuio de nomes a reas de dados.

Para estabelecer o endereo de uma rea de dados


esttica ou global, o compilador normalmente carrega
um rtulo relocvel de linguagem assembly.

O posicionamento de variveis automticas locais no AR leva a um acesso eficiente.


Como o cdigo j precisa do ARP em um registrador, pode usar deslocamentos relativos
ao ARP para acessar esses valores, com operaes como loadAI ou loadAO. O
acesso frequente ao AR provavelmente o manter na cache de dados. O compilador
posiciona variveis com tempos de vida estticos ou visibilidade global para as reas
de dados na regio esttica da memria. O acesso a esses valores exige um pouco
mais de trabalho em tempo de execuo; o compilador precisa garantir que ter um
endereo para a rea de dados em um registrador.
Valores armazenados no heap tm tempos de vida que o compilador no pode prever
com facilidade. Um valor pode ser colocado no heap por dois mecanismos distintos. O
programador pode alocar armazenamento explicitamente a partir do heap; o compilador
no deve modificar esta deciso. O compilador pode colocar um valor no heap quando
detectar que o valor poderia sobreviver ao procedimento que o criou. De qualquer
forma, um valor no heap representado por um endereo completo, ao invs de um
deslocamento a partir de algum endereo de base.

UMA CARTILHA SOBRE MEMRIAS CACHE


Uma forma pela qual os arquitetos tentam preencher a lacuna entre velocidade de
processador e velocidade de memria por meio do uso de memrias cache. Uma
cache uma memria pequena e rpida, colocada entre o processador e a memria
principal. dividida em uma srie de frames de mesmo tamanho. Cada um tem um
campo de endereo, chamado tag, que mantm um endereo na memria principal.
O hardware automaticamente mapeia locais da memria a frames de cache. O
mapeamento mais simples, usado em uma cache mapeada diretamente, calcula o
endereo de cache como o principal endereo de memria mdulo do tamanho
da cache. Isto particiona a memria em um conjunto linear de blocos, cada um do
tamanho de um frame de cache. Uma linha um bloco de memria mapeado para
um frame. Em qualquer ponto no tempo, cada frame de cache mantm uma cpia
dos dados de um de seus blocos. Seu campo de tag mantm o endereo na memria
onde esses dados normalmente residem.
Em cada acesso de leitura memria, o hardware verifica se a palavra solicitada j est
em seu frame de cache. Se sim, os bytes solicitados so retornados ao processador. Se
no, o bloco atualmente no frame removido e o solicitado trazido para a cache.

7.2 Atribuio de locais de armazenamento 289

Algumas caches usam mapeamentos mais complexos. A cache associativa em


conjunto usa mltiplos frames para cada linha de cache, normalmente dois ou quatro
frames por linha. A cache totalmente associativa pode colocar qualquer bloco em
qualquer frame. Esses dois esquemas usam uma busca associativa sobre as tags para
determinar se um bloco est na cache. Os esquemas associativos usam uma poltica
para determinar qual bloco remover; os esquemas comuns so a substituio aleatria
e a substituio do bloco usado menos recentemente (LRU Least Recently Used).
Na prtica, a velocidade efetiva de memria determinada pela largura de banda da
memria, tamanho de bloco de cache, razo entre velocidade de cache e velocidade
de memria, e a porcentagem de acessos que tm sucesso na cache. Do ponto de
vista do compilador, os trs primeiros so fixos. Os esforos baseados em compilador
para melhorar o desempenho da memria se concentram no aumento da razo entre
sucessos e falhas de cache, chamada razo de acerto.
Algumas arquiteturas fornecem instrues que permitem a um programa dar
cache dicas sobre quando blocos especficos devem ser trazidos para a memria
(pr-buscados) e quando no so mais necessrios (esvaziados).

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.

Deslocamentos relativos e desempenho de cache


O uso generalizado de memrias cache nos sistemas de computador modernos tem
implicaes sutis para o layout de variveis em memria. Se dois valores forem usados
em proximidade no cdigo, o compilador gostaria de garantir que ambos possam residir

A maioria das linguagens assembly possui diretivas


para especificar o alinhamento do incio de uma rea de
dados, como uma fronteira de palavra dupla.

290 CAPTULO 7 Forma de cdigo

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.

7.2.3 Mantendo valores em registradores


Derramar
Quando o alocador de registradores no pode atribuir
algum registrador virtual a um registrador fsico,
ele derrama o valor, armazenando-o na RAM aps
cada definio, e carregando-o para um registrador
temporrio antes de cada uso.

No modelo de memria de registrador-para-registrador, o compilador tenta atribuir o


mximo de valores possvel aos registradores virtuais. Nesta tcnica, o compilador conta
com o alocador de registradores para mapear registradores virtuais na IR a registradores
fsicos no processador e derramar para a memria qualquer registrador virtual que ele
no possa manter em um registrador fsico. Se o compilador mantiver um valor esttico
em um registrador, dever carregar o valor antes do seu primeiro uso no procedimento
e armazen-lo de volta para a memria antes de sair do procedimento, seja na sada do
procedimento ou em qualquer local de chamada dentro do procedimento.
Na maioria dos exemplos deste livro, seguimos um mtodo simples para atribuir
registradores virtuais a valores. Cada valor recebe seu prprio registrador virtual
com um subscrito distinto. Esta disciplina expe o maior conjunto de valores para
anlise e otimizao subsequentes. Ela pode, de fato, usar muitos nomes. Porm,
esse esquema tem trs vantagens principais. simples. Pode melhorar os resultados
da anlise e otimizao. Impede o construtor de compiladores de trabalhar com restries especficas do processador no cdigo antes da otimizao, melhorando assim
a portabilidade. Um alocador de registradores forte pode gerenciar o espao de nomes
e ajust-lo exatamente s necessidades da aplicao e aos recursos disponveis no
processador-alvo.

7.2 Atribuio de locais de armazenamento 291

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.

292 CAPTULO 7 Forma de cdigo

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?

7.3 OPERADORES ARITMTICOS


Processadores modernos fornecem um vasto suporte para avaliao de expresses. Uma
mquina RISC tpica tem um suplemento completo de operaes de trs endereos,
incluindo operadores aritmticos, deslocamentos e operadores booleanos. A forma de
trs endereos permite que o compilador nomeie o resultado de qualquer operao e o
preserve para reso posterior. E tambm elimina a principal complicao da forma de
dois endereos: operaes destrutivas.
Para gerar cdigo para uma expresso trivial, como a+b, o compilador, primeiro, emite
cdigo para garantir que os valores de a e b estejam em registradores, digamos ra e
rb. Se a estiver armazenado na memria no deslocamento @a no AR atual, o cdigo
resultante poderia ser

Porm, se o valor de a j estiver em um registrador, o compilador pode simplesmente


usar este registrador no lugar de ra. O compilador segue uma cadeia de decises
semelhante para b. E, finalmente, emite uma instruo para realizar a adio, como

Se a expresso for representada em uma IR em forma de rvore, este processo se ajusta


a um percurso de rvore em ps-ordem. A Figura7.5a mostra o cdigo para um percurso
de rvore que gera cdigo para expresses simples, que conta com duas rotinas, base
e, para ocultar parte da complexidade. A rotina base retorna o nome de um registrador
mantendo o endereo de base para um identificador; se for preciso, emite cdigo para
colocar esse endereo em um registrador. A rotina offset tem funo semelhante;
retorna o nome de um registrador que mantm o deslocamento do identificador em
relao ao endereo retornado por base.
O mesmo cdigo lida com +, , e . Sob o ponto de vista de gerao de cdigo,
esses operadores so intercambiveis, ignorando a comutatividade. A chamada da rotina
expr pela Figura7.5a na AST para abc, mostrada na parte b da Figura, produz
os resultados mostrados na parte c. O exemplo considera que a, b e c ainda no esto
nos registradores, e que cada um reside no AR atual.
Observe a semelhana entre o gerador de cdigo de percurso em rvore e o esquema
de traduo ad hoc dirigida pela sintaxe, mostrado na Figura4.15. O percurso em
rvore torna os detalhes mais explcitos, incluindo o tratamento de terminais e a
ordem de avaliao para subrvores. No esquema de traduo dirigida pela sintaxe,
aordem de avaliao controlada pelo parser. Ainda assim, os dois esquemas
produzem cdigo relativamente equivalente.

7.3.1 Reduo da demanda por registradores


Muitos aspectos afetam a qualidade do cdigo gerado. Por exemplo, a escolha dos locais
de armazenamento tem um impacto direto, mesmo para esta expresso simples. Se a
estivesse em uma rea de dados global, a sequncia de instrues necessria para obter
a de um registrador poderia exigir um loadI adicional a fim de obter o endereo de

7.3 Operadores aritmticos 293

FIGURA 7.5 Gerador de cdigo simples de percurso em rvore para expresses.

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.

294 CAPTULO 7 Forma de cdigo

Naturalmente, a avaliao da direita para a esquerda no uma soluo geral. Para a


expresso ab+c, a avaliao da esquerda para a direita produz demanda mais baixa
por registradores. Algumas expresses, como a + (b + c) d, desafiam uma regra esttica simples. A ordem de avaliao que minimiza a demanda por registradores
a + ((b + c) d).

FIGURA 7.6Reescrevendo abc para reduzir a demanda por registradores.

7.3 Operadores aritmticos 295

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.

7.3.2 Acesso a valores de parmetro


O gerador de cdigo na Figura7.5 considera implicitamente que um nico mtodo de
acesso funciona para todos os identificadores. Os parmetros formais podem precisar
de tratamento diferente. Um parmetro de chamada por valor passado no AR pode ser
tratado como se fosse uma varivel local. Um parmetro de chamada por referncia
passado no AR exige uma indireo adicional. Assim, para o parmetro de chamada
por referncia d, o compilador poderia gerar

para obter o valor de d. As duas primeiras operaes movem o endereo do valor do


parmetro para r2. A operao final move o prprio valor para r3.
GERAO DE CARREGAMENTO DE ENDEREO IMEDIATO
Um leitor atento poderia observar que o cdigo na Figura7.5 nunca gera a
instruo de carregamento de endereo imediato da ILOC, loadAI. Ao invs
disso, gera um load imediato (loadAI) seguido por um load de deslocamento de
endereo (loadAO).

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).

Esta tcnica, anlise seguida por transformao,


aplica-se gerao de cdigo e otimizao [150].

296 CAPTULO 7 Forma de cdigo

Muitas convenes de ligao passam os primeiros parmetros em registradores.


Conforme escrito, o cdigo contido na Figura7.5 no pode tratar de um valor que seja
permanentemente mantido em um registrador. As extenses necessrias, porm, so
fceis de implementar.
j

Parmetros de chamada por valor. O caso IDENT precisa verificar se o valor


j est em um registrador. Se estiver, apenas atribui o nmero do registrador
a result. Caso contrrio, usa os mecanismos padro para carregar o valor da
memria.
j Parmetro de chamada por referncia. Se o endereo residir em um registrador,
o compilador simplesmente carrega o valor em um registrador. Se residir no AR,
deve carregar o endereo antes de carregar o valor.

COMUTATIVIDADE, ASSOCIATIVIDADE E SISTEMAS NUMRICOS


O compilador, com frequncia, pode tirar proveito das propriedades algbricas dos
operadores. Adio e multiplicao so comutativas e associativas, assim como os
operadores booleanos. Portanto, se o compilador encontrar um fragmento de cdigo
que calcule a+b e depois calcular b+a, sem quaisquer atribuies entre os dois para a
ou b, ele deve reconhecer que ambos calculam o mesmo valor. De modo semelhante,
se encontrar as expresses a + b + c e d + a + b, deve reconhecer que a+b uma
subexpresso comum. Se avaliar as duas expresses na ordem estrita da esquerda
para a direita, nunca reconhecer a subexpresso comum, pois calcular a segunda
expresso como d+a e depois (d + a) + b.
O compilador deve usar a comutatividade e a associatividade para melhorar
a qualidade do cdigo que gera. A reordenao de expresses pode expor
oportunidades adicionais para muitas transformaes.
Devido s limitaes na preciso, os nmeros de ponto flutuante em um computador
representam apenas um subconjunto dos nmeros reais, que no preserva a
associatividade. Por este motivo, os compiladores no devem reordenar as expresses de
ponto flutuante, a menos que a definio da linguagem assim permita especificamente.
Considere o exemplo a seguir: calcular a b c. Podemos atribuir valores de ponto
flutuante para a, b e c, de modo que
mas a (b + c) a. Neste caso, o resultado numrico depende da ordem de avaliao.
A avaliao de (a b) c produz resultado idntico a a, enquanto a avaliao de b + c
primeiro e a subtrao dessa quantidade de a produz um resultado distinto de a.
Este problema surge pela natureza aproximada dos nmeros de ponto flutuante; a
mantissa pequena em relao ao intervalo do expoente. Para somar dois nmeros,
o hardware precisa normaliz-los; se a diferena em expoentes for maior do que
a preciso da mantissa, o nmero menor ser truncado para zero. O compilador
no pode facilmente resolver esta questo, de modo que, em geral, deve evitar a
reordenao de clculos de ponto flutuante.

De qualquer forma, o cdigo ajusta-se muito bem ao framework de percurso em rvore.


Observe que o compilador no pode manter o valor de um parmetro de chamada por
referncia em um registrador por meio de uma atribuio, a menos que possa provar
que a referncia no ambgua em todas as chamadas ao procedimento.
Se o parmetro real for uma varivel local do chamador
e seu endereo nunca for tomado, o parmetro formal
correspondente no ambguo.

7.3.3 Chamadas de funo em uma expresso


At aqui, consideramos que todos os operandos em uma expresso so variveis, constantes e valores temporrios produzidos por outras subexpresses. As chamadas de funo

7.3 Operadores aritmticos 297

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).

7.3.4 Outros operadores aritmticos


Para lidar com outras operaes aritmticas, podemos estender o modelo de percurso em
rvore. O esquema bsico permanece o mesmo: obter os operandos nos registradores,
realizar a operao e armazenar o resultado. A precedncia de operadores, a partir
da gramtica da expresso, garante a ordem correta de avaliao. Alguns operadores
exigem sequncias complexas de mltiplas operaes para sua implementao (por
exemplo, exponenciao e funes trigonomtricas). Estes podem ser expandidos em
linha ou implementados com uma chamada a uma rotina de biblioteca fornecida pelo
compilador ou pelo sistema operacional.

7.3.5 Expresses de tipo misto


Uma complicao permitida por muitas linguagens de programao uma operao
com operandos de diferentes tipos. (Aqui, estamos preocupados principalmente com
os tipos bsicos na linguagem-fonte, ao invs de tipos definidos pelo programador.)
Conforme descrito na Seo 4.2, o compilador deve reconhecer esta situao e inserir o
cdigo de converso exigido pela tabela de converso de cada operador. Normalmente,
isto envolve converter um ou ambos os operandos a um tipo mais geral e realizar a
operao nesse tipo mais geral. A operao que consome o valor do resultado pode
precisar convert-lo para ainda outro tipo.
Alguns processadores fornecem operadores de converso explcita; outros, esperam
que o compilador gere cdigo complexo, dependente de mquina. Em qualquer caso, o
construtor de compiladores pode querer fornecer operadores de converso no IR. Estes
operadores encapsulam todos os detalhes da converso, incluindo qualquer fluxo de
controle, e permitem que o compilador os sujeite a uma otimizao uniforme. Assim,
a movimentao de cdigo pode puxar uma converso invariante, para fora de um lao
sem se preocupar com o fluxo de controle interno do lao.
Normalmente, a definio da linguagem de programao especifica uma frmula para cada converso. Por exemplo, para converter integer para complex em FORTRAN 77,
o compilador primeiro converte o integer para um real. Usa o nmero resultante
como a parte real do nmero complexo e define a parte imaginria como um zero real.
Para tipos definidos pelo usurio, o compilador no ter tabelas de converso que
definem cada caso especfico. Porm, a linguagem-fonte ainda define o significado da
expresso. A tarefa do compilador implementar esse significado; se uma converso
for ilegal, ento deve ser evitada. Como vimos no Captulo4, muitas converses ilegais
podem ser detectadas e evitadas em tempo de compilao. Quando uma verificao

298 CAPTULO 7 Forma de cdigo

em tempo de compilao for impossvel ou inconclusiva, o compilador deve gerar uma


verificao em tempo de execuo que teste os casos ilegais. Quando o cdigo tenta
uma converso ilegal, a verificao deve disparar um erro de runtime.

7.3.6 Atribuio como um operador


A maioria das linguagens do tipo Algol implementa a atribuio com as seguintes
regras simples:
1. Avaliar o lado direito da atribuio como um valor.
2. Avaliar o lado esquerdo da atribuio como um local.
3. Armazenar o valor do lado direito no local do lado esquerdo.
Assim, em uma instruo como a b , as duas expresses a e b so avaliadas de
formas diferentes. Como b aparece direita do operador de atribuio, avaliado para
produzir um valor; se b uma varivel inteira, este valor um inteiro. Como a est
esquerda do operador de atribuio, avaliado para produzir um local; se a uma
varivel inteira, este valor o local de um inteiro. Esse local pode ser um endereo na
memria, ou ento um registrador. Para distinguir entre esses modos de avaliao, s
vezes nos referimos ao resultado da avaliao no lado direito de uma atribuio como
um rvalue, e a do lado esquerdo como um lvalue.
Em uma atribuio, o tipo do lvalue pode diferir do tipo do rvalue. Dependendo da
linguagem e dos tipos especficos, esta situao pode exigir, ou uma converso inserida
pelo compilador, ou uma mensagem de erro. A regra tpica da linguagem-fonte para a
converso faz que o compilador avalie o rvalue para seu tipo natural, e depois converta
o resultado para o tipo do lvalue.

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 OPERADORES BOOLEANOS E RELACIONAIS


A maioria das linguagens de programao opera sobre um conjunto mais rico de valores
do que nmeros. Normalmente, isto inclui os resultados de operadores booleanos e
relacionais, ambos produzindo valores booleanos. Como a maioria das linguagens de

7.4 Operadores booleanos e relacionais 299

programao tem operadores relacionais que produzem resultados booleanos, tratamos


os operadores booleanos e relacionais juntos. Um uso comum para as expresses booleanas e relacionais alterar o fluxo de controle do programa. Grande parte do poder
das modernas linguagens de programao deriva da capacidade de calcular e testar
esses valores.
A Figura7.7 mostra a gramtica de expresso padro aumentada com operadores booleanos e relacionais. O construtor de compiladores, por sua vez, deve decidir como
representar esses valores e como calcul-los. Para expresses aritmticas, essas decises
de projeto so em grande parte ditadas pela arquitetura-alvo, que fornece formatos
numricos e instrues para realizar a aritmtica bsica. Felizmente, os arquitetos de
processador parecem ter alcanado um acordo geral sobre como dar suporte aritmtica.
De modo semelhante, a maioria das arquiteturas oferece um conjunto rico de operaes
booleanas. Porm, o suporte para operadores relacionais varia muito de uma arquitetura
para outra. O construtor de compiladores deve usar uma estratgia de avaliao que
combine as necessidades da linguagem com o conjunto de instrues disponvel.

FIGURA 7.7 Incluso de booleanos e relacionais gramtica de expresso.

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.

A gramtica usa os smbolos para not, para


and e para or a fim de evitar confuso com os
operadores da ILOC.
O verificador de tipos deve garantir que cada expresso
aplique operadores a nomes, nmeros e expresses de
tipos apropriados.

300 CAPTULO 7 Forma de cdigo

Por exemplo, se b, c e d esto todos em registradores, o compilador pode produzir o


seguinte cdigo para o exemplo b c d:

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 .

A implementao de a < b com operaes de cdigo de condio exige mais operaes


do que usar uma comparao que retorna um booleano.
A ILOC contm sintaxe para implementar os dois estilos
de comparao e desvio. Uma IR normal escolheria um
deles; a ILOC inclui ambos para que possa expressar o
cdigo nesta seo.

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.

7.4 Operadores booleanos e relacionais 301

FIGURA 7.8 Codificao de a < b c < d e < f .

302 CAPTULO 7 Forma de cdigo

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:

Para gerar o cdigo em curto-circuito, o compilador precisa analisar a expresso luz


dessas duas identidades e encontrar o conjunto de condies mnimas que determina
seu valor. Se as clusulas na expresso contm operadores dispendiosos ou se a
avaliao usar desvios, como muitos dos esquemas discutidos nesta seo, ento a
avaliao em curto-circuito pode reduzir significativamente o custo da avaliao de
expresses booleanas.
Algumas linguagens de programao, como C, exigem que o compilador use a
avaliao em curto-circuito. Por exemplo, a expresso

em C conta com a avaliao em curto-circuito por segurana. Se x zero, y/x no


definido. Nitidamente, o programador deseja evitar a exceo de hardware disparada
pela diviso por zero. A definio da linguagem especifica que esse cdigo nunca
realizar a diviso se x tiver o valor zero.

7.4.2 Suporte de hardware para operaes relacionais


Detalhes especficos de baixo nvel no conjunto de instrues da mquina-alvo influenciam fortemente a escolha de uma representao para valores relacionais. Em particular,
o construtor de compiladores precisa prestar ateno ao tratamento de cdigos de
condio, operaes de comparao e operaes de movimentao condicional, pois
tm impacto importante sobre os custos relativos das diversas representaes. Vamos
considerar quatro esquemas para dar suporte a expresses relacionais: cdigos de
condio diretos, cdigos de condio aumentados com uma operao de movimento

7.4 Operadores booleanos e relacionais 303

(move) condicional, comparaes booleanas e operaes predicadas. Cada esquema


uma verso idealizada de uma implementao real.
A Figura7.9 mostra duas construes de nvel-fonte e suas implementaes sob cada
um desses esquemas. A Figura7.9a mostra um if-then-else que controla um par
de instrues de atribuio. A Figura7.9b mostra a atribuio de um valor booleano.

AVALIAO EM CURTO-CIRCUITO COMO UMA OTIMIZAO


A avaliao em curto-circuito surgiu de uma codificao posicional dos valores das
expresses booleanas e relacionais. Em processadores que usam cdigos de condio
para registrar o resultado de uma comparao e usam desvios condicionais para
interpretar o cdigo de condio, o curto-circuito faz sentido.
medida que os processadores incluem recursos, como movimentao condicional,
comparaes de valor booleano e execuo predicada, as vantagens da avaliao
em curto-circuito iro provavelmente desaparecer. Com o crescimento das latncias
de desvio, o custo dos desvios condicionais exigido para o curto-circuito tambm
cresce. Quando os custos de desvio excedem as economias de evitar a avaliao, o
curto-circuito no mais uma vantagem. Ao invs disso, a avaliao total ser mais
rpida.
Quando a linguagem exigir a avaliao em curto-circuito, como acontece em
C, o compilador pode precisar realizar alguma anlise para determinar quando
seguro substituir a avaliao total pela avaliao em curto-circuito. Assim,
futuros compiladores C podem incluir a anlise e a transformao para substituir
o curto-circuito pela avaliao total, assim como os compiladores no passado
realizavam a anlise e a transformao para substituir a avaliao completa pelo
curto-circuito.

Cdigos de condio diretos


Neste esquema, a operao de comparao define um registrador de cdigo de condio.
A nica instruo que interpreta o cdigo de condio um desvio condicional, com
variantes que desviam em cada uma das seis relaes (<, , =, ,>e ). Essas instrues podem existir para operandos de vrios tipos.
O compilador precisa usar desvios condicionais para interpretar o valor de um cdigo
de condio. Se o nico uso do resultado for para determinar o fluxo de controle, como
na Figura7.9a, ento o desvio condicional que o compilador usa para ler o cdigo de
condio normalmente tambm pode implementar a construo de fluxo de controle
de nvel-fonte. Se o resultado for usado em uma operao booleana, ou preservado
em uma varivel, como na Figura7.9b, o cdigo precisa converter o resultado em
uma representao concreta de um booleano, como fazem as duas operaes loadI
na Figura7.9b. De qualquer forma, o cdigo tem pelo menos um desvio condicional
por operador relacional.
A vantagem dos cdigos de condio vem de outro recurso que os processadores
normalmente implementam junto com esses cdigos. Normalmente, as operaes
aritmticas nesses processadores definem o cdigo de condio para refletir seus
resultados calculados. Se o compilador puder planejar para fazer com que as operaes
aritmticas que precisam ser realizadas tambm definam o cdigo de condio necessrio para controlar o desvio, ento a operao de comparao pode ser omitida.
Assim, os defensores deste estilo de arquitetura argumentam que isto permite uma
codificao mais eficiente do programa o cdigo pode executar menos instrues
do que faria com um comparador que coloca um valor booleano em um registrador
de propsito geral.

FIGURA 7.9 Implementao de operadores booleanos e relacionais.

7.4 Operadores booleanos e relacionais 305

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.

306 CAPTULO 7 Forma de cdigo

nas partes then e else da construo fonte. Na Figura7.9b, a predicao leva ao


mesmo cdigo do esquema de comparao booleana.
O processador pode usar a predicao para evitar a execuo da operao, ou pode
executar a operao e usar o predicado para evitar a atribuio do resultado. Desde que
a operao ociosa no levante uma exceo, as diferenas entre essas duas tcnicas so
irrelevantes para a nossa discusso. Nossos exemplos mostram as operaes exigidas
para produzir tanto o predicado quanto seu complemento. Para evitar a computao
extra, um processador poderia fornecer comparaes que retornam dois valores, tanto
o valor booleano quanto seu complemento.
REVISO DA SEO
A implementao de operadores booleanos e relacionais envolve mais escolhas do
que a implementao dos operadores aritmticos. O construtor de compiladores
precisa escolher entre uma codificao numrica e uma posicional. O compilador
precisa mapear essas decises no conjunto de operaes fornecidas pela ISA do
processador-alvo.
Na prtica, os compiladores escolhem entre a codificao numrica e a posicional
com base no contexto. Se o cdigo utiliza o valor, a codificao numrica necessria.
Se o nico uso do valor determinar o fluxo de controle, a codificao posicional
normalmente produz melhores resultados.

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?

7.5 ARMAZENAMENTO E ACESSO A ARRAYS


At aqui, consideramos que as variveis armazenadas na memria contm valores
escalares. Muitos programas precisam de arrays ou estruturas semelhantes. O cdigo
exigido para localizar e referenciar um elemento de um array supreendentemente
complexo. Esta seo mostra vrios esquemas para dispor arrays na memria e descreve
o cdigo que cada esquema produz para uma referncia de array.

7.5.1 Referncia a um elemento de vetor


A forma mais simples de um array tem uma nica dimenso; vamos cham-la de
vetor. Vetores normalmente so armazenados em memria contgua, de modo que
o i-simo elemento precede o (i+1)-simo elemento. Assim, o vetor V[3...10] gera
o seguinte layout de memria, no qual o nmero abaixo de uma clula indica seu
ndice no vetor:

7.5 Armazenamento e acesso a arrays 307

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.

O uso do limite inferior zero elimina a subtrao. Se o compilador souber o limite


inferior de V, pode incorporar a subtrao em @V. Ao invs de usar @V como endereo
de base para V, pode usar V0 = @ V low w . Chamamos @V de falso zero de V.

Usando @V0 e supondo que i esteja em ri, o cdigo para acessar V[i] torna-se

Este cdigo mais curto e, supostamente, mais rpido. Um bom programador em


linguagem assembly poderia escrev-lo. Em um compilador, a sequncia maior pode
produzir melhores resultados pela exposio de detalhes, como a multiplicao e a
adio, para a otimizao. Melhorias de baixo nvel, como converter a multiplicao

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.

308 CAPTULO 7 Forma de cdigo

em um shift e a converso da sequncia addload por loadAO, podem ser feitas


mais tarde na compilao.
Se o compilador no souber os limites de um array, ele pode calcular o falso zero do array
em tempo de execuo e reutilizar esse valor em cada referncia ao array. Pode, ainda,
calcular o falso zero na entrada para um procedimento que referencia elementos do array
vrias vezes. Uma estratgia alternativa, empregada em linguagens como C, fora o uso
de zero como um limite inferior, o que garante que @V0=@V e simplifica todos os clculos
de endereo de array. Porm, a ateno aos detalhes no compilador pode conseguir os
mesmos resultados sem restringir a escolha de um limite inferior pelo programador.

7.5.2 Layout de armazenamento de array


O acesso a um elemento de um array multidimensional exige mais trabalho. Antes
de discutir as sequncias de cdigo que o compilador precisa gerar, temos que considerar como ele mapear ndices de array para locais de memria. A maioria das
implementaes usa um de trs esquemas: ordem por linhas, ordem por colunas ou
vetores de indireo. A definio da linguagem-fonte normalmente especifica um
desses mapeamentos.
O cdigo exigido para acessar um elemento de array depende do modo como o array
mapeado na memria. Considere o array A[1...2,1...4]. Conceitualmente, ele se parece
com:

Na lgebra linear, a linha de uma matriz bidimensional a sua primeira dimenso,


e a coluna a segunda. Na ordem por linhas, os elementos de A so mapeados para
locais de memria consecutivos, de modo que elementos adjacentes de uma nica linha
ocupam locais de memria consecutivos. Isto produz o seguinte layout:

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:

7.5 Armazenamento e acesso a arrays 309

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.

FIGURA 7.10 Vetores de indireo em ordem por linhas para B[1...2,1...3,1...4] .

310 CAPTULO 7 Forma de cdigo

7.5.3 Referncia a um elemento de array


Programas que usam arrays normalmente contm referncias a seus elementos individuais. Assim como para os vetores, o compilador precisa traduzir uma referncia
de array em um endereo de base para o armazenamento do array e um deslocamento
onde o elemento localizado em relao ao endereo inicial.
Esta seo descreve os clculos de endereo para arrays armazenados como um
bloco contguo em ordem por linhas e como um conjunto de vetores de indireo.
Os clculos para a ordem por colunas seguem o mesmo esquema bsico daqueles
para a ordem por linhas, com as dimenses invertidas. Deixamos essas equaes
para o leitor derivar.

Ordem por linhas


Nesta ordem, o clculo de endereo precisa encontrar o incio da linha e depois gerar um
deslocamento dentro dela como se fosse um vetor. Estendendo a notao que usamos
para descrever os limites de um vetor, acrescentamos subscritos a low e high que especificam uma dimenso. Assim, low1 refere-se ao limite inferior da primeira dimenso,
e high2 ao limite superior da segunda dimenso. Em nosso exemplo A[1...2,1...4], low1
1 e high2 4.
Para acessar o elemento A[i, j] , o compilador precisa emitir cdigo que calcula o
endereo da linha i, acompanhado do deslocamento para o elemento j, que sabemos
da Seo7.5.1 que (j low2 ) w . Cada linha contm quatro elementos, calculados como
high2low2+1, onde high2 a coluna de nmero mais alto e low2 a de nmero mais
baixo os limites superior e inferior da segunda dimenso de A. Para simplificar a
exposio, seja lenk=highklowk+1, o tamanho da dimenso k. Como as linhas
so dispostas consecutivamente, a linha i comea em (i low1 ) len2 w a partir do
incio de A. Isto sugere o clculo de endereo:

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].

No caso do vetor, pudemos simplificar o clculo quando os limites superior e inferior


eram conhecidos em tempo de compilao. A aplicao da mesma lgebra para criar
um falso zero no caso bidimensional produz:

O ltimo termo, (low1len2w+low2w), independente de i e j, de modo que


pode ser fatorado diretamente para o endereo de base:

7.5 Armazenamento e acesso a arrays 311

Agora, a referncia de array simplesmente:

Finalmente, podemos refatorar e mover o w para fora, evitando uma multiplicao


irrelevante:

Para o endereo de A[2, 3], isto avaliado como:

Como @A0 exatamente @A20, isto equivalente a @ A 20 + 44 = @ A + 24, o mesmo


local encontrado com a verso original do endereo de array polinomial.
Se considerarmos que i e j esto em ri e rj, e que len2 uma constante, essa forma
polinomial leva seguinte sequncia de cdigo:

Desta forma, reduzimos o clculo a duas multiplicaes e duas adies (uma no


loadAO). A segunda multiplicao pode ser reescrita como um shift.
Se o compilador no tiver acesso aos limites do array, deve calcular o falso zero em
tempo de execuo ou usar a forma polinomial mais complexa que inclui as subtraes
que ajustam limites inferiores. A primeira opo pode ser lucrativa se os elementos
do array forem acessados vrias vezes em um procedimento; calcular o falso zero na
entrada do procedimento permite que o cdigo use o clculo de endereo menos dispendioso. O clculo mais complexo s faz sentido se o array for acessado com pouca
frequncia.
As ideias por trs do clculo de endereo para os arrays com duas dimenses podem
ser generalizadas para arrays de dimenso maior. O polinmio de endereo para um
array armazenado em ordem por colunas pode ser obtido de modo semelhante. As
otimizaes que aplicamos para reduzir o custo dos clculos de endereo aplicam-se
igualmente bem aos polinmios de endereo para esses outros tipos de arrays.

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.

312 CAPTULO 7 Forma de cdigo

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.

Acesso a parmetros com valor de array


Quando um array passado como parmetro, a maior parte das implementaes o passa
por referncia. Mesmo em linguagens que usam a chamada por valor para todos os outros
parmetros, os arrays normalmente so passados por referncia. Considere o mecanismo
exigido para passar um array por valor. O chamador precisaria copiar o valor de cada
elemento de array para o registro de ativao do procedimento chamado. A passagem do
array como um parmetro por referncia pode reduzir bastante o custo de cada chamada.
Se o compilador tiver que gerar referncias de array no procedimento chamado, precisar de informaes sobre as dimenses do array que est vinculado ao parmetro. Em
FORTRAN, por exemplo, o programador precisa declarar o array usando constantes ou
outros parmetros formais para especificar suas dimenses. Assim, esta linguagem d ao
programador a responsabilidade por passar ao procedimento chamado as informaes
de que precisa para enderear corretamente um parmetro array.
Outras linguagens deixam a tarefa de coletar, organizar e passar as informaes necessrias para o compilador. O compilador constri um descritor que contm tanto um
ponteiro para o incio do array quanto a informao necessria para cada dimenso.
O descritor tem tamanho conhecido, mesmo quando o tamanho do array no pode ser
conhecido em tempo de compilao. Assim, o compilador pode alocar espao para o
descritor no AR do procedimento chamado. O valor passado no slot de parmetro do
array um ponteiro para esse descritor, chamado vetor dopado.

7.5 Armazenamento e acesso a arrays 313

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.

FIGURA 7.11 Vetores dopados.

Observe que o custo de acessar um parmetro com valor de array ou um array de


tamanho dinmico maior do que o custo de acessar um array local com limites
fixos. Na melhor das hipteses, o vetor dopado introduz referncias de memria
adicionais para acessar as entradas relevantes. Na pior, ele impede que o compilador
realize otimizaes que se baseiam no conhecimento completo de uma declarao
de array.

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.

314 CAPTULO 7 Forma de cdigo

7.5.4 Verificao de limites (range)


A maior parte das definies de linguagens de programao pressupe, explcita ou
implicitamente, que um programa se refere apenas a elementos de array dentro dos
limites definidos de um array. Um programa que referencia um elemento fora
dos limites, por definio, no est bem formado. Algumas linguagens (por exemplo, Java
e Ada) exigem que os acessos fora dos limites sejam detectados e relatados. Em outras,
os compiladores incluram mecanismos opcionais para detectar e relatar esses acessos.
A implementao mais simples da verificao de limites (range checking) insere
um teste antes de cada referncia de array. O teste verifica se cada valor de ndice se
encontra dentro da faixa vlida para a dimenso em que usado. Em um programa com
uso intenso de arrays, o overhead dessas verificaes pode ser significativo. Muitas
melhorias sobre este esquema simples so possveis. A alternativa menos dispendiosa
provar, no compilador, que uma determinada referncia no pode gerar uma referncia
fora dos limites.
Se o compilador pretender inserir verificaes de limites para parmetros com valor de
array, pode ter que incluir informaes adicionais nos vetores dopados. Por exemplo,
se o compilador utiliza o polinmio de endereo baseado no falso zero do array, tem
informaes de tamanho para cada dimenso, mas no de limite superior e inferior. Ele
pode realizar um teste impreciso verificando o deslocamento contra o tamanho global
do array. Porm, para realizar um teste preciso, precisa incluir os limites superior e
inferior para cada dimenso no vetor dopado e testar cada um deles.
Quando o compilador gera o cdigo de runtime para a verificao de limites, insere
muitas cpias do cdigo para relatar um subscrito fora dos limites. Os compiladores
otimizadores normalmente contm tcnicas que melhoram o cdigo de verificao de
limites. As verificaes podem ser combinadas. E podem ser movidas para fora dos
laos. Pode-se provar que elas so redundantes. Juntas, essas otimizaes podem reduzir
bastante o overhead da verificao de limites.

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.

7.6 Strings de caracteres 315

7.6 STRINGS DE CARACTERES


As operaes que as linguagens de programao oferecem para dados de caractere
so diferentes daquelas fornecidas para dados numricos. O nvel de suporte da
linguagem de programao para strings de caracteres varia do nvel de suporte
da linguagem C, na qual a maior parte da manipulao toma a forma de chamadas
para rotinas de biblioteca, at o nvel de suporte da PL/I, na qual a linguagem oferece
mecanismos de primeira classe para atribuir caracteres individuais, especificar
substrings quaisquer e concatenar strings para formar novas strings. Para ilustrar os
problemas que surgem na implementao de strings, esta seo discute a atribuio,
concatenao e clculo de tamanho de string.
As operaes com strings podem ser muito dispendiosas. As arquiteturas CISC mais antigas, como S/370da IBM e a VAX da DEC, oferecem bastante suporte para a manipulao
de strings. Arquiteturas RISC modernas contam mais com o compilador para codificar
essas operaes complexas usando um conjunto de operaes mais simples. A operao
bsica, copiar bytes de um local para outro, surge em muitos contextos diferentes.

7.6.1 Representaes de string


O construtor de compiladores precisa escolher uma representao para strings; os
detalhes desta representao tm forte impacto sobre o custo das operaes de string.
Para verificar este ponto, considere duas representaes comuns de uma string b. A
da esquerda tradicional nas implementaes C; usa um vetor de caracteres simples,
representa
com um caractere designado (\0) servindo como terminador. O smbolo
um espao em branco. A representao da direita armazena o tamanho da string (8)
junto com seu contedo. Muitas implementaes de linguagem tm usado este mtodo.

Se o campo de tamanho precisar de mais espao do que o terminador nulo, ento o


armazenamento do tamanho aumentar um pouco o tamanho da string na memria.
(Nossos exemplos consideram que o tamanho ocupa 4 bytes; na prtica, poderia ser
menor.) Todavia, o armazenamento do tamanho simplifica vrias operaes sobre
strings. Se uma linguagem permitir que strings de tamanho varivel sejam armazenadas
dentro de uma alocada com algum tamanho fixo, o implementador tambm poderia
armazenar o tamanho alocado com a string. O compilador pode usar o tamanho alocado
para verificao de limites em runtime na atribuio e concatenao.

7.6.2 Atribuio de strings


A atribuio de strings conceitualmente simples. Em C, a atribuio do terceiro
caractere de b ao segundo de a pode ser escrita como a[1] = b[2]. Em uma mquina com
operaes de memria com tamanho de caractere (cload e cstore), isto se traduz em
um cdigo simples, mostrado na margem. (Lembre-se de que o primeiro caractere
em a a[0], pois C usa zero como limite inferior de todos os arrays.)

316 CAPTULO 7 Forma de cdigo

Porm, se o hardware subjacente no admitir operaes de memria orientadas a


caractere, o compilador deve gerar um cdigo mais complexo. Supondo que a e b
comecem sobre fronteiras de palavra, que um caractere ocupa 1 byte e uma palavra
ocupe 4 bytes, o compilador poderia emitir o seguinte cdigo:

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:

7.6 Strings de caracteres 317

Se a mquina-alvo admite autoincremento sobre operaes load e store, os dois


adds no lao podem ser realizados nas operaes cload e cstore, o que reduz
o lao a quatro operaes. (Lembre-se de que C foi implementada originalmente no
PDP/11da DEC, que admitia o ps-incremento automtico.)
Sem o autoincremento, o compilador geraria um cdigo melhor usando cloadAO e
cstoreAO com um deslocamento comum. Esta estratgia s usaria uma operao
add dentro do lao.
Para conseguir uma execuo eficiente para strings longas alinhadas por palavra, o
compilador pode gerar cdigo que usa loads e stores de palavra completa, seguido por um
lao orientado a caractere para tratar de quaisquer caracteres restantes ao final da string.
Se o processador no tiver operaes de memria orientadas a caractere, o cdigo ser
mais complexo. O compilador poderia substituir o load e o store no corpo do lao por
uma generalizao do esquema para mascarar e deslocar caracteres isolados mostrados
na atribuio de nico caractere. O resultado um lao funcional, porm feio, que exige
muito mais instrues para copiar b para a.
As vantagens dos laos orientados a caractere so a simplicidade e a generalidade.
Este tipo de lao trata dos casos incomuns, porm complexos, como a sobreposio de
substrings e strings com diferentes alinhamentos. A desvantagem sua ineficincia em
relao a um lao que move blocos de memria maiores em cada iterao. Na prtica,
o compilador poderia muito bem chamar uma rotina de biblioteca cuidadosamente
otimizada para implementar os casos no triviais.

7.6.3 Concatenao de strings


Concatenao simplesmente uma abreviao para uma sequncia de uma ou mais
atribuies. E pode ter duas formas bsicas: anexar a string b string a, e criar uma
nova string que contm a seguida imediatamente por b.
O primeiro caso um clculo de tamanho seguido por uma atribuio. O compilador
emite cdigo para determinar o tamanho de a. Se o espao permitir, ele realiza uma
atribuio de b ao espao que vem imediatamente aps o contedo de a. (Se no
houver espao suficiente, o cdigo gera um erro em tempo de execuo.) O segundo
caso exige a cpia de cada caractere em a e cada caractere em b. O compilador trata a
concatenao como um par de atribuies e gera cdigo para as atribuies.
De qualquer forma, o compilador deve garantir que haja espao suficiente alocado para
manter o resultado. Na prtica, ou o compilador ou o sistema de runtime deve conhecer o

318 CAPTULO 7 Forma de cdigo

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.

7.6.4 Tamanho de string


Os programas que manipulam strings normalmente precisam calcular o tamanho de uma
string de caracteres. Nos programas em C, a funo strlen da biblioteca padro toma
uma string como seu argumento e retorna o tamanho dela, expresso como um inteiro. Em
PL/I, a funo interna length realiza a mesma funo. As duas representaes de string
descritas anteriormente levam a custos radicalmente diferentes para o clculo do tamanho.
1. String terminada em nulo. O clculo de tamanho deve comear no incio da
string e examinar cada caractere, em ordem, at que alcance o nulo. O cdigo
semelhante ao lao de cpia de caracteres em C, e exige tempo proporcional ao
tamanho da string.
2. Campo de tamanho explcito. O clculo de tamanho uma referncia de memria.
Em ILOC, isto se torna um loadI do endereo inicial da string para um registrador, seguido por um loadAI para obter o tamanho. O custo constante e pequeno.
O compromisso entre essas representaes simples. O terminador nulo economiza
uma pequena quantidade de espao, mas exige mais cdigo e mais tempo para o clculo
do tamanho. Um campo de tamanho explcito custa uma palavra a mais por string, mas
faz com que o clculo do tamanho tome um tempo constante.
Um exemplo clssico de um problema de otimizao de string encontrar o tamanho que resultaria da concatenao de duas strings, a e b. Em uma linguagem com
operadores de string, isto poderia ser escrito como length(a + b) , onde + significa
concatenao. Esta expresso tem duas implementaes bvias: construir a string
concatenada e calcular seu tamanho ( strlen(strcat(a, b) em C) ou somar os tamanhos de a e b ( strlen(a) + strlen(b) em C). A segunda soluo, naturalmente,
desejada. Com um campo de tamanho explcito, a operao pode ser otimizada para
usar dois load s e um add.

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?

7.7 Referncias de estrutura 319

7.7 REFERNCIAS DE ESTRUTURA


A maior parte das linguagens de programao fornece um mecanismo para agregar
dados em uma estrutura. A estrutura C tpica; agrega elementos com nomes individuais, normalmente de tipos diferentes. Uma implementao de lista, em C, poderia,
por exemplo, usar a seguinte estrutura para criar listas de inteiros:

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.

7.7.1 Entendendo layouts de estrutura


Quando o compilador emite cdigo para referncias de estrutura, precisa conhecer o
endereo inicial da estrutura e o deslocamento e o tamanho de cada elemento da estrutura. Para manter estes fatos, o compilador pode construir uma tabela separada de
layouts de estrutura. Esta tabela de tempo de compilao precisa incluir o nome textual
para cada elemento da estrutura, seu deslocamento dentro da estrutura e seu tipo de
dados na linguagem-fonte. Para o exemplo anterior de lista, o compilador poderia construir as tabelas mostradas na Figura7.12. As entradas na tabela de elementos utilizam
nomes totalmente qualificados para evitar conflitos devido ao reso de um nome em
vrias estruturas distintas.

FIGURA 7.12 Tabelas de estrutura para o exemplo de lista.

320 CAPTULO 7 Forma de cdigo

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

Aqui, o compilador encontra o deslocamento de next seguindo a tabela a partir da


entrada node na tabela de estrutura at a cadeia de entradas para node na tabela de
elementos. Percorrendo essa cadeia, ele encontra a entrada para node.next e seu
deslocamento, 4.
Para realizar o layout de uma estrutura e a atribuio de deslocamentos aos seus elementos, o compilador precisa obedecer s regras de alinhamento da arquitetura-alvo, o
que poder for-lo a deixar um espao no usado na estrutura. O compilador enfrenta
este problema quando estabelece a estrutura declarada esquerda:

Elementos em ordem de declarao

Elementos ordenados por alinhamento


O desenho no alto, direita, mostra o layout da estrutura se o compilador estiver restrito
a colocar os elementos na ordem de declarao. Como fie e fum precisam ser ali
nhados em uma palavra dupla, o compilador precisa inserir um preenchimento aps fee
e foe. Se o compilador pudesse ordenar os elementos na memria de forma arbitrria,
poderia usar o layout apresentado embaixo, direita, que no precisa de preenchimento.
Este uma questo de projeto de linguagem: a definio da linguagem especifica se o
layout de uma estrutura ou no exposto ao usurio.

7.7.2 Arrays de estruturas


Muitas linguagens de programao permitem que o usurio declare um array de
estruturas. Se ele tiver permisso para apanhar o endereo de um elemento com
valor de um array de estrutura, ento o compilador deve dispor os dados na memria
como mltiplas cpias do layout de estrutura. Se o programador no puder apanhar o endereo de um elemento com valor de array de estrutura, o compilador
poderia dispor a estrutura como se fosse uma estrutura composta de elementos
que, por si s, so arrays. Dependendo de como o cdigo acessa os dados, essas
duas estratgias podem ter desempenho notavelmente diferente em um sistema
com memria cache.
Para enderear um array de estruturas disposto como mltiplas cpias da estrutura, o
compilador usa os polinmios de endereo de array descritos na Seo 7.5. O tamanho
global da estrutura, incluindo qualquer preenchimento necessrio, torna-se o tamanho
de elemento w no polinmio de endereo. O polinmio gera o endereo do incio da

7.7 Referncias de estrutura 321

estrutura. Para obter o valor de um elemento especfico, o deslocamento do elemento


somado ao endereo da estrutura.
Se o compilador tiver disposto a estrutura com elementos que so arrays, deve
calcular o local inicial do array de elementos usando a informao da tabela de
deslocamento e a dimenso do array. Esse endereo pode ento ser usado como o
ponto de partida para um clculo de endereo usando o polinmio apropriado de
endereo de array.

7.7.3 Unies e etiquetas de runtime


Muitas linguagens permitem que o programador crie uma estrutura com mltiplas
interpretaes dependentes dos dados. Em C, a construo de unio tem este efeito.
Pascal conseguiu o mesmo efeito com seus registros variantes.
Unies e variantes apresentam uma complicao adicional. Para emitir cdigo para
uma referncia a um elemento de uma unio, o compilador precisa associar a referncia
a um deslocamento especfico. Como uma unio est construda a partir de vrias
definies de estrutura, existe a possibilidade de que os nomes de elemento no sejam
exclusivos. O compilador precisa associar cada referncia a um deslocamento e tipo
nicos no objeto de runtime.
Este problema tem uma soluo lingustica. A linguagem de programao pode forar
o programador a tornar a referncia no ambgua. Considere as declaraes em C
apresentadas na Figura7.13. O painel a mostra declaraes para dois tipos de n, um
que mantm um valor inteiro e outro, um valor de ponto flutuante.
O cdigo no painel b declara uma unio chamada one que um n1 ou um n2. Para
referenciar um inteiro value, o programador especifica u1.inode.value. Para
referenciar um value, de ponto flutuante, especifica u1.fnode.value. O nome
totalmente qualificado resolve qualquer ambiguidade.
O cdigo no painel c declara uma unio chamada two, que tem as mesmas
propriedades de one. A declarao de two mostra explicitamente sua estrutura
interna. Entretanto, o mecanismo lingustico para remover a ambiguidade de uma
referncia ao valor o mesmo o programador especifica um nome totalmente
qualificado.
Alternativamente, alguns sistemas tm contado com a discriminao em tempo de
execuo. Aqui, cada variante na unio tem um campo que a distingue de todas

FIGURA 7.13 Declaraes de unio em C.

322 CAPTULO 7 Forma de cdigo

as outras variantes uma etiqueta (tag). (Por exemplo, a declarao de two


poderia inicializar kind como um para inode e dois para fnode.) O compilador
pode, assim, emitir o cdigo para verificar o valor do campo de etiqueta e garantir
que cada objeto seja tratado corretamente. Basicamente, emite uma instruo case
com base no valor da etiqueta. A linguagem pode exigir que o programador defina o
campo de etiqueta e seus valores; como alternativa, o compilador poderia gerar e inserir
etiquetas automaticamente. Neste ltimo caso, o compilador tem uma forte motivao
para realizar a verificao de tipo e remover o mximo de verificaes possvel.

7.7.4 Ponteiros e valores annimos


Um programa em C pode criar uma estrutura de duas maneiras. Pode declarar a estrutura, como com NilNode no exemplo anterior. Alternativamente, o cdigo pode
alocar explicitamente uma estrutura. Para uma varivel fee declarada como um
ponteiro para node, a alocao se pareceria com:
O nico acesso a este novo node ocorre devido ao ponteiro fee. Assim, pensamos
nele como um valor annimo, pois no possui um nome permanente.
Como o nico nome para um valor annimo um ponteiro, o compilador no pode
determinar facilmente se duas referncias de ponteiro especificam o mesmo local da
memria. Considere o fragmento de cdigo

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

7.8 Construes de controle de fluxo 323

ponteiro normalmente so menos eficientes do que as computaes correspondentes


em valores de locais no ambguos.
Efeito semelhante ocorre para o cdigo que faz uso intenso de arrays. A menos que o
compilador realize uma anlise profunda dos subscritos de array, pode no ser capaz
de determinar se duas referncias de array se sobrepem. Quando ele no pode distinguir entre duas referncias, como em a[i, j, k] e a a[i, j,l], deve tratar ambas de
modo conservador. O problema de remover a ambiguidade de referncias de array,
embora desafiador, mais fcil do que o de remover a ambiguidade de referncias
de ponteiro.
A anlise para remover a ambiguidade das referncias de ponteiro e de array uma
fonte de melhoria em potencial no desempenho do programa. Para programas com uso
intenso de ponteiros, o compilador pode realizar uma anlise interprocedimental de
fluxo de dados que visa descobrir, para cada ponteiro, o conjunto de objetos para os
quais ele pode apontar. Para programas com uso intenso de array, o compilador pode
usar a anlise de dependncia de dados para entender os padres de referncias de array.

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?

7.8 CONSTRUES DE CONTROLE DE FLUXO


Um bloco bsico apenas uma sequncia com tamanho mximo de cdigo em linha reta,
no predicado. Qualquer instruo que no afete o fluxo de controle pode aparecer dentro
de um bloco. Qualquer transferncia de fluxo de controle termina o bloco, assim como uma

A anlise de dependncia de dados est fora do escopo


deste livro. Consulte [352,20,270].

324 CAPTULO 7 Forma de cdigo

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.

7.8.1 Execuo condicional


A maior parte das linguagens de programao fornece alguma verso de construo
if-then-else. Dado o texto-fonte:

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.

7.8 Construes de controle de fluxo 325

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.

PREDIO DE DESVIO PELOS USURIOS


Uma lenda urbana de compiladores trata da predio de desvio. FORTRAN tem
uma instruo if aritmtico que usa um dentre trs desvios, dependendo se a
expresso de controle avaliada como um valor negativo, zero ou positivo. Um antigo
compilador permitia que o usurio fornecesse um peso para cada rtulo que refletisse
a probabilidade relativa de tomar esse desvio. E, ento, usava os pesos para ordenar os
desvios de um modo que minimizasse o atraso esperado total a partir do desvio.
Depois que o compilador estava em campo por um ano, diz a histria, um
mantenedor descobriu que os pesos estavam sendo usados na ordem contrria,
maximizando o atraso esperado. Ningum havia reclamado. A histria geralmente
contada como uma fbula sobre o valor das opinies dos programadores sobre o
comportamento do cdigo que eles escrevem. (Naturalmente, ningum relatou a
melhoria, se que houve alguma, com o uso de pesos de desvio na ordem correta.)

FIGURA 7.14Predicao versus ramificao.

326 CAPTULO 7 Forma de cdigo

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.

7.8.2 Laos e iterao


A maioria das linguagens de programao inclui construes de lao para realizar
iterao. O primeiro compilador FORTRAN introduziu o lao do para esta realizao.
Hoje, os laos so encontrados em muitas formas. Em sua maior parte, tm uma estrutura semelhante.
Considere o lao for em C como exemplo. A Figura7.15 mostra como o compilador
poderia dispor o cdigo. O lao for tem trs expresses de controle e1, que fornece
inicializao; e2, que avaliada como um booleano e controla a execuo do lao; e e3,

7.8 Construes de controle de fluxo 327

FIGURA 7.15 Esquema geral para layout de um lao for.

que executada no final de cada iterao e, potencialmente, atualiza os valores usados


em e2. Usaremos esta figura como o esquema bsico para explicar a implementao
de vrios tipos de laos.
Se o corpo do lao consiste em um nico bloco bsico ou seja, no contm outro
fluxo de controle , ento o lao que resulta deste esquema tem um desvio inicial
mais um desvio por iterao. O compilador poderia ocultar a latncia desse desvio
de duas maneiras. Se a arquitetura lhe permitir que preveja se o desvio tomado ou
no, ele deve prever o desvio na etapa 4 como sendo tomado (para iniciar a prxima
iterao). Se a arquitetura lhe permitir mover as instrues para o(s) slot(s) de atraso
do desvio, ele deve tentar preencher o(s) slot(s) de atraso com instruo(es) a partir
do corpo do lao.

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:

O cdigo produzido nas etapas 1, 2 e 4 simples. Se o corpo do lao (etapa 3)


consistir em um nico bloco bsico ou terminar com um nico bloco bsico, ento
o compilador pode otimizar a atualizao e teste produzidos na etapa 4 com o corpo

328 CAPTULO 7 Forma de cdigo

do lao, o que pode levar a melhorias no cdigo por exemplo, o escalonador de


instrues poderia usar operaes do final da etapa 3 para preencher slots de atraso
no desvio da etapa 4.
O compilador tambm pode modelar o lao de modo que tenha apenas uma cpia
do teste aquela na etapa 2. Dessa forma, a etapa 4 avalia e3 e depois salta para
a etapa 2. O compilador substituiria a sequncia cmp_LE, cbr ao final do lao
por um jumpl. Esta forma do lao uma operao a menos que a forma de dois
testes. Porm, cria um lao de dois blocos at mesmo para os laos mais simples, o
que estende o caminho pelo lao em pelo menos uma operao. Quando o tamanho
do cdigo uma considerao sria, o uso consistente desta forma de lao mais
compacta pode ser valioso. Desde que o salto de fim de lao seja um salto imediato,
o hardware pode tomar medidas para minimizar qualquer problema que isto possa
causar.
A forma cannica de lao da Figura7.15 tambm prepara o palco para a otimizao
posterior. Por exemplo, se e1 e e2 tiverem apenas constantes conhecidas, como no
exemplo, o compilador pode passar o valor da etapa 1 para o teste na etapa 2 e eliminar
o compare & desvie (se o controle entrar no lao) ou eliminar o corpo do lao (se
o controle nunca entrar no lao). No lao de teste nico, o compilador no pode fazer
isto. Ao invs disso, ele encontra dois caminhos levando ao teste um da etapa 1 e
um da etapa 4. O valor usado no teste, ri, tem um valor varivel ao longo da aresta da
etapa 4, de modo que o resultado do teste no previsvel.

Lao do em FORTRAN
Em FORTRAN, o lao iterativo um lao do. semelhante ao lao for em C, mas
tem uma forma mais restrita.

Os comentrios mapeiam partes do cdigo ILOC para o esquema da Figura7.15.


A definio de FORTRAN, como a de muitas linguagens, tem alguns detalhes interessantes. Uma peculiaridade relaciona-se aos laos do e suas variveis de ndice.
O nmero de iteraes de um lao fixado antes que a execuo entre no lao. Se o
programa mudar o valor da varivel de ndice, esta mudana no afeta o nmero de
iteraes que so executadas. Para garantir o comportamento correto, o compilador
pode ter que gerar uma varivel de ndice oculta, chamada varivel sombra de ndice,
para controlar a iterao.

7.8 Construes de controle de fluxo 329

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.

A replicao do teste na etapa 4 cria a possibilidade de um lao com um nico bloco


bsico. Os mesmos benefcios que advm de um lao for a partir desta estrutura
tambm ocorrem para um while.

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.

Expressando a iterao como recurso de cauda


Em linguagens como LISP, a iterao normalmente implementada (pelos programadores) usando uma forma estilizada de recurso. Se a ltima ao executada por uma
funo for uma chamada, esta conhecida como chamada de cauda. Por exemplo, para
encontrar o ltimo elemento de uma lista em Scheme, o programador poderia escrever
a seguinte funo simples:

Os compiladores normalmente sujeitam chamadas de cauda a um tratamento especial,


pois eles podem gerar chamadas particularmente eficientes para elas (ver Seo 10.4.1).
A recurso de cauda pode ser usada para conseguir os mesmos efeitos da iterao,
como no cdigo Scheme a seguir:

330 CAPTULO 7 Forma de cdigo

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.

7.8.3 Instrues case


Chamada de cauda
Chamada de procedimento que ocorre como a ltima
ao em algum procedimento conhecida como
chamada de cauda. Uma chamada de cauda recursiva
denominada uma recurso de cauda.

Muitas linguagens de programao incluem alguma variante de uma instruo case.


FORTRAN tem seu goto calculado. Algol-W introduziu a instruo case em sua forma
moderna. BCPL e C tm uma construo switch, enquanto PL/I, uma construo
generalizada que mapeia bem em um conjunto aninhado de instrues if-then-else.
Como a introduo deste captulo sugeriu, a implementao de uma instruo case de
modo eficiente algo complexo.
Considere a implementao da instruo switch de C. A estratgia bsica simples:
(1) avaliar a expresso de controle; (2) desviar para o caso selecionado; e (3) executar o
cdigo para esse caso. As etapas 1 e 3 so bem entendidas, pois seguem de discusses
anteriores deste captulo. Em C, os casos individuais normalmente terminam com uma
instruo break que sai da instruo switch.
A parte complexa da implementao da instruo case est na escolha de um mtodo
eficiente para localizar o caso designado. Como o caso desejado no conhecido
antes da execuo, o compilador precisa emitir cdigo que usar o valor da expresso
de controle para localizar o caso correspondente. Nenhum mtodo isolado funciona
bem para todas as instrues case. Muitos compiladores tm proviso para vrios esquemas de busca diferentes e escolhem entre eles com base nos detalhes especficos
do conjunto de casos.
Esta seo examina trs estratgias: busca linear, busca binria e endereo calculado.
Cada estratgia apropriada sob diferentes circunstncias.

7.8 Construes de controle de fluxo 331

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.

FIGURA 7.16 Instruo case implementada com busca linear.

Clculo de endereo diretamente


Se os rtulos de caso formarem um conjunto compacto, o compilador pode fazer melhor
do que a pesquisa binria. Considere a instruo switch mostrada na Figura7.17a.
Ela tem os rtulos de caso de zero a nove, mais um caso default. Para esse cdigo,
o compilador pode construir um vetor compacto, ou tabela de saltos, que contm os
rtulos de bloco, e encontrar o rtulo apropriado pelo ndice na tabela. A tabela de
saltos mostrada na Figura7.17b, enquanto o cdigo para calcular o rtulo de caso
correto aparece na Figura7.17c. O cdigo de busca assume que a tabela de saltos
armazenada em @Table e que cada rtulo ocupa quatro bytes.
Para um conjunto de rtulos denso, esse esquema gera cdigo compacto e eficiente.
O custo pequeno e constante um breve clculo, uma referncia de memria e um
jump. Se houver uns poucos buracos no conjunto de rtulos, o compilador preencher
esses slots com o rtulo para o caso default. Se no existir um caso default, a ao
apropriada depende da linguagem. Em C, por exemplo, o cdigo dever desviar para
a primeira instruo aps o switch, de modo que o compilador pode colocar esse
rtulo em cada buraco na tabela. Se a linguagem tratar um caso ausente como um erro,
como PL/I fazia, o compilador pode preencher os buracos na tabela de saltos com o
rtulo de um bloco que dispara o apropriado erro de runtime.

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

332 CAPTULO 7 Forma de cdigo

FIGURA 7.17 Instruo case implementada com clculo direto de endereo

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.

A forma exata do lao de busca pode variar. Por


exemplo, o cdigo na figura no realiza o curto-circuito
do caso quando encontra o rtulo mais cedo. O teste
emprico de diversas variantes escritas no cdigo assembly da mquina-alvo necessrio para encontrar as
melhores escolhas.

A Figura7.18a mostra nosso exemplo de instruo case, reescrito com um conjunto


de rtulos diferente. Para a figura, vamos considerar os rtulos 0, 15, 23, 37, 41, 50,
68, 72, 83 e 99, bem como um caso default. Os rtulos poderiam, claro, cobrir uma
faixa muito maior. Para tal instruo case, o compilador poderia montar uma tabela
de busca como aquela mostrada na Figura7.18b e gerar uma busca binria, como na
Figura7.18c, para localizar o caso desejado. Se o comportamento fall-through for permitido, como em C, o compilador precisa garantir que os blocos apaream na memria
em sua ordem original.
Em uma busca binria ou clculo direto de endereo, o construtor de compiladores
deve garantir que o conjunto potencial de destinos de salto seja visvel na IR, usando
uma construo como a pseudo-operao tbl da ILOC (ver Apndice A.4.2). Essas
dicas simplificam a anlise posterior e tornam seus resultados mais precisos.

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.

7.9 Chamadas de procedimento 333

FIGURA 7.18 Instruo case implementada com busca binria.

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?

7.9 CHAMADAS DE PROCEDIMENTO


A implementao de chamadas de procedimento , na maior parte, simples. Como
vemos na Figura7.19, uma chamada de procedimento consiste em duas sequncias,
de pr-chamada e de ps-retorno no chamador, e um prlogo e um eplogo no procedimento chamado. Um nico procedimento pode conter vrios locais de chamada,
cada um com suas prprias sequncias de pr-chamada e ps-retorno. Na maioria
das linguagens, um procedimento tem um ponto de entrada, de modo que tem uma
sequncia de prlogo e uma de eplogo. (Algumas linguagens permitem vrios pontos

FIGURA 7.19 Ligao padro de procedimento.

Tabela de saltos
Vetor de rtulos usado para transferir o controle com
base em um ndice calculado para a tabela.

334 CAPTULO 7 Forma de cdigo

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.

7.9.1 Avaliao de parmetros reais


Ao construir a sequncia de pr-chamada, o compilador precisa emitir cdigo para
avaliar os parmetros reais da chamada. Ele trata cada parmetro real como uma expresso. Para um parmetro de chamada por valor, a sequncia de pr-chamada avalia
a expresso e armazena seu valor em um local designado para esse parmetro ou em
um registrador ou no AR do procedimento chamado. Para um parmetro de chamada
por referncia, a sequncia de pr-chamada avalia o parmetro para um endereo e
armazena este endereo em um local designado para esse parmetro. Se um parmetro
de chamada por referncia no tiver local de armazenamento, ento o compilador pode
ter que alocar espao para manter o valor do parmetro de modo que tenha um endereo
para passar ao procedimento chamado.
Se a linguagem-fonte especificar uma ordem de avaliao para os parmetros reais, o
compilador deve, naturalmente, segui-la. Caso contrrio, deve usar uma ordem consistente seja da esquerda para a direita ou da direita para a esquerda. A ordem de
avaliao importa para os parmetros que possam ter efeitos colaterais. Por exemplo,
um programa que usasse duas rotinas push e pop para manipular uma pilha produziria
diferentes resultados para a sequncia subtract(pop( ), pop( )) sob avaliaes da
esquerda para a direita e da direita para a esquerda.
Os procedimentos normalmente tm vrios argumentos implcitos, que incluem o ARP
do procedimento, o ARP do chamador, o endereo de retorno e quaisquer informaes
necessrias para estabelecer a endereabilidade. As linguagens orientadas a objeto
passam o receptor como um parmetro implcito. Alguns desses argumentos so passados em registradores, enquanto outros normalmente residem na memria. Muitas
arquiteturas possuem uma operao como

7.9 Chamadas de procedimento 335

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.

7.9.2 Salvamento e restaurao de registradores


Sob qualquer conveno de chamada, o procedimento chamador ou o chamado (ou
ambos) deve preservar valores de registrador. Normalmente, as convenes de ligao
utilizam uma combinao de salvamentos de registradores pelo chamador e pelo
chamado. Como tanto o custo das operaes de memria quanto o nmero de registradores tm aumentado, o custo de salvar e restaurar registradores nos locais de
chamada aumentou, ao ponto de merecer uma ateno cuidadosa.
Na escolha de uma estratgia para salvar e restaurar registradores, o construtor de
compiladores deve considerar a eficincia e o tamanho do cdigo. Alguns recursos
do processador afetam essa escolha. Recursos que derramam uma parte do conjunto
de registradores podem reduzir o tamanho do cdigo; alguns exemplos so janelas de
registrador nas mquinas SPARC, as operaes load e store com mltiplas palavras nas
arquiteturas Power e a operao de chamada de alto nvel no VAX. Cada um oferece
ao compilador um modo compacto de salvar e restaurar alguma parte do conjunto de
registradores.
Embora conjuntos de registradores maiores possam aumentar o nmero de registradores que o cdigo salva e restaura, em geral, o uso desses registradores adicionais
melhora a velocidade do cdigo resultante. Com menos registradores, o compilador
seria forado a gerar loads e stores por meio do cdigo; com mais registradores, muitos
dos derramamentos ocorrem somente em um local de chamada. (O conjunto de registradores maior deve reduzir o nmero total de derramamentos no cdigo.) A concentrao de salvamentos e restauraes nos locais de chamada apresenta ao compilador
oportunidades para melhor tratar deles do que poderia se estivessem espalhados pelo
procedimento inteiro.
j

Uso de operaes de memria com mltiplos registradores. Ao salvar e restaurar


registradores adjacentes, o compilador pode usar uma operao de memria
com mltiplos registradores. Muitas ISAs admitem operaes load e store com
palavra dupla e qudrupla. O uso dessas operaes pode reduzir o tamanho do
cdigo; e tambm melhorar a velocidade de execuo. As operaes de memria
com mltiplos registradores generalizadas podem ter o mesmo efeito.
j Uso de uma rotina de biblioteca. medida que o nmero de registradores aumenta, as sequncias de pr-chamada e ps-retorno aumentam. O construtor de
compiladores pode substituir a sequncia de operaes de memria individuais
por uma chamada a uma rotina de salvamento ou restaurao fornecida pelo
compilador. Feita em todas as chamadas, esta estratgia pode produzir economias significativas no tamanho do cdigo. Como as rotinas de salvamento ou
restaurao so conhecidas apenas pelo compilador, podem usar uma sequncia
de chamada mnima para manter baixo o custo de runtime.

336 CAPTULO 7 Forma de cdigo

As rotinas de salvamento ou restaurao podem tomar um argumento que


especifica quais registradores devem ser preservados. Pode ser valioso gerar
verses otimizadas para os casos comuns, como preservar todos os registradores
de salvamentos do chamador ou chamado.
j Combinao de responsabilidades. Para reduzir ainda mais o overhead, o
compilador poderia combinar o trabalho de salvamento de registradores pelo
chamador e pelo chamado. Nesse esquema, o chamador passa um valor ao
chamado que especifica quais registradores ele dever salvar. O procedimento
chamado acrescenta a esse valor os registradores que ele precisa salvar e chama
a rotina apropriada de salvamento fornecida pelo compilador. O eplogo passa
o mesmo valor para a rotina de restaurao, de modo que possa recarregar os
registradores necessrios. Esta tcnica limita o overhead a uma chamada para
salvar registradores e uma para restaur-los, e, assim, separa a responsabilidade
(salvamentos pelo chamador versus salvamentos pelo chamado) do custo para
chamar a rotina.
O construtor de compiladores precisa prestar muita ateno s implicaes das diversas
opes sobre o tamanho do cdigo e da velocidade de execuo. O cdigo deve usar as
operaes mais rpidas para salvamentos e restauraes, o que exige um exame atento
dos custos das operaes de nico e de mltiplos registradores na arquitetura-alvo.
Ouso de rotinas de biblioteca para realizar salvamentos e restauraes pode economizar
espao; a implementao cuidadosa dessas rotinas de biblioteca pode reduzir o custo
adicional de cham-las.

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?

7.10 Resumo e perspectiva 337

7.10 RESUMO E PERSPECTIVA


Uma das tarefas mais sutis que o construtor de compiladores enfrenta selecionar um
padro de operaes da mquina-alvo para implementar cada construo da linguagem
fonte. Mltiplas estratgias de implementao so possveis para quase toda instruo
da linguagem-fonte. As escolhas especficas feitas em tempo de projeto tm forte
impacto sobre o cdigo que o compilador gera.
Em um compilador que no voltado para uso em produo um compilador de
depurao ou um de aluno , o construtor do compilador poderia selecionar tradues
fceis de implementar para cada estratgia, que produzam cdigo simples, compacto.
Em um compilador otimizador, o construtor de compiladores deve focalizar tradues que exponham o mximo de informaes possveis para as prximas fases do
compilador otimizao de baixo nvel, escalonamento de instrues e alocao
de registradores. Essas duas perspectivas diferentes levam a diferentes formas para
laos, diferentes disciplinas para nomeao de variveis temporrias e, possivelmente,
diferentes ordens de avaliao para expresses.
O exemplo clssico dessa distino a instruo case. Em um compilador de depurao, a implementao como uma srie de construes if-then-else em cascata
razovel. J em um compilador otimizador, a ineficincia desses inmeros testes e
desvios justifica um esquema de implementao mais complexo. O esforo para melhorar a instruo case precisa ser feito quando a IR for gerada; poucos otimizadores
(se houver algum) convertero uma srie de condicionais em cascata para uma busca
binria ou para uma tabela de salto direto.

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.

338 CAPTULO 7 Forma de cdigo

A otimizao de verificaes de limites tem uma longa histria. O compilador PL/.8


insistia em verificar cada referncia; a otimizao reduziu o overhead [257]. Mais
recentemente, Gupta e outros estenderam essas ideias para aumentar o conjunto de
verificaes que podem ser movidas para o tempo de compilao [173].

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:

Desenhe um mapa da memria para essas variveis:


a. Supondo que o compilador no possa reordenar as variveis.
b. Supondo que o compilador possa reordenar as variveis para economizar espao.
2. Conforme demonstrado na questo anterior, o compilador precisa de um
algoritmo para ajustar locais de memria dentro de uma rea de dados. Suponha
que o algoritmo receba como entrada uma lista de variveis, seus tamanhos e
suas restries de alinhamento, como em
a, 4, 4, b, 1, 3, c, 8, 8, d, 4, 4, e, 1, 4, f, 8, 16, g, 1, 1.
O algoritmo deve produzir, como sada, uma lista de variveis e seus deslocamentos na rea de dados. O objetivo do algoritmo minimizar o espao no
usado, ou desperdiado.
a. Escreva um algoritmo para estabelecer o layout de uma rea de dados com o
mnimo de espao desperdiado.
b. Aplique seu algoritmo lista do exemplo acima e duas outras que voc
projete para demonstrar os problemas que podem surgir no layout do
armazenamento.
c. Qual a complexidade do seu algoritmo?
3. Para cada um dos tipos de varivel a seguir, indique onde, na memria, o compilador poderia alocar o espao para tal varivel. As respostas possveis incluem
registradores, registros de ativao, reas de dados estticas (com diferentes
visibilidades) e heap de runtime.
a. Uma varivel local a um procedimento.
b. Uma varivel global.
c. Uma varivel global alocada dinamicamente.
d. Um parmetro formal.
e. Uma varivel temporria gerada pelo compilador.
Seo 7.3
4. Use o algoritmo de gerao de cdigo de travessia em rvore, da Seo 7.3,
para gerar um cdigo simples para a rvore de expresso a seguir. Considere um
conjunto ilimitado de registradores.

7.10 Resumo e perspectiva 339

5. Encontre o nmero mnimo de registradores exigidos para avaliar as seguintes


rvores usando o conjunto de instrues ILOC. Para cada n no folha, indique
quais de seus filhos devem ser avaliados primeiro a fim de conseguir esse nmero
mnimo de registradores.

6. Construa rvores de expresso para as duas expresses aritmticas a seguir,


usando a precedncia padro e a avaliao da esquerda para a direita. Calcule o
nmero mnimo de registradores exigidos para avaliar cada uma delas usando o
conjunto de instrues ILOC.
a. ((a + b) + (c + d)) + ((e + f) + (g + h))
b. a + b + c + d + e + f + g + h

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?

340 CAPTULO 7 Forma de cdigo

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?

7.10 Resumo e perspectiva 341

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:

mostre o cdigo que um compilador geraria para armazenar na varivel g o valor


do grau do i-simo elemento de grades, supondo o seguinte:
a. O array grades armazenado como um array de estruturas.
b. O array grades armazenado como uma estrutura de arrays.

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?

342 CAPTULO 7 Forma de cdigo

c. Como suas respostas mudam se x fosse passado como um parmetro de


chamada por valor?
19. A conveno de ligao um contrato entre o compilador e quaisquer chamadores externos do cdigo compilado. Ela cria uma interface conhecida que pode ser
usada para chamar um procedimento e obter quaisquer resultados que ele retorna
(enquanto protege o ambiente de runtime do chamador). Assim, o compilador s
deveria violar a conveno de ligao quando tal violao no puder ser detectada de fora do cdigo compilado.
a. Sob quais circunstncias o compilador pode estar certo de que o uso de
uma variao da conveno de ligao seguro? D exemplos obtidos de
linguagens de programao reais.
b. Nessas circunstncias, o que o compilador poderia mudar sobre a sequncia
de chamada e a conveno de ligao?

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

344 CAPTULO 8 Introduo otimizao

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.

Antes de implementar uma transformao, o construtor de compiladores deve entender


quando ela pode ser aplicada com segurana e quando esperar lucro de sua aplicao.
A Seo 8.2 explora segurana e lucratividade. A Seo 8.3 apresenta as diferentes
granularidades, ou escopos, sobre as quais a otimizao ocorre. O restante do captulo
usa exemplos selecionados para ilustrar diferentes fontes de melhoria e diferentes escopos de otimizao. Este captulo no inclui uma seo de Tpicos avanados; os
Captulos9 e10 servem a esta finalidade.

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 Fundamentos 345

distino entre compiladores depuradores e compiladores otimizadores. Os


primeiros enfatizam a compilao rpida custa da qualidade do cdigo. Esses
compiladores no rearranjam o cdigo de modo significativo, por isso permanece
uma correspondncia forte entre o cdigo-fonte e o executvel, simplificando
a tarefa de mapeamento entre um erro de execuo e uma linha especfica do
cdigo-fonte; da o termo depurador. Ao contrrio, um compilador otimizador
focaliza a melhoria do tempo de execuo docdigo executvel, custa do tempo
de compilao. Gastar mais tempo na compilao normalmente produz um cdigo
melhor. Como o otimizador frequentemente movimenta operaes, o mapeamento
entre cdigo-fonte e cdigo executvel menos transparente, e a depurao, por
consequncia, mais difcil.
Quando os processadores RISC entraram no mercado (e as tcnicas de implementao
RISC foram aplicadas a arquiteturas CISC), mais peso pelo desempenho de runtime
caiu sobre os compiladores. Para aumentar o desempenho, os arquitetos de processador
voltaram-se para recursos que exigem mais suporte do compilador, que incluem slots
de atraso aps desvios, operaes de memria sem bloqueio, maior uso de pipelines e
maior nmero de unidades funcionais. Esses recursos tornam os processadores mais
sensveis ao desempenho tanto para as questes de alto nvel de layout e estrutura de
programa, quanto para detalhes de baixo nvel de escalonamento e alocao de recursos.
medida que a lacuna entre velocidade de processador e desempenho de aplicao
aumentava, a demanda por otimizao crescia, at o ponto em que os usurios esperam
que todo compilador realize otimizao.
A incluso rotineira de um otimizador, por sua vez, muda o ambiente em que o front
end e o back end operam. A otimizao isola ainda mais o front end dos problemas
de desempenho; at certo ponto, isso simplifica a tarefa de gerao da IR no front
end. Ao mesmo tempo, a otimizao muda o cdigo que o back end processa. Os
otimizadores modernos consideram que o back end tratar da alocao de recursos;
assim, normalmente visam uma mquina idealizada que tem um estoque ilimitado de
registradores, memria e unidades funcionais. Isto, por sua vez, coloca mais presso
sobre as tcnicas usadas no back end do compilador.
Se os compiladores tiverem que assumir sua fatia de responsabilidade pelo desempenho de runtime, precisam incluir otimizadores. Conforme veremos, as ferramentas
de otimizao tambm desempenham papel importante no back end do compilador.
Por esses motivos, importante introduzir a otimizao e explorar algumas das
questes que ela levanta antes de discutir as tcnicas usadas no back end de um
compilador.

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.

Melhoria do clculo de endereo de array


Considere a IR que o front end de um compilador poderia gerar para uma referncia
de array, como m(i,j) em FORTRAN. Sem o conhecimento especfico sobre m,

346 CAPTULO 8 Introduo otimizao

i e j, ou sobre o contexto ao redor, o compilador deve gerar a expresso completa


para endereamento de um array bidimensional armazenado em ordem por colunas.
No Captulo7, vimos o clculo para a ordem por linhas; esta, em FORTRAN,
semelhante:

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:

onde i1=i c e ij=ij1 +c


Ver Seo 10.7.2.

onde hw highi(m)w. Se a referncia ocorrer dentro de um lao onde j vai de


1 a k, o compilador poderia usar a reduo de fora do operador para substituir o termo
+ hw.
(j1)hw por uma sequncia j1,j2,j3,jk , onde j1 =(1-1) hw =0e ji = ji-1
Se i tambm for a varivel de induo de um lao que vai de 1 a l, ento a
reduo de fora pode substituir (i 1) w pela sequncia i1,i2,i3,i1 ,
onde i1 = 0 e ij = ij-1 + w . Depois dessas mudanas, o clculo de endereo
apenas:

O lao j precisa incrementar j9 de hw, e o lao i, incrementar i9 de w. Se o lao j


for o externo, ento o clculo de @m+j9 pode ser movido para fora do lao interno.
Neste ponto, o clculo de endereo no lao interno contm uma soma e o incremento
para i9, enquanto o externo contm uma soma e o incremento para j9. O conhecimento
do contexto em torno da referncia a m(i,j) permite ao compilador reduzir significativamente o custo do endereamento de array.
Se m for um parmetro real para o procedimento, ento o compilador pode no
saber disso em tempo de compilao. De fato, os limites superior e inferior para
m poderiam mudar em cada chamada para o procedimento. Nesses casos, o compilador pode ser incapaz de simplificar o clculo de endereo conforme o que foi
apresentado.

Melhoria do aninhamento de lao em LINPACK


Como exemplo mais dramtico de contexto, considere o aninhamento de lao mostrado na Figura8.1. o aninhamento de lao central da verso FORTRAN da rotina
dmxpy da biblioteca numrica LINPACK. O cdigo envolve dois laos em torno de
uma nica atribuio longa. Este aninhamento forma o ncleo de uma rotina para
calcular y+xm, para vetores x e y e matriz m. Vamos considerar o cdigo sob
dois pontos de vista diferentes: primeiro, as transformaes que o autor aplicou
mo para melhorar o desempenho; segundo, os desafios que o compilador enfrenta
na traduo desse aninhamento de lao para ser executado de modo eficiente em um
processador especfico.

8.2 Fundamentos 347

FIGURA 8.1 Trecho de dmxpy em LINPACK.

Antes que o autor transformasse o cdigo mo, o aninhamento de lao realizou a


seguinte verso mais simples do mesmo clculo:

Para melhorar o desempenho, o autor desenrolou o lao externo, o lao j, 16 vezes.


Essa modificao criou 16 cpias da instruo de atribuio com valores distintos
para j, variando de j at j15. Isto tambm mudou o incremento no lao externo
de 1 para 16. Em seguida, o autor mesclou as 16 atribuies em uma nica instruo,
eliminando 15 ocorrncias de y(i) = y(i) +... ; assim excluindo 15 adieseamaior parte dos loads e stores de y(i). O desenrolamento do lao elimina
algumas operaes escalares. Isto normalmente tambm melhora a localidade de cache.
Para lidar com os casos em que os limites do array no so mltiplos integrais de 16, o
procedimento completo tem quatro verses do aninhamento de lao que precedem aquele
apresentado na Figura8.1. Esses laos de configurao processam at 15 colunas param,
deixando j definido como um valor para o qual n2-j um mltiplo integral de 16. O
primeiro lao trata de uma nica coluna de m, correspondendo a um n2 mpar. Os outros
trs aninhamentos de lao lidam com duas, quatro e oito colunas de m. Isto garante que o
aninhamento de lao final, mostrado na Figura8.1, possa processar as colunas 16 a cada vez.
O ideal que o compilador transforme automaticamente o aninhamento de lao original
nessa verso mais eficiente, ou na forma mais apropriada para determinada mquinaalvo. Porm, poucos compiladores incluem todas as otimizaes necessrias para
realizar este objetivo. No caso de dmxpy, o autor realizou as otimizaes mo para
produzir um bom desempenho para uma grande faixa de mquinas alvo e compiladores.

Desenrolamento de lao
Replica o corpo do lao para iteraes distintas e ajusta
correspondentemente os clculos de ndice.

348 CAPTULO 8 Introduo otimizao

Do ponto de vista do compilador, o mapeamento do aninhamento de lao mostrado


na Figura8.1 para a mquina-alvo apresenta alguns desafios difceis. O aninhamento
de lao contm 33 expresses de endereo de array distintas, 16 para m, 16 para x e
uma para y, que ele usa duas vezes. A menos que o compilador possa simplificar esses
clculos de endereo, o lao estar inundado de aritmtica de inteiros.
Considere as referncias a x. Elas no mudam durante a execuo do lao interno, que
varia i. O otimizador pode mover os clculos de endereo e os loads para x para fora do
lao interno. Se puder manter os valores de x em registradores, ele pode eliminar uma
grande parte do overhead do lao mais interno. Para uma referncia como x(j12),
o clculo de endereo apenas @x+(j12)w. Para simplificar as coisas ainda mais,
o compilador pode refatorar todas as 16 referncias a x para a forma @x+jwck,
onde jw jw e ck kw para cada 0k15. Nesse formato, cada load usa o
mesmo endereo de base, @x+jw, com um deslocamento constante diferente, ck.
Para mapear isso de modo eficiente na mquina-alvo, preciso ter conhecimento dos
modos de endereamento disponveis. Se o destino tiver o equivalente da operao
loadAI da ILOC (endereo de base de registrador mais um pequeno deslocamento
constante), ento todos os acessos a x podem ser escritos para usar uma nica varivel
de induo. Seu valor inicial @x+jminw. Cada iterao do lao j o incrementa de w.
Os 16 valores de m usados no lao interno mudam a cada iterao. Assim, o lao
interno precisa calcular endereos e carregar 16 elementos de m em cada iterao.
A refatorao cuidadosa das expresses de endereo, combinada com a reduo de
fora, podem reduzir o overhead do acesso a m. O valor @m+jhigh1 (m) pode ser
calculado no lao j. (Observe que high1(m) a nica dimenso concreta declarada no
cabealho de dmxpy.) O lao interno pode produzir um endereo de base somando-o a
(i1)w. Depois, os 16 loads podem usar constantes distintas, ckhigh1(m), onde
ck k w para cada 0k15.
Para alcanar essa forma de cdigo, o compilador precisa refatorar as expresses de
endereo, realizar a reduo de fora, reconhecer clculos invariantes ao lao e mov-los
para fora dos laos internos, e escolher o modo de endereamento apropriado para os
loads. Mesmo com essas melhorias, o lao interno precisa realizar 16 loads, 16 multiplicaes de ponto flutuante e 16 adies de ponto flutuante, mais um store. O bloco
resultante apresentar um desafio para o escalonador de instrues.
Se o compilador falhar em alguma parte desta sequncia de transformao, o cdigo
resultante pode ser substancialmente pior do que o original. Por exemplo, se ele no
puder refatorar as expresses de endereo em torno de um endereo de base comum
para x e um para m, o cdigo poderia manter 33 variveis de induo distintas
uma para cada expresso de endereo distinta para x, m e y. Se a demanda resultante
por registradores forar o alocador de registradores ao derramamento, ele inserir
loads e stores adicionais no lao (que provavelmente j ligado memria). Em
casos como este, a qualidade do cdigo produzido pelo compilador depende da srie
orquestrada de transformaes que precisa funcionar; quando uma deixa de alcanar
seu propsito, a sequncia global pode produzir um cdigo de qualidade inferior ao
que o usurio espera.

8.2.2 Consideraes em relao otimizao


No exemplo anterior, o programador aplicou as transformaes acreditando que fariam
o programa ser executado mais rapidamente. Ele teve de acreditar que elas preservariam
o significado do programa. (Afinal, se as transformaes no precisarem preservar o
significado, por que no substituir o procedimento inteiro por um nico nop?)

8.2 Fundamentos 349

Duas questes, segurana e lucratividade, esto no centro de cada otimizao. O compilador


precisa ter um mecanismo para provar que cada aplicao da transformao segura ou
seja, preserva o significado do programa. O compilador precisa ainda ter um motivo para
crer que a aplicao da transformao lucrativa que melhora o desempenho do programa. Se um destes no for verdadeiro ou seja, a aplicao da transformao mudar o
significado do programa ou piorar seu desempenho , o compilador no deve aplic-la.

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

Um valor calculado como y(i) no reutilizado at a prxima iterao do lao


externo. As iteraes do lao interno so independentes uma da outra, pois cada
uma define exatamente um valor, e nenhuma outra iterao referencia esse valor.
Assim, as iteraes podem ser executadas em qualquer ordem. (Por exemplo, se
executarmos o lao mais interno de n1 para 1, isto produz os mesmos resultados.)
j A interao por meio de y limitada em seu efeito. O i-simo elemento de y
acumula a soma de todas as i-simas iteraes do lao interno. Esse padro de
acmulo seguramente reproduzido no lao desenrolado.
Grande parte da anlise feita na otimizao prossegue fornecendo a segurana das
transformaes.

350 CAPTULO 8 Introduo otimizao

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

Demanda por registradores. O lao original s precisa de alguns registradores para


manter seus valores ativos. Somente x(j), alguma parte dos clculos de endereo
para x, y e m, e as variveis de ndice de lao precisam de registradores entre as iteraes de lao, enquanto y(i) e m(i,j) precisam deles brevemente. Ao contrrio, o
lao transformado tem 16 elementos de x para manter em registradores durante o lao,
juntamente com os 16 valores de m e y(i) que precisam de registradores brevemente.

8.2 Fundamentos 351

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.

Outros problemas de natureza especfica de mquina tambm aparecem. Por exemplo,


os 17 loads e um store, as 16 multiplicaes, as 16 adies mais os clculos de endereo
e operaes de overhead de lao em cada iterao devem ser escalonados com cuidado.
O compilador pode ter que emitir algumas das operaes load em uma iterao anterior,
de modo que possa escalonar a tempo as operaes iniciais de ponto flutuante.

8.2.3 Oportunidades para otimizao


Conforme vimos, a tarefa de otimizar um simples lao pode envolver consideraes
complexas. Em geral, os compiladores otimizadores aproveitam as oportunidades que
surgem de vrias fontes distintas.
1. Reduzir o overhead de abstrao. Conforme vimos para o clculo de endereo
de array no incio do captulo, as estruturas e tipos de dados introduzidos pelas
linguagens de programao exigem suporte de runtime. Os otimizadores usam
anlise e transformao para reduzir esse overhead.
2. Tirar proveito de casos especiais. Normalmente, o compilador pode usar o conhecimento sobre o contexto em que uma operao executada para especializar
essa operao. Como um exemplo, um compilador C++ s vezes pode determinar que uma chamada a uma funo virtual sempre usa a mesma implementao.
Neste caso, ele pode remapear a chamada e reduzir o custo de cada invocao.
3. Equiparar o cdigo aos recursos do sistema. Se os requisitos de recursos de
um programa diferirem das capacidades do processador, o compilador pode
transform-lo para alinhar suas necessidades mais perto dos recursos disponveis.
As transformaes aplicadas a dmxpy tm este efeito, diminuem o nmero de
acessos memria por operao de ponto flutuante.
Essas so reas extensas, descritas com generalidade abrangente. medida que discutirmos sobre tcnicas especficas de anlise e transformao, nos Captulos9 e10,
preencheremos essas reas com exemplos mais detalhados.
REVISO DA SEO
A maior parte da otimizao baseada em compilador funciona especializando cdigo
de propsito geral ao seu contexto especfico. Para algumas transformaes de cdigo, os benefcios advm de efeitos locais, como as melhorias nos clculos de endereo
de array. Outras exigem um amplo conhecimento de regies maiores no cdigo,
e seus benefcios advm dos efeitos que ocorrem sobre extenses maiores do cdigo.
Considerando qualquer otimizao, o construtor de compiladores precisa se preocupar com:
1. Segurana; por exemplo, a transformao no muda o significado do cdigo?
2. Lucratividade; por exemplo, como a transformao melhorar o cdigo?
3. Encontrar oportunidades; por exemplo, como o compilador pode localizar rapidamente
locais no cdigo onde a aplicao de determinada transformao segura e lucrativa?

352 CAPTULO 8 Introduo otimizao

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?

8.3 ESCOPO DE OTIMIZAO

Escopo de otimizao
A regio de cdigo onde uma otimizao opera o seu
escopo de otimizao.

Otimizaes operam em diferentes granularidades ou escopos. Na seo anterior,


examinamos a otimizao de uma nica referncia de array e de um aninhamento
de lao completo. Os diferentes escopos dessas otimizaes apresentaram diferentes
oportunidades ao otimizador. A reformulao da referncia do array melhorou o
desempenho para sua execuo. A reescrita do lao melhorou o desempenho por uma
regio maior. Em geral, as transformaes e as anlises que lhes do suporte operam
sobre um de quatro escopos distintos: local, regional, global ou programa inteiro.

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.

8.3 Escopo de otimizao 353

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.

Bloco bsico estendido


Conjunto de blocos b1, b2,. . ., bn onde b1 tem
mltiplos predecessores no CFG e todo bi tem apenas
um, que algum bj no conjunto.
Dominncia
Em um CFG, x domina y se, e somente se, todo caminho
da raiz at y inclui x.

354 CAPTULO 8 Introduo otimizao

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?

8.4 OTIMIZAO LOCAL


Otimizaes que operam sobre um escopo local um nico bloco bsico esto
entre as tcnicas mais simples que o compilador pode usar. O modelo de execuo
simples de um bloco bsico leva a uma anlise razoavelmente precisa no suporte da
otimizao. Assim, esses mtodos so surpreendentemente eficazes.

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.

8.4 Otimizao local 355

8.4.1 Numerao de valor local


Considere o bloco bsico de quatro instrues mostrado na margem. Vamos nos referir
ao bloco como B. Uma expresso, como b+c ou ad, redundante em B se, e somente se, tiver sido calculada anteriormente em B e nenhuma operao interveniente redefinir um de seus argumentos constituintes. Em B, a ocorrncia de b+c na terceira operao no redundante, pois a segunda redefine b. A ocorrncia de ad na quarta
operao redundante porque B no redefine a ou d entre a segunda e a quarta operaes.
O compilador pode reescrever esse bloco de modo que calcule ad uma vez, como
mostramos na margem. A segunda avaliao de ad substituda por uma cpia de b.
Uma estratgia alternativa substituiria usos subsequentes de d por usos de b. Porm,
esse mtodo exige anlise para determinar se b redefinido ou no antes de algum uso
de d. Na prtica, mais simples fazer o otimizador insirir uma cpia e permitir que um
passo subsequente determine quais operaes de cpia so de fato necessrias e quais
podem ter seus nomes de origem e destino combinados.
Em geral, substituir avaliaes redundantes por referncias a valores previamente
calculados lucrativo ou seja, o cdigo resultante roda mais rapidamente do que o
original. Porm, a lucratividade no garantida. Substituir daa por db tem
o potencial de estender o tempo de vida de b e encurtar os de a ou d, ou ambos
dependendo, em cada caso, de onde se encontra o ltimo uso do valor. Dependendo
dos detalhes precisos, cada reescrita pode aumentar a demanda por registradores,
diminu-la ou deix-la inalterada. A substituio de uma computao redundante por
uma referncia provavelmente no ser lucrativa se a reescrita fizer que o alocador de
registrador derrame um valor no bloco.
Na prtica, o otimizador no pode prever de forma consistente o comportamento do
alocador de registradores, em parte porque o cdigo ainda ser transformado antes de
alcanar o alocador. Portanto, a maior parte dos algoritmos para remover a redundncia
considera que a reescrita para evitar redundncia lucrativa.
No exemplo anterior, a expresso redundante era textualmente idntica ocorrncia
anterior. A atribuio pode, naturalmente, produzir uma expresso redundante que
difere textualmente de sua predecessora. Considere o bloco mostrado na margem. A
atribuio de b a d faz com que a expresso dc produza o mesmo valor que bc.
Para reconhecer este caso, o compilador deve rastrear o fluxo de valores por meiodos
nomes. As tcnicas que se baseiam na identidade textual no detectam tais casos.
Os programadores protestaro porque no escrevem cdigo que contm expresses
redundantes, como aquelas no exemplo. Na prtica, a eliminao da redundncia
encontra muitas oportunidades. A traduo do cdigo fonte para a IR elabora muitos
detalhes, como clculos de endereo, e introduz expresses redundantes.
Muitas tcnicas que encontram e eliminam redundncias tm sido desenvolvidas. Numerao de valor local uma das mais antigas e mais poderosas dessas transformaes.
Ela descobre tais redundncias dentro de um bloco bsico e reescreve o bloco para
evit-las, e fornece um framework simples e eficiente para outras otimizaes locais,
como o desdobramento de constante e a simplificao usando identidades algbricas.

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.

356 CAPTULO 8 Introduo otimizao

A Figura8.2 mostra o algoritmo de numerao de valor local (LVN Local Value


Numbering), que usa como entrada um bloco com n operaes binrias, cada uma na
forma TiLiOpiRi; examina cada operao, em ordem; e usa uma tabela hash para
mapear nomes, constantes e expresses em nmeros de valor distintos. A tabela hash
est inicialmente vazia.

FIGURA 8.2 Numerao de valor de um nico bloco.

Para processar a i-sima operao, a LVN obtm nmeros de valor para Li e Ri


p rocurando-os na tabela hash. Se encontrar uma entrada, usa o nmero de valor dessa
entrada. Se no, cria uma e atribui um novo nmero de valor.
Dados os nmeros de valor para Li e Ri, chamados VN(Li) e VN(Ri), a LVN constriuma
chave hash a partir de VN(Li), Opi, VN(Ri) procura por essa chave na tabela. Se existir
uma entrada, a expresso redundante e pode ser substituda por uma referncia ao
valor previamente calculado. Se no, a operao i a primeira computao da expresso
nesse bloco, de modo que a LVN cria uma entrada para sua chave hash e atribui a essa
entrada um novo nmero de valor. Ela tambm atribui o nmero de valor da chave hash,
seja novo ou preexistente, entrada de tabela para Ti. Como a LVN usa nmeros de
valor para construir a chave hash da expresso, ao invs de nomes, pode efetivamente
rastrear o fluxo de valores por meio de operaes de cpia e atribuio, como no pequeno
exemplo rotulado Efeito da Atribuio na pgina anterior. A extenso da LVN para
expresses de aridade arbitrria simples.
A IMPORTNCIA DA ORDEM
A ordem especfica em que as expresses so escritas tem impacto direto sobre a
capacidade de as otimizaes as analisarem e transformarem. Considere as seguintes
codificaes distintas de vabc:

A codificao esquerda atribui nmeros de valor a ab, a (ab)c e a v,


enquanto a codificao direita atribui nmeros de valor a bc, a a(bc)
e a v. Dependendo do contexto ao redor, uma ou outra codificao pode ser
prefervel. Por exemplo, se bc ocorre mais tarde no bloco, mas ab no, ento a
codificao da direita produz redundncia, enquanto a da esquerda no.
Em geral, o uso da comutatividade, associatividade e distributividade para reordenar
expresses pode mudar os resultados da otimizao. Efeitos semelhantes podem
ser vistos com o desdobramento de constante; se substituirmos a por 3 e c por 5,
nenhuma ordenao produz a operao constante 35, que pode ser desdobrada.
Como o nmero de maneiras de reordenar expresses proibitivamente grande,
os compiladores usam tcnicas heursticas para encontrar boas ordenaes
para expresses. Por exemplo, o compilador IBM FORTRAN H gerava clculos
de endereo de array em uma ordem que costumava melhorar outras otimizaes.
Outros compiladores tm classificado os operandos de operaes comutativas
e associativas em uma ordem que corresponde ao nvel de aninhamento de lao
em que so definidos. Como tantas solues so possveis, as heursticas para
o problema normalmente exigem experimentao e ajuste para descobrir o que
apropriado para uma linguagem, compilador e estilo de codificao especficos.

8.4 Otimizao local 357

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

Operaes comutativas. Operaes comutativas que diferem apenas na ordem


de seus operandos, como ab e ba, devem receber os mesmos nmeros
de valor. Como a LVN constri uma chave hash para o lado direito da operao
atual, pode classificar os operandos usando algum esquema conveniente, como
orden-los pelo nmero de valor. Esta simples ao garantir que as variantes
comutativas recebam o mesmo nmero de valor.
j Desdobramento de constante. Se todos os operandos de uma operao tiverem
valores constantes conhecidos, a LVN pode realizar a operao e utilizar a resposta diretamente no cdigo. A LVN pode armazenar informaes sobre constantes na tabela hash, incluindo seu valor. Antes da formao da chave hash,
ela pode testar os operandos e, se possvel, avali-los. Se descobrir uma expresso constante, pode substituir a operao por um load imediato do resultado.
O desdobramento de cpia subsequente ir limpar o cdigo.
j Identidades algbricas. A LVN pode aplicar identidades algbricas para simplificar o cdigo. Por exemplo, x+0 e x devem receber o mesmo nmero de valor.
Infelizmente, a LVN precisa de cdigo especial para cada identidade. Diversos
testes, um por identidade, podem facilmente se tornar longos o suficiente para
produzir um atraso inaceitvel no algoritmo. Para melhorar esse problema, a
LVN deve organizar os testes em rvores de deciso especficas de operador.
Como cada operador tem apenas algumas identidades, esta tcnica mantm o
overhead baixo. A Figura8.3 mostra algumas das identidades que podem ser
tratadas dessa maneira.

FIGURA 8.3 Identidades algbricas para numerao de valor.

358 CAPTULO 8 Introduo otimizao

FIGURA 8.4 Numerao de valor local com extenses.

NaN
Not a Number, uma constante definida que representa
um resultado invlido ou sem significado no padro
IEEE para aritmtica de ponto flutuante.

Um implementador inteligente descobrir outras identidades, incluindo algumas


que so especficas de tipo. O ou-exclusivo de dois valores idnticos deve gerar
um zero do tipo apropriado. Os nmeros de ponto flutuante no formato IEEE tm
seus prprios casos especiais introduzidos pelas representaes explcitas de e
NaN; por exemplo, =NaN, NaN=NaN, e NaN=NaN.
A Figura8.4 mostra a LVN com essas extenses. As etapas 1 e 5 apareceram no
algoritmo original; a etapa 2 avalia e desdobra operaes com valor constante; a etapa
3 verifica identidades algbricas usando as rvores de deciso mencionadas anteriormente; a etapa 4 reordena os operandos de operaes comutativas. Mesmo com essas
extenses, o custo por operao da IR permanece extremamente baixo. Cada etapa tem
uma implementao eficiente.

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.

8.4 Otimizao local 359

Entretanto, agora o compilador precisa reconciliar esses nomes em subscrito com


aqueles nos blocos ao redor, para preservar o significado do cdigo original. Em nosso exemplo, o nome original a deveria se referir ao valor do nome subscritado a1 no
cdigo reescrito. Uma implementao inteligente mapearia o novo a1 ao a original,
b0 ao b original, c0 ao c original, e renomearia a0 para um novo nome temporrio.
Essa soluo reconcilia o espao de nomes do bloco transformado com o contexto ao
redor sem introduzir cpias.
Esse esquema de nomeao assemelha-se a uma propriedade do espao de nomes
criado para a forma de atribuio nica esttica, ou SSA (Static Single-Assignment),
introduzida na Seo 5.4.2. A Seo 9.3 explora a traduo de cdigo linear para a forma
SSA e da forma SSA de volta para o cdigo linear. Os algoritmos que ela apresenta
para traduo do espao de nomes so mais gerais do que o necessrio para um nico
bloco, mas certamente trataro do caso de nico bloco e tentaro minimizar o nmero
de operaes de cpia que devem ser inseridas.

EXCEES EM RUNTIME E OTIMIZAO


Algumas condies de runtime anormais podem gerar excees. Exemplos incluem
referncias de memria fora dos limites, operaes aritmticas indefinidas, como
diviso por zero, e operaes malformadas. (Um modo para o depurador disparar
um ponto de interrupo substituir a instruo por uma malformada e capturar
a exceo.) Algumas linguagens incluem recursos para tratamento de excees, tanto
para situaes predefinidas quanto definidas pelo programador.
Normalmente, uma exceo de runtime causa transferncia de controle
para um tratador de exceo. Este pode resolver o problema, reexecutar a operao
problemtica e retornar o controle ao bloco. Como alternativa, pode transferir
o controle para algum outro lugar ou terminar a execuo.
O otimizador precisa entender quais operaes podem disparar uma exceo
e precisa considerar o impacto de uma exceo sobre a execuo do programa.
Como um tratador de exceo poderia modificar os valores de variveis ou transferir
o controle, o compilador precisa tratar de operaes causadoras de exceo de forma
conservadora. Por exemplo, cada uma dessas operaes poderia forar o trmino
do bloco bsico atual. Esse tratamento pode limitar bastante a capacidade
do otimizador de melhorar o cdigo.
Para otimizar o cdigo carregado de excees, o compilador precisa entender
e modelar os efeitos dos tratadores de exceo. Para faz-lo, precisa acessar o cdigo
dos tratadores de exceo e precisa de um modelo de execuo global para entender
quais tratadores podero estar em vigor quando uma operao especfica causadora
de exceo for executada.

O impacto das atribuies indiretas


A discusso anterior considera que as atribuies so diretas e bvias, como em
abc. Muitos programas contm atribuies indiretas, nos quais o compilador
pode no saber quais valores ou locais so modificados. Exemplos incluem a atribuio
por meio de um ponteiro, como em *p=0; em C, ou a atribuio a um elemento deestrutura ou um elemento de array, como em a(i,j)=0 em FORTRAN. As atribuies
indiretas complicam a numerao de valor e outras otimizaes, pois criam imprecises
no entendimento do fluxo de valores pelo compilador.
Considere a numerao de valor com o esquema de nomeao com subscrito apresentado na seo anterior. Para gerenciar os subscritos, o compilador mantm um
mapeamento do nome de varivel bsico, digamos, a, para o seu subscrito atual. Em
uma atribuio, como abc, o compilador simplesmente incrementa o subscrito

Dica: a tabela hash de nmeros de valor precisa refletir


os nomes com subscritos. O compilador pode usar uma
segunda tabela, menor, para mapear os nomes bsicos
aos subscritos.

360 CAPTULO 8 Introduo otimizao

atual para a. As entradas na tabela de valores para o subscrito anterior permanecem


intactas. Em uma atribuio indireta, como *p0, o compilador pode no saber quais
subscritos de nome bsico incrementar. Sem conhecimento especfico dos locais de
memria aos quais p pode se referir, o compilador precisa incrementar o subscrito de
cada varivel que a atribuio possivelmente poderia modificar potencialmente o
conjunto de todas as variveis. De modo semelhante, uma atribuio como a(i,j)=0,
onde o valor de i ou j desconhecido, precisa ser tratada como se mudasse o valor
de cada elemento de a.
Referncia ambgua
Uma referncia ambgua se o compilador no puder
associ-la a um nico local da memria.

Embora isso parea drstico, mostra o verdadeiro impacto de uma atribuio


indireta ambgua sobre o conjunto de fatos que o compilador pode deduzir. O
compilador pode realizar anlise para retirar a ambiguidade das referncias de
ponteiro ou seja, para estreitar o conjunto de variveis s quais ele acredita que
um ponteiro pode enderear. De modo semelhante, pode usar uma srie de tcnicas
para entender os padres de acesso aos elementos de um array novamente, para
reduzir o conjunto de locais que ele precisa assumir que so modificados por uma
atribuio a um elemento.

8.4.2 Balanceamento de altura de rvore


Como vimos no Captulo7, os detalhes especficos de como o compilador codifica
uma computao podem afetar sua capacidade de otimizar essa computao. Muitos
processadores modernos tm vrias unidades funcionais, de modo que possam executar vrias operaes independentes em cada ciclo. Se o compilador puder arrumar
o fluxo de instrues de modo que contenha operaes independentes, codificadas
da forma apropriada, especfica da mquina, ento a aplicao ser executada mais
rapidamente.
Considere o cdigo para a+b+c+d+e+f+g+h mostrado na margem. Uma
avaliao da esquerda para a direita produziria a rvore associativa esquerda na
Figura8.5a. Outras rvores permissveis incluem aquelas nas Figuras8.5b e c.
Cada rvore distinta implica restries sobre a ordem de execuo que no so exigidas
pelas regras de adio. A rvore associativa esquerda implica que o programa precisa
avaliar a+b antes de poder realizar as adies envolvendo g ou h. A rvore associativa
direita correspondente, criada por uma gramtica recursiva direita, implica que
g+h precisa preceder as adies envolvendo a ou b. A rvore balanceada impe
menos restries, mas ainda implica uma ordem de avaliao com mais restries do
que a aritmtica real.
Se o processador puder realizar mais de uma adio por vez, ento a rvore balanceada deve permitir que o compilador produza um escalonamento mais curto para a
computao. A Figura8.6 mostra possveis escalonamentos para a rvore balanceada
e a associativa esquerda em um computador com dois somadores de nico ciclo. A

FIGURA 8.5 Formas potenciais de rvore para a+b+c+d+e+f+g+h.

8.4 Otimizao local 361

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.

FIGURA 8.6 Escalonamentos de diferentes formas de rvore para a+b+c+d+e+f+g+h.

Esse esquema de duas fases, anlise seguida pela transformao, comum na otimizao.

Determinao de rvores candidatas


Um bloco bsico consiste em uma ou mais computaes misturadas. O compilador
pode interpretar um bloco, em cdigo linear, como um grafo de dependncia (ver Seo
5.2.2); este captura o fluxo de valores e as restries de ordenao sobre as operaes.
No bloco curto mostrado na margem, o cdigo precisa calcular ab antes que possa
calcular t1+t2 ou t1t2.

Bloco bsico curto

362 CAPTULO 8 Introduo otimizao

O grafo de dependncia, em geral, no forma uma nica rvore. Ao invs disso,


consiste em vrias rvores conectadas interligadas. As rvores de expresso candidatas que o algoritmo de balanceamento precisa contm um subconjunto dos ns
do grafo de dependncia do bloco. Nosso bloco de exemplo muito curto para ter
rvores no triviais, mas tem quatro rvores distintas uma para cada operao,
como mostramos na margem.
Quando o algoritmo rearruma os operandos, rvores candidatas maiores oferecem
mais oportunidades para este rearranjo. Assim, o algoritmo tenta construir rvores
candidatas de tamanho mximo. Conceitualmente, o algoritmo encontra rvores
candidatas que podem ser consideradas como um nico operador n-rio, para um
valor de n o maior possvel. Diversos fatores limitam o tamanho de uma rvore
candidata.

Valor observvel
Um valor observvel, com relao a um fragmento
de cdigo (bloco, lao etc.), se for lido fora desse
fragmento.

1. A rvore no pode ser maior do que o bloco que representa. Outras


transformaes podem aumentar o tamanho de um bloco bsico (ver
Seo 10.6.1).
2. O cdigo reescrito no pode mudar os valores observveis do bloco ou seja,
qualquer valor usado fora do bloco deve ser calculado e preservado como
se estivesse no cdigo original. De modo semelhante, qualquer valor usado
vrias vezes no bloco deve ser preservado; no exemplo, tanto t1 quanto t2 tm
essa propriedade.
3. A rvore no pode se estender para trs para alm do incio do bloco. Em nosso
exemplo da margem, a, b, c e d recebem seus valores antes do incio do bloco,
e, assim, tornam-se folhas na rvore.
A fase de localizao de rvore tambm precisa saber, para cada nome Ti definido no
bloco, onde Ti referenciado. Ele assume um conjunto USES(Ti) que contm o ndice
no bloco de cada uso de Ti. Se Ti usado aps o bloco, ento USES(Ti) deve conter
duas entradas adicionais inteiros arbitrrios maiores do que o nmero de operaes
no bloco. Esse truque garante que |USES(x)|=1 se, e somente se, x for usada como
uma varivel temporria local. Deixamos a construo dos conjuntos USES como um
exerccio para o leitor (ver Exerccio 8.8); ela conta com os conjuntos LIVEOUT (ver
Seo 8.6.1).
As Figuras8.7 e8.8 apresentam o algoritmo para balancear um bloco bsico.
A fase1 do algoritmo, na Figura8.7, enganosamente simples. Ela percorre as
operaes no bloco; e testa cada operao para ver se esta operao deve ser a raiz
de sua prpria rvore. Quando encontra uma raiz, acrescenta o nome definido por
essa operao em uma fila de prioridade de nomes, ordenada pela precedncia do
operador da raiz.
O teste para identificar uma raiz tem duas partes. Suponha que a operao i tenha a
forma TiLiOpiRi. Primeiro, Opi precisa ser comutativo e associativo. Segundo,
uma das duas condies a seguir precisa ser mantida:
1. Se Ti for usado mais de uma vez, ento a operao i deve ser marcada como uma
raiz para garantir que Ti esteja disponvel para todos os seus usos. Mltiplos usos
tornam Ti observvel.
2. Se Ti for usado apenas uma vez, na operao j, mas Opi Opj, ento a operao i
deve ser uma raiz, pois no pode fazer parte da rvore que contm Opj.
Em qualquer caso, a fase 1 marca Opj como uma raiz e o coloca na fila.

8.4 Otimizao local 363

FIGURA 8.7 Algoritmo de balanceamento de altura de rvore, Parte I.

Reconstruo do bloco na forma balanceada


A fase 2 toma a fila de razes de rvores candidatas e constri, a partir de cada raiz, uma
rvore aproximadamente balanceada. Esta fase comea com um lao while que chama
Balance para cada raiz de rvore candidata. Balance, Flatten e Rebuild
implementam a fase dois.
Balance chamado para uma raiz de rvore candidata. Trabalhando com Flatten,
cria uma fila de prioridades que mantm todos os operandos da rvore atual.
Balance aloca uma nova fila e depois chama Flatten para percorrer a rvore
recursivamente, atribuir ordens a cada operando e enfileir-los. Quando a rvore
candidata tiver sido achatada e ordenada, Balance chama Rebuild (ver Figura8.8)
para reconstruir o cdigo.
Rebuild usa um algoritmo simples para construir a nova sequncia de cdigo.
Repetidamente, retira da rvore os dois itens com ordenao mais baixa; emite uma

364 CAPTULO 8 Introduo otimizao

FIGURA 8.8 Algoritmo de balanceamento de altura de rvore, Parte II.

operao para combin-los; ordena o resultado e o insere j ordenado de volta na fila


de prioridade. Esse processo continua at que a fila esteja vazia.
Vrios detalhes desse esquema so importantes.

Exposto para cima (Upward-Exposed)


Um nome x exposto para cima no bloco b se o
primeiro uso de x em b se refere a um valor calculado
antes da entrada de b.

1. Ao atravessar uma rvore candidata, Flatten pode encontrar a raiz de outra


rvore. Nesse ponto, recorre a Balance, ao invs de Flatten, para criar uma
nova fila de prioridade para a rvore candidata da raiz e garantir que emita o
cdigo para a subrvore de precedncia mais alta antes do cdigo que referencia
o valor da subrvore Lembre-se de que a fase 1 ordenou a fila Roots por ordem
crescente de precedncia, o que fora a ordem de avaliao correta aqui.
2. O bloco contm trs tipos de referncias: constantes, nomes definidos no bloco
antes de seu uso no bloco e nomes expostos para cima. A rotina Flatten trata
cada caso separadamente, e conta com o conjunto UEVAR(b) que contm todos
os nomes expostos para cima (Upward-Exposed) do bloco b. A computao
de UEVAR descrita na Seo 8.6.1 e apresentada na Figura8.14a.
3. A fase 2 ordena os operandos de um modo cuidadoso. Constantes recebem ordenao
zero, o que as fora para a frente da fila, onde Fold avalia operaes com valor
constante, cria novos nomes para os resultados e trabalha os resultados na rvore. As
folhas recebem ordem um. Os ns interiores recebem a soma das ordenaes de cada
subrvore, que igual ao nmero de operandos no constantes na subrvore. Essa
ordenao produz uma aproximao a uma rvore binria balanceada.

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.

8.4 Otimizao local 365

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:

FIGURA 8.9 Exemplo de balanceamento de altura de rvore.

supondo que a precedncia de+ 1, a de 2.


A fase 2 do algoritmo remove repetidamente um n da fila Roots e chama Balance
para process-lo. Balance, por sua vez, usa Flatten para criar uma fila de prioridades dos operandos e, depois, Rebuild para criar uma computao balanceada a partir
dos operandos. (Lembre-se de que cada rvore contm apenas um tipo de operao.)
A fase 2 comea chamando Balance em t11. Lembre-se, da Figura8.9, que t11 a
soma de t3 e t7. Balance chama Flatten em cada um desses ns, que so, por si
ss, razes de outras rvores. Assim, a chamada a Flatten(t3, q) invoca Balance
para t3 e depois o invoca para t7.
Balance(t3) achata essa rvore para a fila {4, 0, 13, 0, b, 1, a, 1} e invoca
Rebuild para essa fila. Rebuild retira 4, 0 e 13, 0 da fila, combina-os e inclui

366 CAPTULO 8 Introduo otimizao

17, 0 na fila. Em seguida, retira 17, 0 e b, 1 da fila, emite n017+b e


acrescenta n0, 1 na fila. Na iterao final para a rvore t3, retira n0, 1 e a, 1 e
emite t3n0+a. E marca t3 com a ordem 2 e retorna.
Invocar Balance em t7 monta uma fila trivial, {e, 1, f, 1}, e emite a operao
t7e+f. Completando assim a primeira iterao do lao while na fase 2.
Em seguida, a fase 2 invoca Balance na rvore em t11. Chama Flatten, que constri
a fila {h, 1, g, 1, t7, 2, t3, 3}. Depois, Rebuild emite o cdigo n1h+g e
coloca n1 na fila com ordem 2. Em seguida, emite o cdigo n2n1+t7 e coloca n2 na
fila com ordem 4. Finalmente, emite o cdigo t11n2+t3 e marca t11 com ordem 6.
Os dois itens seguintes que a fase 2 retira da fila Roots, t7 e t3, j foram processados,
de modo que possuem ordens diferentes de zero. Assim, Balance retorna imediatamente
em cada um deles.
A chamada final para Balance da fase 2 lhe passa a raiz t6. Para t6, Flatten constri
a fila: {3, 0, d, 0, c, 1, t3, 1}. Rebuild emite o cdigo n33+d e coloca n3
na fila com ordem 1. Em seguida, emite n4n3+c e coloca n4 na fila com ordem
2. Finalmente, emite t6n4+t3 e marca t3 com ordem 4.
A rvore resultante mostrada na Figura8.10. Observe que a rvore enraizada em t6
agora tem altura de trs operaes, ao invs de seis.

FIGURA 8.10 Estrutura de cdigo aps balanceamento.

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.

8.5 Otimizao regional 367

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?

8.5 OTIMIZAO REGIONAL


As ineficincias no esto limitadas a blocos isolados. O cdigo que executado em
um bloco pode fornecer o contexto para melhorar o cdigo em outro bloco. Assim,
a maior parte das otimizaes examina um contexto maior do que um nico bloco.
Esta seo examina duas tcnicas que operam sobre regies de cdigo que incluem
vrios blocos, mas, normalmente, no se estendem a um procedimento inteiro. A
principal complicao que surge na mudana da otimizao local para a regional
a necessidade de lidar com mais de uma possibilidade para o fluxo de controle. Um
if-then-else pode assumir um de dois caminhos. O desvio no final de um lao
pode saltar de volta para outra iterao ou para o cdigo que vem aps o lao.
Para ilustrar as tcnicas regionais, apresentamos duas delas. A primeira, numerao de
valor superlocal, uma extenso da numerao de valor local para regies maiores. A
segunda uma otimizao de lao que apareceu em nossa discusso do aninhamento
de lao dmxpy: desenrolamento de lao.

8.5.1 Numerao de valor superlocal


Para melhorar os resultados da numerao de valor local, o compilador pode estender
seu escopo de um nico bloco bsico para um bloco bsico estendido, ou EBB. Para
processar um EBB, o algoritmo deve numerar o valor de cada caminho atravs do EBB.
Considere, por exemplo, o cdigo mostrado na Figura8.11a. Seu CFG, mostrado na
Figura8.11b, contm um EBB no trivial, (B0, B1, B2, B3, B4), e dois EBBs triviais,
(B5) e (B6). Chamamos o algoritmo resultante de numerao de valor superlocal (SVN
Superlocal Value Numbering).
No EBB grande, a SVN poderia tratar cada um dos trs caminhos como se fosse um
nico bloco. Ou seja, poderia se comportar como se cada um de (B0, B1), (B0, B2, B3)
e (B0, B2, B4) fosse cdigo em linha reta. Para processar (B0, B1), o compilador pode
aplicar a LVN a B0 e usar a tabela hash resultante como ponto de partida quando aplicar
a LVN a B1. A mesma tcnica trataria (B0, B2, B3) e (B0, B2, B4) processando cada um
dos blocos em ordem e levando adiante as tabelas hash. O efeito desse esquema
tratar um caminho como se fosse um nico bloco. Por exemplo, ele otimizaria (B0, B2,
B3) como se tivesse o cdigo mostrado na Figura8.11c. Qualquer bloco com vrios
predecessores, como B5 e B6, deve ser tratado como na numerao de valor local sem
contexto de quaisquer predecessores.
Essa tcnica pode encontrar redundncias e expresses de valor constante que um
algoritmo de numerao de valor estritamente local no acharia.
j

Em (B0, B1), a LVN descobre que as atribuies a n0 e r0 so redundantes. A


SVN descobre as mesmas redundncias.

368 CAPTULO 8 Introduo otimizao

FIGURA 8.11 Exemplo de numerao de valor superlocal.

8.5 Otimizao regional 369

Em (B0, B2, B3), a LVN descobre que a atribuio a n0 redundante. A SVN


tambm descobre que as atribuies a q0 e s0 so redundantes.
j Em (B0, B2, B4), a LVN descobre que a atribuio a n0 redundante. A SVN
tambm descobre que as atribuies a q0 e t0 so redundantes.
j Em B5 e B6, a SVN degenera-se LVN.
A dificuldade nessa tcnica est em tornar o processo eficiente. A tcnica bvia tratar
cada caminho como se fosse um nico bloco, fingindo, por exemplo, que o cdigo
para (B0, B2, B3) se parece com o cdigo da Figura8.11c. Infelizmente, essa tcnica
analisa um bloco uma vez para cada caminho que o inclui. No exemplo, ela analisaria
B0 trs vezes e B2 duas. Embora desejemos os benefcios da otimizao que vm do
exame do contexto aumentado, tambm queremos minimizar os custos de tempo de
compilao. Por esse motivo, os algoritmos superlocais com frequncia aproveitam a
estrutura de rvore do EBB.
Para tornar a SVN eficiente, o compilador precisa reutilizar os resultados dos blocos
que ocorrem como prefixos em vrios caminhos por meio do EBB. Precisa, tambm, de
um modo de desfazer os efeitos do processamento de um bloco. Aps o processamento
de (B0, B2, B3), ele precisa recriar o estado para o final de (B0, B2) de modo que possa
reutilizar esse estado para processar B4.
Algumas entre as muitas maneiras como o compilador pode realizar esse efeito:
j

Registrar o estado da tabela em cada fronteira de bloco e restaurar esse estado


quando necessrio.
j Desfazer os efeitos de um bloco percorrendo o bloco para trs e, em cada
operao, desfazendo o trabalho da passagem para a frente.
j Implementar a tabela de valores usando os mecanismos desenvolvidos
para definir o escopo lxico das tabelas hash. Ao entrar em um bloco, ele cria
um novo escopo. Para retrair os efeitos do bloco, exclui o escopo dele.
Embora todos os trs esquemas funcionem, o uso de uma tabela de valor com escopo pode produzir implementao mais simples e mais rpida, particularmente se o
compilador puder reutilizar uma implementao do front end (ver Seo 5.5.3).
A Figura8.12 mostra um esboo de alto nvel do algoritmo SVN usando uma tabela de
valor com escopo. Ela considera que o algoritmo LVN foi parametrizado para aceitar
um bloco e uma tabela de valor com escopo. Em cada bloco b, ele aloca uma tabela de
valor para b, vincula as tabelas de valor do bloco predecessor como se fosse um escopo
englobante e chama a LVN no bloco b com essa nova tabela. Quando a LVN retorna,
a SVN precisa decidir o que fazer com cada um dos sucessores de b.
Para um sucessor s de b, surgem dois casos. Se s tem exatamente um predecessor, b, ento
deve ser processado com o contexto acumulado de b. De forma correspondente, a SVN
recorre sobre s com a tabela contendo o contexto de b. Se s tiver mltiplos predecessores,
ento s deve comear com um contexto vazio. Assim, a SVN acrescenta s WorkList
onde o lao externo mais tarde o encontrar e chama a SVN sobre ele e com a tabela vazia.
Resta uma complicao. O nmero de valor de um nome registrado na tabela de valor
associada primeira operao no EBB que o define. Esse efeito pode atrapalhar nosso
uso do mecanismo de definio de escopo. Em nosso CFG de exemplo, se um nome x
fosse definido em cada um dos blocos B0, B3 e B4, seu nmero de valor seria registrado
na tabela com escopo para B0. Ao processar B3, a SVN registraria o novo nmero de
valor de x a partir de B3 na tabela para B0. Ao excluir a tabela para B3 e criar uma nova
tabela para B4, o nmero de valor da definio em B3 permaneceria.

A implementao do feixe de tabelas mostrada na


Seo 5.5.3 tem as propriedades certas para a SVN.
A SVN pode facilmente estimar o tamanho de cada
tabela. O mecanismo de excluso simples e rpido.

370 CAPTULO 8 Introduo otimizao

FIGURA 8.12 Algoritmo de numerao de valor superlocal.

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.

8.5.2 Desenrolamento de lao (loop unrolling)


Desenrolamento de lao talvez seja a transformao de lao mais antiga e mais conhecida. Para desenrolar um lao, o compilador replica o corpo do lao e ajusta a lgica

8.5 Otimizao regional 371

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.

Em cada caso, o cdigo transformado precisa de um curto lao de prlogo que


remova iteraes suficientes para garantir que o lao desenrolado processe um
mltiplo inteiro de quatro iteraes. Se os respectivos limites de lao forem todos
conhecidos em tempo de compilao, o compilador pode determinar se o prlogo
necessrio ou no.
Estas duas estratgias distintas, desenrolamentos de lao interno e de lao externo,
produzem diferentes resultados para este aninhamento de lao em particular. O desenrolamento do lao interno produz cdigo que executa muito menos sequncias
de teste-e-desvio do que o cdigo original. Ao contrrio, o desenrolamento do lao
externo seguido pela fuso dos laos internos no apenas reduz o nmero de sequncias
de teste-e-desvio, mas tambm produz a reutilizao de y(i) e o acesso sequencial
ax e m. A reutilizao aumentada muda fundamentalmente a razo entre operaes
aritmticas e de memria no lao; sem dvida, o autor do dmxpy tinha este efeito em
mente quando otimizou o cdigo mo. Conforme j discutimos, cada mtodo tambm
pode gerar benefcios indiretos.

FIGURA 8.13 Desenrolamento do aninhamento de lao de dmxpy.

O acesso a m sequencial porque FORTRAN armazena


arrays na ordem por colunas.

372 CAPTULO 8 Introduo otimizao

Fontes de melhoria e degradao


O desenrolamento de lao tem efeitos diretos e indiretos sobre o cdigo que o compilador pode produzir para um determinado lao. O desempenho final do lao depende
de todos os efeitos, diretos e indiretos.
Em termos de benefcios diretos, o desenrolamento deve reduzir o nmero de operaes
exigidas para completar o lao. As mudanas no fluxo de controle reduzem o nmero
total de sequncias de teste-e-desvio. O desenrolamento pode criar reutilizao dentro
do corpo do lao, reduzindo o trfego de memria. Finalmente, se o lao contm uma
cadeia cclica de operaes de cpia, o desenrolamento pode eliminar as cpias (ver
Exerccio 5 neste captulo).
Como um risco, porm, o desenrolamento aumenta o tamanho do programa, tanto em
sua forma IR quanto na final, como cdigo executvel. O crescimento na IR aumenta o
tempo de compilao; o crescimento no cdigo executvel tem pouco efeito at que o
lao ultrapasse a capacidade da cache de instrues momento em que a degradao
provavelmente supera quaisquer benefcios diretos.
O compilador tambm pode desenrolar para produzir efeitos indiretos, o que pode afetar
o desempenho. O principal efeito colateral do desenrolamento aumentar o nmero
de operaes dentro do corpo do lao. Outras otimizaes podem tirar proveito dessa
mudana de vrias maneiras:
j

Aumentar o nmero de operaes independentes no corpo do lao pode levar


a melhores escalonamentos de instruo. Com mais operaes, o escalonador
tem melhor chance de manter vrias unidades funcionais ocupadas e ocultar
a latncia das operaes de longa durao, como desvios e acessos
memria.
O desenrolamento pode mover acessos consecutivos memria para a mesma
iterao de lao, onde o compilador pode escalon-los juntos. Isso pode melhorar
a localidade ou permitir o uso de operaes de mltiplas palavras.
O desenrolamento pode expor as redundncias entre iteraes que so difceis
de descobrir no cdigo original. Por exemplo, as duas verses do cdigo mostrado na Figura8.13 reutilizam expresses de endereo entre iteraes do lao
original. No lao desenrolado, a numerao de valor local encontraria e eliminaria essas redundncias. No original, no as acharia.
O lao desenrolado pode gerar um modo diferente de otimizao em relao ao
lao original. Por exemplo, aumentar o nmero de vezes que uma varivel ocorre
dentro do lao pode mudar os pesos usados na seleo de cdigo derramado
dentro do alocador de registradores (ver Seo 13.4). A mudana do padro de
derramamento de registrador pode afetar radicalmente a velocidade do cdigo
final para o lao.
O corpo do lao desenrolado pode ter uma demanda maior por registradores
do que o corpo do lao original. Se a maior demanda por registradores induzir
derramamentos de registrador adicionais (stores e reloads), ento o trfego de
memria resultante pode superar os potenciais benefcios
do desenrolamento.

Essas interaes indiretas so muito mais difceis de caracterizar e entender do que os


efeitos diretos. Elas podem produzir melhorias de desempenho significativas. E tambm
produzir degradaes de desempenho. A dificuldade de prever esses efeitos indiretos
tem levado alguns pesquisadores a defender um mtodo adaptativo para a escolha de
fatores de desenrolamento; nesses sistemas, o compilador experimenta vrios fatores
de desenrolamento e mede o desempenho do cdigo resultante.

8.5 Otimizao regional 373

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:

Que melhorias seriam causadas pelo desenrolamento do lao? Como o fator de


desenrolamento afeta os benefcios?

Dica: compare as possveis melhorias com os fatores


de desenrolamento de dois e trs.

374 CAPTULO 8 Introduo otimizao

8.6 OTIMIZAO GLOBAL


Otimizaes globais operam sobre um procedimento (ou mtodo) inteiro. Como seu
escopo inclui construes de controle de fluxo cclicas, como laos, os mtodos de otimizao global normalmente realizam uma fase de anlise antes de modificar o cdigo.
Esta seo apresenta dois exemplos de anlise e otimizao globais. O primeiro,
encontrar variveis no inicializadas com informaes vivas, no estritamente uma
otimizao. Ao invs disso, usa a anlise global de fluxo de dados para descobrir informaes teis sobre o fluxo de valores em um procedimento. Usaremos a discusso
para introduzir a computao de informao de variveis vivas, que desempenha um
papel importante em muitas tcnicas de otimizao, incluindo balanceamento de altura
de rvore (Seo 8.4.2), construo de informao de SSA (Seo 9.3) e alocao
de registradores (Captulo13). O segundo, posicionamento de cdigo global, usa
informaes de perfil colhidas da execuo do cdigo compilado para rearrumar o
layout do cdigo executvel.

8.6.1 Localizao de variveis no inicializadas


com informao viva
Se um procedimento p puder usar o valor de alguma varivel v antes que v tenha
recebido um valor, dizemos que v est no inicializado nesse uso. O uso de uma
varivel no inicializada quase sempre indica um erro lgico no procedimento que est
sendo compilado. Se o compilador puder identificar essas situaes, ele deve alertar
ao programador de sua existncia.
Podemos encontrar potenciais usos de variveis no inicializadas computando informaes sobre vivncia (liveness). Uma varivel v est viva no ponto p se, e somente
se, houver um caminho no CFG a partir de p at um uso de v ao longo do qual v no
redefinida. Codificamos informaes vivas calculando, para cada bloco b no procedimento, um conjunto LIVEOUT(b) que contm todas as variveis que esto vivas na
sada de b. Dado um conjunto LIVEOUT para o n de entrada n0 do CFG, cada varivel
em LIVEOUT(n0) tem um uso potencialmente no inicializado.

Anlise de fluxo de dados


Forma de anlise em tempo de compilao para
raciocinar a respeito do fluxo de valores em tempo
de execuo.

A computao de conjuntos LIVEOUT um exemplo de anlise global de fluxo de


dados, uma famlia de tcnicas para raciocinar, em tempo de compilao, sobre o
fluxo de valores em tempo de execuo. Os problemas na anlise de fluxo de dados
normalmente so propostos como um conjunto de equaes simultneas sobre conjuntos
associados a ns e arestas de um grafo.

Definio do problema de fluxo de dados


A computao de conjuntos LIVEOUT um problema clssico na anlise de fluxo
de dados global. O compilador calcula, para cada n n no CFG do procedimento, um
conjunto LIVEOUT(n) que contm todas as variveis que esto vivas na sada do bloco
correspondente a n. Para cada n n no CFG do procedimento, LIVEOUT(n) definido
por uma equao que usa os conjuntos LIVEOUT dos sucessores de n no CFG, e dois
conjuntos UEVAR(n) e VARKILL(n) que codificam fatos sobre o bloco associado a n.
Podemos resolver as equaes usando um mtodo iterativo de ponto fixo, semelhante
aos mtodos de ponto fixo que vimos em captulos anteriores, como a construo de
subconjunto na Seo 2.4.3.
A equao de definio para LIVEOUT :

8.6 Otimizao global 375

UEVAR(m) contm as variveis expostas para cima em m aquelas variveis que


so usadas em m antes de qualquer redefinio em m. VARKILL(m) contm todas
as variveis que so definidas em m e a barra superior em VARKILL(m) indica seu
complemento lgico, o conjunto de todas as variveis no definidas em m. Como
LIVEOUT(n) definido em termos dos sucessores de n, a equao descreve um problema de fluxo de dados retroativo.
A equao codifica a definio de um modo intuitivo. LIVEOUT(n) simplesmente a
unio daquelas variveis que esto vivas no incio de algum bloco m que vem imediatamente aps n no CFG. A definio exige que um valor esteja vivo em algum caminho,
e no em todos. Assim, as contribuies dos sucessores de n no CFG so unidas para
formar LIVEOUT(n). A contribuio de um sucessor especfico m de n :

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).

Soluo do problema de fluxo de dados


Para calcular os conjuntos LIVEOUT para um procedimento e seu CFG, o compilador
pode usar um algoritmo de trs etapas.
1. Construir um CFG. Esta etapa conceitualmente simples, embora caractersticas
da linguagem e da arquitetura possam complicar o problema (ver Seo 5.3.4).
2. Colher informaes iniciais. O analisador calcula um conjunto UEVAR e VARKILL para cada bloco b em um percurso simples, como mostra a Figura8.14a.
3. Solucionar as equaes para produzir LIVEOUT(b) para cada bloco b. A
Figura8.14b mostra um algoritmo iterativo simples de ponto fixo que resolve as
equaes.
As prximas sees trabalham com um exemplo de computao de LIVEOUT. A Seo
9.2 trata as computaes de fluxo de dados com mais profundidade.

FIGURA 8.14 Anlise viva iterativa.

Problema de fluxo de dados retroativo


Problema em que a informao flui para trs pelas
arestas do grafo.
Problema de fluxo de dados progressivo
Problema em que a informao flui ao longo das
arestas do grafo.

376 CAPTULO 8 Introduo otimizao

Colhendo informaes iniciais


Para calcular LIVEOUT, o analisador precisa dos conjuntos UEVAR e VARKILL
para cada bloco. Um nico passo pode calcular ambos. Para cada bloco, o analisador
inicializa esses conjuntos como . Em seguida, percorre o bloco, de cima para baixo,
e atualiza tanto UEVAR quanto VARKILL para refletir o impacto de cada operao.
A Figura8.14a mostra os detalhes dessa computao.
Considere o CFG com um lao simples que contm uma construo if-then, mostrada
na Figura8.15a. O cdigo remove muitos detalhes. A Figura8.14b mostra os conjuntos
UEVAR e VARKILL correspondentes.

Solucionando as equaes para LIVEOUT


Dados os conjuntos UEVAR e VARKILL, o compilador aplica o algoritmo da Figura8.14b para calcular conjuntos LIVEOUT para cada n do CFG. Ele inicializa todos
os conjuntos LIVEOUT como . Em seguida, calcula o conjunto LIVEOUT para cada
bloco, na ordem de B0 a B4. E repete o processo, calculando LIVEOUT para cada n
em ordem, at que os conjuntos LIVEOUT no se alterem mais.
A tabela na Figura8.15c mostra os valores dos conjuntos LIVEOUT a cada iterao do
algoritmo. A linha rotulada como Inicial mostra os valores iniciais. A primeira iterao
calcula uma aproximao inicial para os conjuntos LIVEOUT. Como o algoritmo
processa os blocos em ordem crescente de seus rtulos, B0, B1 e B2 recebem valores
com base unicamente nos conjuntos UEVAR de seus sucessores CFG. Quando o
algoritmo alcana B3, ele j calculou uma aproximao para LIVEOUT(B1), de modo
que o valor que calcula para B3 reflete a contribuio do novo valor para LIVEOUT(B1).
LIVEOUT(B4) est vazio, conforme apropriado para o bloco de sada.

FIGURA 8.15 Exemplo de computao para LIVEOUT.

8.6 Otimizao global 377

Na segunda iterao, o valor s acrescentado a LIVEOUT(B0) como uma consequncia


de sua presena na aproximao de LIVEOUT(B1). Nenhuma outra mudana acontece.
A terceira iterao no muda os valores dos conjuntos LIVEOUT e termina.
A ordem em que o algoritmo processa os blocos afeta os valores dos conjuntos intermedirios. Se o algoritmo visitasse os blocos em ordem decrescente de seus rtulos,
exigiria um passo a menos. Os valores finais dos conjuntos LIVEOUT so independentes da ordem de avaliao. O algoritmo iterativo na Figura8.14 calcula uma soluo
de ponto fixo para as equaes de LIVEOUT.
O algoritmo termina porque os conjuntos LIVEOUT so finitos e o reclculo do
conjunto LIVEOUT para um bloco s pode aumentar o nmero de nomes nesse
conjunto. O nico mecanismo na equao para excluir um nome a interseo com
VARKILL . Como VARKILL no muda durante a computao, a atualizao a cada
conjunto LIVEOUT aumenta monotonicamente e, assim, o algoritmo por fim tem
que parar.

Localizao de variveis no inicializadas


Uma vez que o compilador tenha calculado conjuntos LIVEOUT para cada n no CFG
do procedimento, encontrar usos de variveis que possam ser no inicializadas muito
simples. Considere alguma varivel v. Se v LIVEOUT(n0), onde n0 o n de entrada
do CFG do procedimento, ento, pela construo de LIVEOUT(n0), existe um caminho
de n0 para um uso de v ao longo do qual v no definido. Assim, v LIVEOUT(n0)
implica que v tem um uso que pode receber um valor no inicializado.
Essa tcnica identifica variveis que tm um uso potencialmente no inicializado. O
compilador deve reconhecer essa situao e inform-la ao programador. Porm, esse
mtodo pode gerar falsos positivos por vrios motivos.
j Se v acessvel por meio de outro nome e inicializado por este nome, a anlise
viva no conectar a inicializao com o uso. Essa situao pode surgir quando
um ponteiro definido para o endereo de uma varivel local, como no fragmento de cdigo mostrado na margem.
j Se v existir antes que o procedimento atual seja invocado, ento ele pode ter sido
inicializado anteriormente de uma maneira invisvel ao analisador. Este caso
pode surgir com variveis estticas do escopo atual ou com variveis declaradas
fora do escopo atual.
j As equaes para anlise viva podem descobrir um caminho a partir da entrada
do procedimento para um uso de v ao longo do qual v no est definida. Se esse
caminho no for vivel em tempo de execuo, ento v aparecer em LIVEOUT(n0) mesmo que nenhuma execuo use o valor no inicializado. Por
exemplo, o programa em C na margem sempre inicializa s antes do seu uso,
embora s LIVEOUT(n0).
Se o procedimento contm uma chamada de procedimento e v passada para este
procedimento de um modo que permita a modificao, ento o analisador precisa
considerar possveis efeitos colaterais da chamada. Na ausncia de informaes
especficas sobre o procedimento chamado, o analisador deve considerar que cada
varivel que poderia ser modificada modificada e que qualquer varivel que poderia
ser usada usada. Essas suposies so seguras, pois representam o comportamento
de pior caso.
O exemplo marginal com o lao while ilustra um dos limites fundamentais da anlisede
fluxo de dados: considera que todos os caminhos por meio do CFG so viveis em tempo

378 CAPTULO 8 Introduo otimizao

de execuo. Essa suposio pode ser extremamente conservadora, como no exemplo.


O nico caminho no CFG levando a um uso no inicializado leva da entrada de main
para o lao, evita a inicializao de s, e atinge o incremento de s. Esse caminho nunca
pode ocorrer, pois i deve ter o valor 1 na primeira iterao do lao. As equaes para
LIVEOUT no podem descobrir este fato.
A suposio de que todos os caminhos no CFG so viveis reduz bastante o custo da
anlise. Ao mesmo tempo, a suposio produz uma perda de preciso nos conjuntos
calculados. Para descobrir que s inicializado na primeira iterao do lao for, o
compilador precisaria combinar uma anlise que rastreasse caminhos individuais com
alguma forma de propagao de constante e com anlise viva. A soluo do problema
em geral exigiria a avaliao simblica de partes do cdigo durante a anlise, uma
perspectiva muito mais dispendiosa.

Outros usos para variveis vivas


Os compiladores usam a vivncia em muitos contextos diferentes da localizao de
variveis no inicializadas.
j

A informao de varivel viva desempenha papel crtico na alocao


de registradores global (ver Seo 13.4). O alocador de registradores no precisa
manter valores em registradores a menos que estejam vivos; quando um valor
faz a transio de vivo para no vivo, o alocador pode reutilizar seu registrador
para outra finalidade.
j A informao de varivel viva usada para melhorar a construo de SSA;
um valor no precisa de uma funo em qualquer bloco onde no esteja vivo.
O uso de informaes vivas deste modo pode reduzir significativamente
o nmero de funes que o compilador precisa inserir ao construir a forma
SSA de um programa.
j O compilador pode usar informao viva para descobrir operaes de store sem
utilidade. Em uma operao que armazena v na memria, se v no estiver viva,
ento o store intil. Esta tcnica simples funciona bem para variveis escalares
no ambguas ou seja, variveis conhecidas por apenas um nome.
Em contextos diferentes, a vivncia calculada para diferentes conjuntos de nomes.
Discutimos LIVEOUT com um domnio implcito de nomes de varivel. Na alocao
de registradores, o compilador calcular conjuntos LIVEOUT sobre o domnio de
nomes de registrador ou sobre subfaixas contguas desses nomes.

8.6.2 Posicionamento de cdigo global


Desvio fall-through
Um desvio de um endereo ou tomado ou a execuo
segue direto (falls-through) para a prxima operao na
sequncia.

Muitos processadores tm custos de desvio assimtricos; o custo de um desvio


fall-through menor do que o de um desvio tomado. Cada desvio tem dois blocos bsicos
sucessores; o compilador pode escolher qual deles se encontra no caminho fall-through
e qual no caminho tomado. A otimizao do posicionamento de cdigo global baseia-se,
implicitamente, na observao de que alguns desvios tm um comportamento desequilibrado o caminho fall-through tem um custo menor do que o caminho tomado.
Considere o CFG mostrado na margem. (B0, B2) executado 100 vezes mais frequentemente do que (B0, B1). Com custos de desvio assimtricos, o compilador deve usar
o desvio menos dispendioso para (B0, B2). Se (B0, B1) e (B0, B2) tivessem frequncias
de execuo aproximadamente iguais, o posicionamento de bloco teria pouco impacto
para esse cdigo.
Dois layouts diferentes para esse cdigo so mostrados a seguir. O layout lento usao
desvio fall-through para implementar (B0, B1) e o desvio tomado para (B0, B2). O layout

8.6 Otimizao global 379

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.

COLHENDO DADOS DE PERFIL


Se o compilador entende as frequncias relativas de execuo das diversas partes
do programa, pode usar essa informao para melhorar o desempenho do programa.
Os dados de perfil podem desempenhar papel importante em otimizaes
como posicionamento de cdigo global (Seo 8.6.2) ou substituio em linha
(Seo 8.7.1). Vrios mtodos so usados para colher dados de perfil.
j Executveis instrumentalizados. Neste esquema, o compilador gera cdigo para
contar eventos especficos, como entradas e sadas de procedimento ou desvios
tomados. Em tempo de execuo, os dados so escritos em um arquivo externo
e processados off-line por outra ferramenta.
j Interrupes de temporizador. Ferramentas que usam este mtodo interrompem
a execuo do programa em intervalos frequentes, regulares. A ferramenta
constri um histograma de locais do contador de programa onde as interrupes
ocorreram. O ps-processamento constri um perfil a partir do histograma.
j Contadores de desempenho. Muitos processadores oferecem alguma forma
de contadores de hardware para registrar eventos de hardware, como ciclos totais,
falhas de cache ou desvios tomados. Se os contadores estiverem disponveis,
o sistema de runtime pode us-los para construir dados tipo perfil altamente precisos.
Esses mtodos produzem informaes um tanto diferentes e tm custos distintos.
Um executvel instrumentalizado pode medir quase toda propriedade da execuo;
uma engenharia cuidadosa pode limitar os custos de overhead. Um sistema
de interrupo de temporizador tem overhead inferior, mas apenas localiza
instrues executadas com frequncia (no os caminhos tomados para alcan-las).
Contadores de hardware so precisos e eficientes, mas dependem de maneira
idiossincrtica da arquitetura e implementao do processador especfico.
Todos esses mtodos provaram ser bem-sucedidos para focalizar a otimizao.
Cada um deles exige a cooperao entre o compilador e a ferramenta de perfil sobre
questes como formatos de dados, layout de cdigo e mtodos para mapear locais
de runtime para nomes baseados no programa.

380 CAPTULO 8 Introduo otimizao

Obteno de dados de perfil


Para o posicionamento de cdigo global, o compilador precisa de estimativas da
frequncia relativa de execuo de cada aresta no CFG. Pode-se obter essa informao
a partir de um perfil de execuo do cdigo: compilar o programa inteiro, execut-lo sob uma ferramenta de perfil com dados representativos e dar ao compilador
acesso aos dados de perfil resultantes. Pode-se obter essa informao a partir de um
modelo de execuo de programa; esses modelos variam de simples a elaborados, com
uma srie de precises.
Especificamente, o compilador precisa de contagens de execuo para as arestas do
CFG. O CFG na margem ilustra por que os contadores de aresta so superiores aos
contadores de bloco para posicionamento de cdigo. A partir dos contadores de execuo, mostrados como rtulos nas arestas, vemos que os blocos B0 e B5 so executados dez vezes cada um. O caminho (B0, B1, B3, B5) executado mais do que qualquer
outro caminho nesse fragmento de CFG.
Os contadores de aresta sugerem, por exemplo, que tornar o desvio (B1, B3) o caso
fall-through melhor do que torn-lo o caso tomado. Porm, com base nos contadores
de execuo para os blocos, o compilador deduziria que os blocos B3 e B4 tm a mesma
importncia; e poderia muito bem escolher a aresta menos importante, (B1, B4), como
o caso fall-through. O algoritmo de posicionamento de cdigo usa dados de perfil para
classificar as arestas do CFG pela frequncia de execuo. Assim, dados de aresta
precisos tm efeito direto sobre a qualidade dos resultados.

Construo de cadeias como caminhos quentes no CFG


Para determinar como deve arranjar o cdigo, o compilador constri um conjunto
de caminhos de CFG que incluem as arestas executadas com mais frequncia
chamados caminhos quentes (hot paths). Cada caminho uma cadeia de um ou mais
blocos. Cada caminho tem uma prioridade que ser usada para construir o layout
de cdigo final.
O compilador pode usar um algoritmo guloso para encontrar os caminhos quentes.
A Figura8.16 mostra um algoritmo deste tipo. Para comear, o algoritmo cria uma
cadeia degenerada a partir de cada bloco que contm exatamente esse bloco; e define
a prioridade de cada cadeia degenerada como um nmero grande, como o nmero de
arestas no CFG ou o maior inteiro disponvel.

FIGURA 8.16 Construo de caminhos quentes.

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

8.6 Otimizao global 381

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.

O algoritmo ignora os laos para si mesmo, x,x,


porque eles no afetam as decises de posicionamento.

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)

(B0)E, (B1)E, (B2)E, (B3)E, (B4)E, (B5)E


(B0, B1)0, (B2)E, (B3)E, (B4)E, (B5)E
(B0, B1)0, (B2)E, (B3, B5)1, (B4)E
(B0, B1)0, (B2)E, (B3, B5)1, (B4)E
(B0, B1, B3, B5)0, (B2)E, (B4)E
(B0, B1, B3, B5)0, (B2)E, (B4)E
(B0, B1, B3, B5)0, (B2, B4)3
(B0, B1, B3, B5)0, (B2, B4)3

0
1
2
2
3
3
4
4

As prioridades so mostradas como subscritos na cadeia e E o nmero de arestas


(edges) no CFG, como na Figura8.16.
O desempate entre arestas de mesma prioridade de modo diferente pode produzir
um conjunto diferente de cadeias. Por exemplo, se o algoritmo considerar (B4, B5)
antes de (B3, B5), ento ele produz duas cadeias: (B0, B1, B3)0 e (B2, B4, B5)1. Cadeias
diferentes podem gerar diferentes layouts de cdigo. O algoritmo de layout ainda
produz bons resultados, mesmo com uma ordenao no tima para arestas de
mesmo peso.

Executando o layout de cdigo


O conjunto de cadeias produzidas pelo algoritmo da Figura8.16 constitui uma ordem
parcial no conjunto de blocos bsicos. Para produzir uma imagem executvel do
cdigo, o compilador precisa colocar todos os blocos em uma ordem linear fixa. A
Figura8.17 mostra um algoritmo que calcula um layout linear a partir do conjunto
de cadeias. Ele codifica duas heursticas simples: (1) colocar os blocos de uma
cadeia em ordem, de modo que os desvios fall-through implementem as arestas da
cadeia, e (2) escolher entre alternativas usando o nmero de prioridade registrado
para as cadeias.

Desvio para a frente


Desvio cujo destino tem um endereo mais alto do
que sua origem chamado desvio para a frente. Em
algumas arquiteturas, estes desvios atrapalham menos
do que os desvios para trs.

382 CAPTULO 8 Introduo otimizao

FIGURA 8.17 Algoritmo de layout de cdigo.

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

(B0, B1, B3, B5)0


(B2, B4 )3

B0, B1, B3, B5


B0, B1, B3, B5, B2, B4

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

(B0, B1, B3)0


(B2, B4, B5)1

Layout de cdigo
B0, B1, B3
B0, B1, B3, B2, B4, B5

Se considerarmos que as frequncias de execuo estimadas esto corretas, no existe


motivo para preferir um layout em relao ao outro.

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:

8.6 Otimizao global 383

Aresta

Conjunto de cadeias

(B3, B4)
(B0, B3)
(B2, B4)
(B0, B2)
(B1, B3)
(B0, B1)

(B0)E, (B1)E, (B2)E, (B3)E, (B4)E


(B0)E, (B1)E, (B2)E, (B3, B4)0
(B0, B3, B4)0, (B1)E, (B2)E
(B0, B3, B4)0, (B1)E, (B2)E
(B0, B3, B4)0, (B1)E, (B2)E
(B0, B3, B4)0, (B1)E, (B2)E
(B0, B3, B4)0, (B1)E, (B2)E

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?

384 CAPTULO 8 Introduo otimizao

8.7 OTIMIZAO INTERPROCEDIMENTAL


Como discutimos no Captulo6, as chamadas de procedimento formam fronteiras nos
sistemas de software. A diviso de um programa em vrios procedimentos tem impactos
positivos e negativos sobre a capacidade do compilador de gerar cdigo eficiente. No
lado positivo, isto restringe a quantidade de cdigo que o compilador considera a
qualquer momento, efeito este que mantm pequenas as estruturas de dados e limita
o custo de diversos algoritmos em tempo de compilao, pela limitao dos tamanhos
dos problemas.
No lado negativo, a diviso do programa em procedimentos limita a capacidade do
compilador de entender o que acontece dentro de uma chamada. Por exemplo, considere
uma chamada de fee para fie que passa uma varivel x como um parmetro de
chamada por referncia. Se o compilador sabe que x tem valor 15 antes da chamada,
no pode usar esse fato aps a chamada, a menos que saiba que a chamada no pode
alterar x. Para usar o valor de x aps a chamada, o compilador precisa provar que o
parmetro formal correspondente a x no ser modificado por fie ou por qualquer
procedimento que ele chame, direta ou indiretamente.
Uma segunda fonte importante de ineficincia introduzida pelas chamadas de procedimento surge do fato de que cada chamada ocasiona a execuo de uma sequncia de
pr-chamada e ps-retorno no chamador, e outra de prlogo e eplogo no procedimento
chamado. Operaes implementadas nessas sequncias levam tempo. As transies
entre elas exigem saltos (potencialmente disruptivo). Essas operaes so todas um
overhead necessrio no caso geral para implementar as abstraes da linguagem-fonte.
Porm, em qualquer chamada especfica, o compilador pode ser capaz de adaptar as
sequncias ou o procedimento chamado ao ambiente de runtime local e alcanar um
desempenho melhor.
Esses efeitos, no conhecimento em tempo de compilao e nas aes em tempo de
execuo, podem introduzir ineficincias que a otimizao intraprocedimental no
consegue resolver. Para reduzir as ineficincias introduzidas por procedimentos separados, o compilador pode analisar e transformar vrios procedimentos juntos, usando
anlise e otimizao interprocedimentais. Essas tcnicas so igualmente importantes
em linguagens tipo Algol e em linguagens orientadas a objeto.
Nesta seo, vamos examinar duas otimizaes interprocedimentais diferentes: substituio em linha de chamadas de procedimento e posicionamento de procedimento
para a localidade de cdigo melhorada. Como a otimizao do programa inteiro exige
que o compilador tenha acesso ao cdigo que est sendo analisado e transformado, a
deciso de realizar a otimizao do programa inteiro tem implicaes na estrutura do
compilador. Assim, a subseo final discute as questes estruturais que surgem em um
sistema que inclui anlise e otimizao interprocedimentais.
O termo programa inteiro indica claramente analisar todo o cdigo. Preferimos o
termo interprocedimental quando falamos sobre analisar alguns, mas no todos, os
procedimentos.

8.7.1 Substituio em linha


Como vimos nos Captulos6 e7, o cdigo que o compilador precisa gerar para implementar uma chamada de procedimento envolve um nmero significativo de operaes. O cdigo precisa alocar um registro de ativao, avaliar cada parmetro real,
preservar o estado do chamador, criar o ambiente do procedimento chamado, transferir
o controle do chamador para o chamado, e de volta, e, se necessrio, retornar valores
do procedimento chamado para o chamador. De certa forma, essas aes em tempo

8.7 Otimizao interprocedimental 385

de execuo fazem parte do overhead de usar uma linguagem de programao; elas


mantm as abstraes da linguagem de programao, mas no so estritamente necessrias para calcular os resultados. Os compiladores otimizadores tentam reduzir o
custo desses overheads.
Em alguns casos, o compilador pode melhorar a eficincia do cdigo final substituindo o
local de chamada por uma cpia do corpo do procedimento chamado, apropriadamente
ajustada ao local de chamada especfico. Essa transformao, chamada substituio
em linha, permite que o compilador evite a maior parte do cdigo de ligao de procedimento e ajuste a nova cpia do corpo do procedimento chamado ao contexto do
chamador. Como a transformao move o cdigo de um procedimento para outro e
altera o grafo de chamadas do programa, a substituio em linha considerada uma
transformao interprocedimental.
Assim como em muitas otimizaes, a substituio em linha tem uma partio natural
em dois subproblemas: a transformao real e um procedimento de deciso que escolhe
os locais de chamada para colocar em linha. A transformao em si relativamente
simples. O procedimento de deciso mais complexo e tem impacto direto sobre o
desempenho.

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

FIGURA 8.18 Antes da substituio em linha.

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.

386 CAPTULO 8 Introduo otimizao

FIGURA 8.19 Aps a substituio em linha.

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.

8.7 Otimizao interprocedimental 387

As principais fontes de melhoria com a colocao em linha so: eliminao direta de


operaes e eficincia melhorada de outras otimizaes. O primeiro efeito aparece quando
partes da sequncia de ligao podem ser eliminadas; por exemplo, o cdigo de salvamento
e recuperao de registrador poderia ser eliminado em favor da permisso para que o
alocador de registradores tome essas decises. O conhecimento do chamador tambm
pode provar que outro cdigo dentro do procedimento chamado est morto ou intil.
Este ltimo efeito surge quando se tem mais informaes contextuais na otimizao global.
A principal fonte de degradao da substituio em linha a menor eficcia de otimizao de cdigo no cdigo resultante. A colocao do procedimento chamado em
linha pode aumentar o tamanho do cdigo e o tamanho do espao de nomes, o que, por
consequncia, pode aumentar a demanda por registradores nas vizinhanas do local de
chamada original. A eliminao do cdigo de salvamento e recuperao de registradores
muda o problema visto pelo alocador de registradores. Na prtica, qualquer um destes
pode levar a uma diminuio na eficcia da otimizao.
Em cada local de chamada, o compilador precisa decidir se coloca ou no a chamada em
linha. Para complicar as coisas, uma deciso tomada em um local de chamada afeta a
deciso em outros. Por exemplo, se a chama b que chama c, a escolha de colocar c em b
muda as caractersticas do procedimento que poderia ser colocado em linha em a e o grafo
de chamadas do programa subjacente. Alm do mais, a colocao em linha tem efeitos,
como o crescimento no tamanho do cdigo, que devem ser vistos por todo o programa; o
construtor de compiladores pode querer limitar o crescimento geral no tamanho do cdigo.
Procedimentos de deciso para a substituio em linha examinam diversos critrios
em cada local de chamada. Entre eles:
j

Tamanho do procedimento chamado. Se este procedimento for menor que o


cdigo de ligao (pr-chamada, ps-retorno, prlogo e eplogo), ento sua colocao em linha dever reduzir o tamanho do cdigo e executar menos operaes.
Esta situao surge, surpreendentemente, com muita frequncia.
Tamanho do chamador. O compilador pode limitar o tamanho geral de qualquer
procedimento para reduzir aumentos no tempo de compilao e diminuies na
eficcia da otimizao.
Contagem dinmica de chamadas. Uma melhoria em um local de chamada
executado com frequncia fornece maior benefcio do que em um local executado com pouca frequncia. Na prtica, os compiladores usam dados de perfil ou
estimativas simples, como 10 vezes a profundidade de aninhamento de lao.
Parmetros reais com valores constantes. O uso destes parmetros conhecidos
em um local de chamada cria o potencial para melhoria, pois essas constantes
podem ser incorporadas no corpo do procedimento chamado.
Contagem esttica de chamadas. Os compiladores normalmente rastreiam o
nmero de locais distintos que chamam um procedimento. Qualquer procedimento chamado de apenas um local pode ser colocado em linha sem qualquer
crescimento no espao de cdigo. O compilador deve atualizar essa mtrica ao
colocar o cdigo em linha, a fim de detectar procedimentos que passam a ter
apenas um local de chamada.
Contagem de parmetros. O nmero de parmetros pode servir como um substituto ao custo da ligao de procedimentos, pois o compilador deve gerar cdigo
para avaliar e armazenar cada parmetro real.
Chamadas no procedimento. Rastrear o nmero de chamadas em um procedimento fornece um jeito fcil de detectar folhas no grafo de chamada elas no
contm chamadas. Os procedimentos folha normalmente so bons candidatos
para colocao em linha.

As mudanas na arquitetura, como conjuntos maiores


de registradores, podem aumentar o custo de uma
chamada de procedimento, o que, por sua vez, pode
tornar mais atrativa a colocao de cdigo em linha.

388 CAPTULO 8 Introduo otimizao

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.

FIGURA 8.20 Heurstica de deciso tpica para a substituio em linha.

8.7.2 Posicionamento de procedimento


A tcnica de posicionamento de cdigo global, da Seo 8.6.2, rearrumou os blocos
dentro de um nico procedimento. Existe um problema semelhante na escala interprocedimental: rearrumar procedimentos dentro de uma imagem executvel.
Dado o grafo de chamada para um programa, com anotaes sobre as
frequncias de execuo medidas ou estimadas para cada local de chamada,
rearrume os procedimentos para reduzir tamanhos de conjuntos de trabalho de memria virtual e para limitar o potencial de conflitos induzidos por
chamada na cache de instrues.
O princpio simples. Se o procedimento p chama q, queremos que p e q ocupem
locais adjacentes na memria.
Lembre-se de que o grafo de chamada de um
programa tem um n para cada procedimento e uma
aresta (x, y) para cada chamada de x para y.

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-

8.7 Otimizao interprocedimental 389

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.

FIGURA 8.21 Algoritmo de posicionamento de procedimento.

A segunda metade do algoritmo constri iterativamente uma ordem para o posicionamento do procedimento. O algoritmo associa a cada n do grafo uma lista

390 CAPTULO 8 Introduo otimizao

ordenada de procedimentos. Essas listas especificam uma ordem linear entre os


procedimentos nomeados. Quando o algoritmo terminar, as listas especificaro
uma ordem total sobre os procedimentos que pode ser usada para posicion-los no
cdigo executvel.
O algoritmo usa os pesos de aresta do grafo de chamada para orientar o processo.
Repetidamente seleciona a aresta de peso mais alto, digamos, (x,y), da fila de prioridades
e combina sua origem x e seu destino y. Em seguida, ele precisa atualizar o grafo de
chamada para refletir a mudana.
1. Para cada aresta (y, z), chama ReSource para substituir (y, z) por (x, z) e
atualizar a fila de prioridades. Se (x, z) j existir, ReSource os combina.
2. Para cada aresta (z, y), chama ReTarget para substituir (z, y) por (z, x) e
atualizar a fila de prioridades. Se (z, x) j existir, ReTarget os combina.
Para afetar o posicionamento de y aps x, o algoritmo acrescenta list(y) a list(x).
Finalmente, exclui y e suas arestas do grafo de chamada.
O algoritmo termina quando a fila de prioridades est vazia. O grafo final ter um n
para cada um dos componentes conectados do grafo de chamada original. Se todos
os ns eram alcanveis a partir do n que representa a entrada do programa, o grafo
final consistir em um nico n. Se alguns procedimentos no eram alcanaveis,
seja porque no existe um caminho no programa que os chame ou porque esses
caminhos so obscurecidos por chamadas ambguas, ento o grafo final consistir
em mltiplos ns. De qualquer forma, o compilador e o ligador podem usar as
listas associadas aos ns no grafo final para especificar o posicionamento relativo
dos procedimentos.

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.

8.7 Otimizao interprocedimental 391

FIGURA 8.22 Etapas do algoritmo de posicionamento de procedimento.

8.7.3 Organizao do compilador para otimizao


interprocedimental
A construo de um compilador que realiza anlise e otimizao por dois ou mais
procedimentos fundamentalmente muda o relacionamento entre o compilador e o cdigo
que produz. Os compiladores tradicionais tm unidades de compilao de um nico
procedimento, uma nica classe ou um nico arquivo de cdigo; o cdigo resultante
depende unicamente do contedo dessa unidade de compilao. Quando o compilador
usa conhecimento sobre um procedimento para otimizar outro, a exatido do cdigo
resultante depende do estado dos dois procedimentos.
Considere o impacto da substituio em linha sobre a validade do cdigo otimizado.
Suponha que o compilador coloque fie em linha em fee. Qualquer alterao de
edio subsequente em fie precisar que fee seja compilado novamente uma
dependncia que resulta de uma deciso de otimizao, ao invs de qualquer relacionamento exposto no cdigo-fonte.
Se o compilador coleta e usa informaes interprocedimentais, problemas semelhantes
podem surgir. Por exemplo, fee pode chamar fie, que chama foe; suponha que o
compilador se baseie no fato de que a chamada a fie no mude o valor constante
conhecido da varivel global x. Se o programador mais tarde editar foe de modo que

Unidade de compilao
A poro de um programa apresentada ao compilador
normalmente chamada unidade de compilao.

392 CAPTULO 8 Introduo otimizao

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

Muitos sistemas modernos utilizam a ligao em


tempo de execuo, ou dinmica, para bibliotecas
compartilhadas. A ligao em tempo de execuo
limita as oportunidades para otimizao em tempo de
ligao.

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.

8.8 Resumo e perspectiva 393

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?

8.8 RESUMO E PERSPECTIVA


O otimizador em um compilador moderno contm uma coleo de tcnicas que tentam
melhorar o desempenho do cdigo compilado. Embora a maioria das otimizaes
tente melhorar a velocidade em tempo de execuo, elas tambm podem visar outras
medidas, como o tamanho do cdigo ou o consumo de energia. Este captulo mostrou
diversas tcnicas que operam sobre escopos que variam de blocos bsicos isolados at
programas inteiros.
As otimizaes melhoram o desempenho ajustando os esquemas de traduo gerais
aos detalhes especficos do cdigo mo. As transformaes em um otimizador tentam
remover o overhead introduzido no suporte de abstraes da linguagem-fonte, incluindo
estruturas de dados, estruturas de controle e verificao de erros. Tentam reconhecer
casos especiais que possuem implementaes eficientes e reescrever o cdigo para
concretizar essas economias. Tentam combinar os recursos necessrios do programa
com os recursos reais disponveis no processador-alvo, incluindo unidades funcionais,
capacidade e largura de banda de cada nvel na hierarquia de memria (registradores,
cache, TLBs Translation Lookaside Buffers e memria) e paralelismo em nvel de
instruo.
Antes que o otimizador possa aplicar uma transformao, precisa determinar que a reescrita proposta do cdigo seja segura que preserva o significado original do cdigo.
Normalmente, isso exige que o otimizador analise o cdigo. Neste captulo, vimos
diversas tcnicas para provar a segurana, variando da construo bottom-up da tabela
de valores na numerao de valor local at a computao de conjuntos LIVEOUT para
detectar variveis no inicializadas.
Uma vez que o otimizador tenha determinado que pode, seguramente, aplicar uma
transformao, precisa decidir se a reescrita melhorar ou no o cdigo. Algumas
tcnicas, como numerao de valor local, simplesmente consideram que as reescritas
que elas usam so lucrativas. Outras tcnicas, como substituio em linha, exigem
procedimentos de deciso complicados para determinar quando uma transformao
poderia melhorar o cdigo.
Este captulo forneceu uma introduo bsica ao campo da otimizao de cdigo
baseada em compilador. Introduziu muitos dos termos e questes que surgem na
otimizao. Mas no inclui uma seo de Tpicos avanados; o leitor interessado
encontrar material adicional sobre anlise esttica para dar suporte otimizao no
Captulo9 e sobre transformaes de otimizao no Captulo10.

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

394 CAPTULO 8 Introduo otimizao

cdigo tivesse se desenvolvido de uma maneira lgica e disciplinada, comeando com


tcnicas locais, estendendo-as primeiro para regies, depois procedimentos inteiros e
finalmente programas inteiros. Porm, este desenvolvimento ocorreu em um padro
mais irregular. Por exemplo, o compilador FORTRAN original [27] realizava otimizao local e global a primeira sobre rvores de expresso e a segunda para alocao
de registradores. O interesse em tcnicas regionais, como otimizao de lao [252],
e tcnicas de programa inteiro, como substituio em linha, tambm apareceu desde
cedo na literatura [16].
Numerao de valor local, com suas extenses para simplificaes algbricas e
desdobramento de constantes, normalmente creditada a Balke, no final da dcada
de 1960 [16,87], embora seja claro que Ershov conseguiu efeitos semelhantes em
um sistema muito mais antigo [139]. De modo semelhante, Floyd mencionou o
potencial para eliminao de redundncia local e aplicao de comutatividade [150].
A extenso para EBBs na numerao de valor superlocal natural e, sem dvida,
foi inventada e reinventada em muitos compiladores. Nosso tratamento deriva de
Simpson [53].
O algoritmo de balanceamento de altura de rvore deve-se a Hunt [200]; ele usa uma
funo de ordenao inspirada pelos cdigos de Huffman, mas facilmente adaptado
para outras mtricas. O algoritmo clssico para o balanceamento de rvores de instruo deve-se a Baer e Bovet [29]. O problema completo de encontrar e explorar o
paralelismo em nvel de instruo est intimamente relacionado ao escalonamento de
instruo (ver Captulo12).
Desenrolamento de lao a mais simples das otimizaes de aninhamento de lao, e tem
uma longa histria na literatura [16]. O uso do desenrolamento para eliminar operaes
de cpia de registrador-para-registrador, como na questo de reviso 2da Seo 8.5,
deve-se a Kennedy [214]. O desenrolamento pode ter efeitos sutis e surpreendentes
[108]. A seleo de fatores de desenrolamento tambm foi estudada [114,325].
As ideias que fundamentam a anlise viva j existem desde que os compiladores
automaticamente alocam locais de armazenamento para valores [242]. Beatty definiu
inicialmente a anlise viva em um relatrio tcnico interno da IBM [15]. Lowry e
Medlock discutem variveis ocupadas [p. 16, 252] e o uso desta informao na
eliminao de cdigo morto e no raciocnio sobre interferncia (ver Captulo13). A
anlise foi formulada como um problema de anlise de fluxo de dados global por volta
de 1971 [13,213]. A anlise viva ir aparecer novamente na construo do formato
SSA, no Captulo9, e na discusso sobre alocao de registradores, no Captulo13.
Os algoritmos de posicionamento de cdigo, nos escopos global e de programa inteiro,
so retirados de Pettis e Hansen [284]. Trabalhos subsequentes sobre este problema
concentraram-se na coleta de melhores dados de perfil e na melhoria dos posicionamentos [161,183]. Estudos mais recentes incluem o trabalho sobre alinhamento de
desvio [66,357] e layout de cdigo [78,93,161].
Substituio em linha tem sido discutida na literatura h dcadas [16]. Embora a
transformao seja simples, sua lucratividade tem sido assunto de muitos estudos
[31,99,119,301].
Anlise e otimizao interprocedimentais tm sido discutidas na literatura h dcadas
[18,34,322]. A substituio em linha tem uma longa histria na literatura [16]. Todos os cenrios mencionados na Seo 8.7.3 tm sido explorados em sistemas reais
[104,322,341]. A anlise de recompilao tratada com profundidade por Burke e
Torczon [64,335]. Veja as notas do Captulo9 para obter mais referncias sobre a
anlise interprocedimental.

8.8 Resumo e perspectiva 395

EXERCCIOS
Seo 8.4
1. Aplique o algoritmo da Figura8.4 a cada um dos seguintes blocos:

2. Considere um bloco bsico, como b0 ou b1 na questo anterior. Ele tem n


operaes, numeradas de 0 a n 1.
a. Para um nome x, USES(x) contm o ndice em b de cada operao que usa
x como um operando. Escreva um algoritmo para calcular o conjunto USES
para cada nome mencionado no bloco b. Se x LIVEOUT(b), ento acrescente duas entradas fictcias (> n) a USES(x).
b. Aplique seu algoritmo aos blocos b0 e b1 acima.
c. Para uma referncia a x na operao i do bloco b, DEF(x,i) o ndice em b
onde o valor de x visvel na operao i foi definido. Escreva um algoritmo
para calcular DEF(x,i) para cada referncia x em b. Se x for exposto para
cima em i, ento DEF(x,i) deve ser 1.
d. Aplique seu algoritmo aos blocos b0 e b1 acima.
3. Aplique o algoritmo de balanceamento de altura de rvore das Figuras8.7 e8.8
aos dois blocos no problema 1. Use a informao computada no Exerccio 2b
acima. Alm disso, considere que LIVEOUT(b0) {t3, t9}, LIVEOUT(b1)
{t7, t8, t9} e que os nomes de a at f so expostos para cima nos blocos.
Seo 8.5
4. Considere o seguinte grafo de fluxo de controle:

396 CAPTULO 8 Introduo otimizao

a. Encontre os blocos bsicos estendidos e liste seus caminhos distintos.


b. Aplique a numerao de valor local a cada bloco.
c. Aplique a numerao de valor superlocal aos EBBs e observe quaisquer melhorias que ela encontra alm daquelas encontradas pela numerao de valor local.
5. Considere a seguinte computao simples de estncil com cinco pontos:

Cada iterao do lao executa duas operaes de cpia.


a. O desenrolamento de lao pode eliminar as operaes de cpia. Que fator de
desenrolamento necessrio para eliminar todas as operaes de cpia nesse lao?
b. Em geral, se um lao contm vrios ciclos de operaes de cpia, como voc
pode calcular o fator de desenrolamento necessrio para eliminar todas as
operaes de cpia?
Seo 8.6
6. Em algum ponto p, LIVE(p) o conjunto de nomes que esto vivos em p.
LIVEOUT(b) simplesmente o conjunto LIVE ao final do bloco b.
a. Desenvolva um algoritmo que use como entrada um bloco b e seu conjunto
LIVEOUT, e produza como sada o conjunto LIVE para cada operao no bloco.
b. Aplique seu algoritmo aos blocos b0 e b1 no Exerccio 1, usando LIVEOUT(b0)={t3, t9} e LIVEOUT(b1)={t7, t8, t9}.
7. A Figura8.16 mostra um algoritmo para construir caminhos quentes no CFG.
a. Elabore uma construo alternativa de caminho quente que esteja atenta aos
empates entre arestas de mesmo peso.
b. Construa dois exemplos onde seu algoritmo leva a um layout de cdigo que
melhore aquele produzido pelo algoritmo do livro. Use o algoritmo de layout
de cdigo da Figura8.17 com as cadeias construdas pelo seu algoritmo e
aquelas criadas pelo algoritmo do livro.
Seo 8.7
8. Considere o seguinte fragmento de cdigo, que mostra um procedimento fee e
dois locais de chamada que invocam fee.

8.8 Resumo e perspectiva 397

a. Que benefcios de otimizao voc esperaria da colocao de fee em linha


em cada um dos locais de chamada? Estime a frao do cdigo de fee que
permaneceria aps a colocao em linha e a otimizao subsequente.
b. Com base na sua experincia da parte a, esboce um algoritmo de alto nvel
para estimar os benefcios da colocao em linha de um local de chamada
especfico. Sua tcnica deve considerar o local de chamada e o procedimento
chamado.
9. No Exerccio 8, caractersticas do local de chamada e seu contexto determinaram
a extenso qual o otimizador poderia melhorar o cdigo que foi colocado em
linha. Esboce, em alto nvel, um procedimento para estimar as melhorias que
poderiam advir da colocao em linha de um local de chamada especfico. (Com
tal estimador, o compilador poderia colocar em linha os locais de chamada com
os lucros estimados mais altos, parando quando alcanasse algum patamar no
tamanho do procedimento ou tamanho total do programa.)
10. Quando o algoritmo de posicionamento de procedimento, mostrado na Figura8.21, considera uma aresta p,q, sempre coloca p antes de q.
a. Formule uma modificao do algoritmo que consideraria o posicionamento
do destino de uma aresta antes de sua origem.
b. Construa um exemplo onde essa tcnica posiciona dois procedimentos mais
prximos do que o algoritmo original. Suponha que todos os procedimentos
sejam de tamanho uniforme.

Captulo

Anlise de fluxo de dados


VISO GERAL DO CAPTULO
Os compiladores analisam o formato IR do programa que est sendo compilado para
identificar oportunidades onde o cdigo pode ser melhorado e provar a segurana e a
lucratividade das transformaes que poderiam melhor-lo. Anlise de fluxo de dados
atcnica clssica para a anlise de programa em tempo de compilao. Ela que permite que
o compilador raciocine a respeito do fluxo de valores do programa em tempo de execuo.
Este captulo explora a anlise de fluxo de dados iterativa, que usa um algoritmo simples
de ponto fixo. A partir dos fundamentos da anlise de fluxo de dados, ele desenvolve a
construo da forma de atribuio nica esttica (SSA Static Single-Assignment),
ilustra o uso desta forma e introduz a anlise interprocedimental.
Palavras-chave: Anlise de fluxo de dados, Forma SSA, Dominncia, Propagao
de constante

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

400 CAPTULO 9 Anlise de fluxo de dados

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 Anlise de fluxo de dados iterativa 401

9.2 ANLISE DE FLUXO DE DADOS ITERATIVA


Compiladores usam anlise de fluxo de dados uma coleo de tcnicas para raciocinar em tempo de compilao sobre o fluxo de valores em tempo de execuo
para localizar oportunidades de otimizao e provar a segurana de transformaes
especficas. Como vimos com a anlise viva na Seo 8.6.1, os problemas na anlise
de fluxo de dados tomam a forma de um conjunto de equaes simultneas definidas
sobre conjuntos associados aos ns e arestas de um grafo que representa o cdigo que
est sendo analisado. A anlise viva formulada como um problema de fluxo de dados
global que opera sobre o grafo de fluxo de controle (CFG) de um procedimento.
Nesta seo, exploraremos as propriedades de problemas de fluxo de dados global e
suas solues com mais detalhes do que foi possvel no Captulo8. Iremos nos concentrar em uma tcnica de soluo especfica: um algoritmo iterativo de ponto fixo, que
tem vantagens de simplicidade e robustez. Como um exemplo inicial, examinaremos
o clculo de informao de dominncia. Quando precisarmos de um exemplo mais
complexo, retornaremos considerao dos conjuntos LIVEOUT.

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}

Para calcular esses conjuntos, o compilador pode resolver o seguinte problema de


fluxo de dados:

com as condies iniciais de que DOM(n0)={n0}, e n n0, DOM(n)=N, onde N o


conjunto de todos os ns no CFG. Essas equaes capturam de forma concisa a noo de
dominncia. Dado um grafo de fluxo qualquer ou seja, um grafo direcionado com uma
nica entrada e uma nica sada , as equaes calcularo corretamente o conjunto DOM
para cada n. Como calculam DOM(n) como uma funo dos predecessores de n, indicado como preds(n), essas equaes formam um problema de fluxo de dados para a
frente.
Para usar as equaes, o compilador pode empregar o mesmo procedimento em
trs etapas utilizado para a anlise viva na Seo 8.6.1. Ele precisa (1) construir um
CFG, (2) colher informaes iniciais para cada bloco e (3) resolver as equaes para

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.

402 CAPTULO 9 Anlise de fluxo de dados

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.

FIGURA 9.1 Solucionador iterativo para a dominncia.

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

9.2 Anlise de fluxo de dados iterativa 403

resposta fundamental se quisermos usar conjuntos DOM na otimizao. Finalmente,


qual a velocidade do solucionador? Os construtores de compilador devem evitar
algoritmos que sejam desnecessariamente lentos.

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.

Operador de encontro (meet operator)


Na teoria da anlise de fluxo de dados, o operador de
encontro (meet) usado para combinar fatos na confluncia de dois caminhos.

Nmero de ps-ordem
Rtulo dos ns de um grafo com sua ordem de visita
em uma travessia de ps-ordem.

404 CAPTULO 9 Anlise de fluxo de dados

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

Visitar os ns nessa ordem produz as seguintes iteraes e valores:


DOM(n)
1
2

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

Com essa numerao de RPO, o algoritmo executa as seguintes iteraes:


DOM(n)
1
2
3

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

9.2 Anlise de fluxo de dados iterativa 405

dados envolve o raciocnio sobre o comportamento do cdigo e do fluxo de dados


entre operaes. Como exemplo desse tipo de clculo, retornaremos anlise de
variveis vivas.

9.2.2 Anlise de varivel viva


Na Seo 8.6.1, usamos os resultados da anlise viva para identificar variveis
no inicializadas. Os compiladores usam informaes vivas para muitas outras
finalidades, como alocao de registradores e construo de algumas variantes da
forma SSA. Formulamos a anlise viva como um problema de fluxo de dados global
com a equao:

e a condio inicial de que LIVEOUT(n)=, n.

NOMEAO DE CONJUNTOS NAS EQUAES DE FLUXO DE DADOS


Na escrita das equaes de fluxo de dados para problemas clssicos, renomeamos
muitos dos conjuntos que contm informaes locais. Os artigos originais usavam
nomes de conjunto mais intuitivos. Infelizmente, estes nomes entram em conflito
uns com os outros entre os problemas. Por exemplo, expresses disponveis, variveis
vivas, definies de alcance e expresses antecipveis utilizam alguma noo de
um conjunto kill. Contudo, esses quatro problemas so definidos por trs domnios
distintos: expresses (AVAILOUT e ANTOUT), pontos de definio (REACHES) e variveis
(LIVEOUT). Assim, o uso de um nico nome de conjunto, como KILL ou KILLED, leva a
confuso entre os problemas.
Os nomes que adotamos codificam tanto o domnio quanto uma dica quanto ao
significado do conjunto. Assim, VARKILL(n) contm o conjunto de variveis mortas no
bloco n, enquanto EXPRKILL(n) contm o conjunto de expresses mortas no mesmo
bloco. De modo semelhante, UEVAR(n) contm o conjunto de variveis expostas para
cima no bloco n, enquanto UEEXPR(n) contm o conjunto de expresses expostas
para cima. Embora esses nomes sejam um tanto esquisitos, tornam explcita a
distino entre a noo de kill usada nas expresses disponveis (EXPRKILL) e aquela
usada nas definies de alcance (DEFKILL).

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.

406 CAPTULO 9 Anlise de fluxo de dados

Apesar dessas diferenas, o framework para resolver um caso de LIVEOUT o mesmo


que para um caso de DOM. O compilador precisa:
1. Realizar anlise de fluxo de controle para construir um CFG, como na
Figura5.6.
2. Calcular os valores dos conjuntos iniciais, como na Figura8.14a.
3. Aplicar o algoritmo iterativo, como na Figura8.14b.
Para ver as questes que surgem na soluo de casos de LIVEOUT, considere o
exemplo na Figura9.2. Ele concretiza o CFG de exemplo que temos usado ao
longo deste captulo. A Figura9.2a mostra o cdigo para cada bloco bsico. A
Figura9.2b mostra o CFG e a Figura9.2c mostra os conjuntos UEVAR e VARKILL
para cada bloco.

FIGURA 9.2 Exemplo de anlise viva.

A Figura9.3 mostra o progresso do solucionador iterativo no exemplo da Figura9.2


usando o mesmo RPO que usamos no clculo de DOM, a saber, B0, B1, B5, B8, B6, B7,
B2, B3, B4. Embora as equaes para LIVEOUT sejam mais complexas do que aquelas
para DOM, os argumentos para terminao, exatido e eficincia so semelhantes
queles para as equaes de dominncia.

9.2 Anlise de fluxo de dados iterativa 407

FIGURA 9.3 Progresso do solucionador vivo iterativo sobre o exemplo da Figura9.2.

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.

Lembre-se de que, em DOM, os conjuntos encolhem


monotonicamente.

Sabemos que o algoritmo alcanar um ponto fixo porque os conjuntos LIVEOUT


so finitos. O tamanho de qualquer conjunto LIVEOUT limitado pelo nmero de
variveis, |V|; qualquer conjunto LIVEOUT V ou um subconjunto prprio de V. No
pior caso, um conjunto LIVEOUT cresceria por um elemento a cada iterao; esse
comportamento terminaria aps n|V| iteraes, onde n o nmero de ns no CFG.
Esta propriedade, terminao do algoritmo iterativo devido combinao de monotonicidade e o nmero finito de valores possveis para os conjuntos subjacentes,
normalmente chamada propriedade de cadeia descendente finita. No problema de
dominncia, os conjuntos DOM encolhem monotonicamente e so limitados pelo
nmero de ns no CFG. Essa combinao, monotonicidade e tamanho limitado,
novamente garante a terminao.

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.

V {a, b, c, d, i, y,z} no cdigo da Figura9.2.


|V| sete.

408 CAPTULO 9 Anlise de fluxo de dados

ANLISE ESTTICA VERSUS ANLISE DINMICA


A noo de anlise esttica leva diretamente pergunta: e a anlise dinmica? Por
definio, a anlise esttica tenta estimar, em tempo de compilao, o que acontecer
em tempo de execuo. Em muitas situaes, o compilador no pode dizer o que
acontecer, embora a resposta possa ser bvia com o conhecimento de um ou mais
valores em tempo de execuo.
Considere, por exemplo, o fragmento em C:

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.

Para um problema retroativo, como LIVEOUT, o solucionador deve usar uma


travessia RPO no CFG reverso, como mostra a Figura9.4. A avaliao iterativa
apresentada anteriormente usava RPO no CFG. Para o CFG de exemplo, um RPO
no CFG reverso :

RPO (n)

B0
8

B1
7

B2
6

B3
1

B4
0

B5
5

B6
4

B7
2

B8
3

Visitar os ns em RPO no CFG reverso produz as iteraes apresentadas na Figura9.5.


Agora, o algoritmo termina em trs iteraes, ao invs das cinco exigidas com uma
travessia ordenada pela RPO no CFG. Comparando essa tabela com a computao

FIGURA 9.4 Solucionador de ps-ordem reversa em rodzio para LIVEOUT.

9.2 Anlise de fluxo de dados iterativa 409

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.

9.2.3 Limitaes na anlise de fluxo de dados


Existem limites sobre o que um compilador pode descobrir com a anlise de fluxo de dados.
Em alguns casos, eles surgem das suposies por trs da anlise. Em outros, pelos recursos
da linguagem que est sendo analisada. Para tomar decises inteligentes, o construtor de
compiladores deve entender o que a anlise de fluxo de dados pode e oque no pode fazer.
Ao calcular o conjunto LIVEOUT para um n n no CFG, o algoritmo iterativo usa
os conjuntos LIVEOUT, UEVAR e VARKILL para todos os sucessores de n no CFG.
Isso implicitamente considera que a execuo pode alcanar todos esses sucessores;
na prtica, um ou mais deles podem no ser alcanveis. Considere o fragmento de
cdigo mostrado na Figura9.6 junto com seu CFG.

FIGURA 9.6 Fluxo de controle limita a preciso da anlise de fluxo de dados.

A atribuio a x em B0 viva por causa do uso de x em B1. A atribuio a x em B2 mata


o conjunto de valores em B0. Se B1 no puder ser executado, ento o valor de x a partir
de B0 no est vivo aps a comparao com y, e x LIVEOUT(B0). Se o compilador
puder provar que o teste (y<x) sempre falso, ento o controle nunca ser transferido
para o bloco B1 e a atribuio a z nunca ser executada. Se a chamada a f no tiver efeitos
colaterais, a instruo inteira em B0 intil e no precisa ser executada. Como o resultado
do teste conhecido, o compilador pode eliminar completamente os blocos B0 e B1.
As equaes para LIVEOUT, porm, tomam a unio sobre todos os sucessores do
bloco, no apenas os sucessores executveis do bloco. Assim, o analisador calcula
LIVEOUT(B0) como:

410 CAPTULO 9 Anlise de fluxo de dados

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.

9.2 Anlise de fluxo de dados iterativa 411

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.

9.2.4 Outros problemas de fluxo de dados


Os compiladores usam anlises de fluxo de dados para provar a segurana da aplicao
de transformaes em situaes particulares. Assim, muitos e distintos problemasde
fluxo de dados tm sido propostos, cada um para controlar uma otimizao em particular.

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

Em seguida, resolve as seguintes equaes:

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).

412 CAPTULO 9 Anlise de fluxo de dados

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:

DEDEF(m) o conjunto de definies expostas para baixo (Downward-Exposed) em


m; aquelas definies em m para as quais o nome definido no subsequentemente
redefinido em m. DEFKILL(m) contm todos os pontos de definio que so obscurecidos por uma definio do mesmo nome em m; d DEFKILL(m) se d define
algum nome v e m contm uma definio que tambm define v. Assim, DEFKILL( m )
consiste dos pontos de definio que no esto obscurecidos em m.
DEDEF e DEFKILL so ambos definidos sobre o conjunto de pontos de definio,
mas calcular cada um deles exige um mapeamento de nomes (variveis e temporrios
gerados pelo compilador) para pontos de definio. Assim, colher a informao inicial
para as definies de alcance mais complexo do que para variveis vivas.

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.

Aqui, UEEXPR(m) o conjunto de expresses expostas para cima (Upward-Exposed)


aquelas usadas em m antes que estejam mortas. EXPRKILL(m) o conjunto de
expresses definidas em m; este o mesmo conjunto que aparece nas equaes paraexpresses disponveis.
Os resultados da anlise de expresses antecipveis so usados na movimentao
decdigo tanto para diminuir o tempo de execuo, na movimentao de cdigopreguioso,e para reduzir o tamanho do cdigo compilado, como tambm na elevao
de cdigo. As duas transformaes so discutidas na Seo 10.3.

9.2 Anlise de fluxo de dados iterativa 413

IMPLEMENTAO DE FRAMEWORKS DE FLUXO DE DADOS


As equaes para muitos problemas de fluxo de dados globais mostram uma
semelhana marcante. Por exemplo, expresses disponveis, variveis vivas, definies
de alcance e expresses antecipveis possuem funes de propagao na forma
onde c1 e c2 so constantes determinadas pelo cdigo real, e op1 e op2 operaes
padres de conjunto, como e . Essa semelhana aparece nas descries dos
problemas. E tambm deve aparecer em suas implementaes.
O construtor de compiladores pode facilmente desconsiderar os detalhes nos quais esses
problemas diferem e implementar um nico analisador parametrizado. O analisador
precisa de funes para calcular c1 e c2, implementaes dos operadores e uma indicao
da direo do problema. No retorno, ele produz a informao de fluxo de dados desejada.
Essa estratgia de implementao encoraja o reso de cdigo; oculta os detalhes
de baixo nvel do solucionador; e, ao mesmo tempo, cria uma situao em que o
construtor de compiladores pode lucrativamente investir esforos na otimizao da
implementao. Por exemplo, um esquema que permita ao framework implementar
f(x)=c1 op1 (x op2 c2 ) como nica funo pode superar uma implementao que usa
f1 (x)=c1 op1 x e f2 (x)=x op1 c2 e calcula f(x) como f1 (f2 (x)). Este esquema permite que
todas as transformaes cliente se beneficiem com a otimizao de representaes
de conjunto e de implementaes de operador.

Problemas de resumo interprocedimental


Ao analisar um nico procedimento, o compilador precisa levar em considerao o
impacto de cada chamada de procedimento. Na ausncia de informaes especficas sobre
a chamada, o compilador deve fazer suposies de pior caso, que levam em conta todas
as aes possveis do procedimento chamado, ou quaisquer procedimentos que, por sua
vez, ele chame. Essas suposies de pior caso podem degradar seriamente a qualidade
da informao global de fluxo de dados. Por exemplo, o compilador precisa supor que
o procedimento chamado modifica cada varivel que possa acessar; essa suposio
basicamente suspende a propagao de fatos em um local de chamada para todas as
variveis globais, variveis em nvel de mdulo e parmetros de chamada por referncia.
Para limitar tal impacto, o compilador pode calcular informaes de resumo em cada
local de chamada. Os problemas clssicos de resumo calculam o conjunto de variveis
que poderiam ser modificadas como resultado da chamada e que poderiam ser usadas
como resultado da chamada. O compilador pode ento usar esses conjuntos de resumo
calculados no lugar de suas suposies de pior caso.
O problema interprocedimental conhecido como pode modificar (may-modify) associa a
cada local de chamada um conjunto de nomes que o procedimento chamado e os procedimentos que ele chama podem modificar. Este um dos problemas mais simples na anlise
interprocedimental, mas pode ter impacto significativo sobre a qualidade da informao
produzida por outras anlises, como a propagao de constante global. O problema pode
modificar proposto como um conjunto de equaes de fluxo de dados sobre o grafo de
chamada do programa que associa a cada procedimento um conjunto MAYMOD.

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.

414 CAPTULO 9 Anlise de fluxo de dados

fora de p. Ele calculado como o conjunto de nomes definidos em p menos quaisquer


nomes que sejam estritamente locais a p.
Para solucionar o problema MAYMOD, o compilador pode definir MAYMOD(p)
como LOCALMOD(p) para todos os procedimentos p, e ento avaliar iterativamente a equao para MAYMOD at alcanar um ponto fixo. Dados os conjuntos
MAYMOD para cada procedimento, o compilador pode calcular o conjunto de
nomes que poderiam ser modificados em uma chamada especfica, e=(p,q),
calculando um conjunto S como unbinde (MAYMOD(q)) e ento acrescentando
a S quaisquer nomes que sejam pseudnimos (aliases) dentro do procedimento p
de nomes em S.
O compilador tambm pode calcular informaes sobre quais variveis poderiam ser
referenciadas como resultado da execuo de uma chamada de procedimento o
problema pode referenciar da anlise interprocedimental. As equaes para associar
cada procedimento p a um conjunto MAYREF(p) so semelhantes s equaes para
MAYMOD.

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?

9.3 Forma de atribuio nica esttica 415

9.3 FORMA DE ATRIBUIO NICA ESTTICA


Com o tempo, muitos e diferentes problemas de fluxo de dados tm sido formulados.
Se cada transformao usa sua prpria anlise idiossincrtica, a quantidade de tempo
e esforo gastos implementando, depurando e mantendo os passos de anlise podem
se tornar incrivelmente grandes. Para limitar o nmero de anlises que o construtor de
compiladores precisa implementar e o compilador precisa executar, desejvel usar
uma nica anlise para realizar mltiplas transformaes.
Uma estratgia para implementar essa anlise universal envolve a criao de uma
forma variante do programa que codifica tanto o fluxo de dados quanto o fluxo de controle diretamente na IR. A forma SSA, introduzida nas Sees 5.4.2 e 8.5.1, tem esta
propriedade. Ela pode servir como base para um grande conjunto de transformaes. A
partir de uma nica implementao que traduz o cdigo para a forma SSA, o compilador
pode realizar muitas das otimizaes escalares clssicas.
Considere os diversos usos da varivel x no fragmento de cdigo mostrado na
Figura9.7a. As linhas cinzas mostram quais definies podem alcanar cada uso de
x. A Figura9.7b mostra o mesmo fragmento, reescrito para converter x para a forma
SSA. As definies de x foram renomeadas, com subscritos, para garantir que cada
definio tenha um nico nome SSA. Para simplificar, deixamos as referncias a
outras variveis sem modificao.
A forma SSA do cdigo inclui novas atribuies (para x3, x5 e x6) que reconciliam
os nomes SSA distintos para x com os usos de x (nas atribuies para s e z). Essas
atribuies garantem que, ao longo de cada aresta no CFG, o valor atual de x tenha
recebido um nome exclusivo, independentemente de qual caminho levou o controle para
a aresta. Os lados direitos dessas atribuies contm uma funo especial, funo-,
que combina os valores de arestas distintas.
Uma funo- usa como argumentos os nomes SSA para os valores associados a cada
aresta que entra no bloco. Quando o controle entra em um bloco, todas as funes-
do bloco so executadas simultaneamente. Elas so avaliadas para o argumento que

FIGURA 9.7 SSA: codificao do fluxo de controle no fluxo de dados.

416 CAPTULO 9 Anlise de fluxo de dados

corresponde aresta ao longo da qual o controle entrou no bloco. Por conveno de


notao, escrevemos os argumentos da esquerda para a direita, para corresponder s
arestas da esquerda para a direita. Na pgina impressa, isto fcil. J na implementao,
exige algum cuidado.
A construo SSA insere funes- aps cada ponto no CFG onde vrios caminhos
convergem cada ponto de juno. Nos pontos de juno, nomes SSA distintos
precisam ser reconciliados para um nico nome. Aps o procedimento inteiro ter
sido convertido para a forma SSA, duas regras so vlidas: (1) cada definio
no procedimento cria um nome exclusivo, e (2) cada uso refere-se a uma nica
definio.
Para transformar um procedimento na forma SSA, o compilador deve inserir as funes- apropriadas para cada varivel no cdigo, e renomear as variveis com subscritos para fazer com que as duas regras sejam vlidas. Este plano simples, em duas
etapas, produz o algoritmo bsico de construo de SSA.

9.3.1 Um mtodo simples para criao da forma SSA


Para construir a forma SSA de um programa, o compilador deve inserir funes- nos
pontos de juno do CFG, e renomear variveis e valores temporrios conforme as
regras que controlam o espao de nomes SSA. O algoritmo segue este esboo:
1. Insero de funes-. No incio de cada bloco que possui vrios predecessores,
insira uma funo-, como y (y, y) , para cada nome y que o cdigo define
ou usa no procedimento atual. A funo- deve ter um argumento para cada
bloco predecessor no CFG. Esta regra insere uma funo- em cada caso onde
uma necessria. E tambm insere muitas funes- irrelevantes. O algoritmo
pode inserir as funes- em uma ordem arbitrria. A definio das funes-
exige que todas elas no topo de um bloco sejam executadas simultaneamente
ou seja, todas leiam seus parmetros de entrada simultaneamente e depois
escrevam seus valores de sada simultaneamente. Isto permite que o algoritmo
evite pequenos detalhes que uma ordenao poderia introduzir.
2. Renomeao. Aps as funes- terem sido inseridas, o compilador pode
calcular as definies de alcance (ver Seo9.2.4). Como as funes- inseridas
tambm so definies, garantem que somente uma definio alcance qualquer
uso. Em seguida, o compilador pode renomear cada uso, tanto de variveis
quanto de temporrios, para refletir a definio que o alcana.
O compilador precisa classificar as definies que alcanam cada funo- e fazer que
os nomes correspondam aos caminhos ao longo dos quais elas alcanam o bloco que
contm a funo-. Embora conceitualmente simples, esta tarefa exige algum cuidado.
Este algoritmo constri uma forma SSA correta para o programa. Cada varivel
definida exatamente uma vez, e cada referncia usa o nome de uma definio distinta. Porm, produz a forma SSA que tem, potencialmente, muito mais funes-
do que o necessrio. Funes- extras so problemticas; diminuem a preciso de
alguns tipos de anlise quando realizadas sobre a forma SSA, e ocupam espao,
de modo que o compilador desperdia memria representando funes- que so
redundantes (ou seja x j (x i , x i ) ) ou no esto vivas. Elas aumentam o custo de
qualquer algoritmo que use a forma SSA resultante, pois ele precisa passar por todas
as funes- irrelevantes.
Chamamos essa verso SSA de forma SSA mxima. Para criar a forma SSA com menos
funes-, preciso realizar mais trabalho; em particular, o compilador precisa analisar

9.3 Forma de atribuio nica esttica 417

o cdigo para determinar onde valores potencialmente distintos convergem no CFG.


Essa computao baseia-se na informao de dominncia descrita na Seo 9.2.1.
As prximas trs subsees apresentam, em detalhes, um algoritmo para criar a forma
SSA semipodada uma verso com menos funes-. A Seo9.3.2 mostra como
a informao de dominncia introduzida na Seo9.2.1 pode ser usada para calcular
fronteiras de dominncia para orientar a insero de funes-. A Seo9.3.3 d um
algoritmo para inserir funes- e a Seo9.3.4 mostra como reescrever nomes de
varivel para completar a construo da forma SSA. A Seo9.3.5 discute as dificuldades que podem surgir na converso do cdigo de volta para uma forma executvel.

9.3.2 Fronteiras de dominncia


O principal problema com a forma SSA mxima que ela contm muitas funes-.
Para reduzir este nmero, o compilador deve determinar mais cuidadosamente onde
elas so exigidas. A chave para posicionar funes- est em entender quais variveis
precisam de uma funo- em cada ponto de juno. Para resolver este problema
de modo eficiente e eficaz, o compilador pode dar meia-volta na questo. Ele pode
determinar, para cada bloco i, o conjunto de blocos que precisaro de uma funo-
para qualquer definio no bloco i. A dominncia desempenha um papel crtico nessa
computao.
Considere uma definio no n n do CFG. Esse valor potencialmente poderia alcanar
cada n m onde n DOM(m) sem necessidade de uma funo-, pois cada caminho
que alcana m passa por n. A nica maneira do valor no alcanar m se outra definio
do mesmo nome interferir ou seja, se ocorrer em algum n p entre n e m. Neste
caso, a definio em n no fora a presena de uma funo-; mas, a redefinio em
p a fora.
A definio no n n fora uma funo- nos pontos de juno que se encontram
imediatamente fora da regio do CFG que n domina. Mais formalmente, uma definio
no n n fora uma funo- correspondente em qualquer ponto de juno m onde (1)
n domina um predecessor de m (q preds(m) e n DOM(q)), e (2) n no domina m
estritamente. (O uso de dominncia estrita ao invs de dominncia permite uma funo- no incio de um lao de bloco nico. Neste caso, n=m, e m DOM(n){n}.)
Chamamos a coleo de ns m que tm essa propriedade com relao a n de fronteira
de dominncia de n, indicada por DF(n).

Dominncia estrita
a domina estritamente b se, e somente se, a
DOM(b) {b}.

Informalmente, DF(n) contm os primeiros ns alcanveis a partir de n, que n no


domina, em cada caminho do CFG saindo de n. No CFG do nosso exemplo em andamento, B5 domina B6, B7 e B8, mas no domina B3. Em cada caminho saindo de B5, B3
o primeiro n que B5 no domina. Assim, DF(B5)={B3}.

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.

418 CAPTULO 9 Anlise de fluxo de dados

A rvore de dominadores codifica de modo compacto tanto a informao de IDOM


quanto os conjuntos DOM completos para cada n. Dado um n n nesta rvore,
IDOM(n) simplesmente seu pai na rvore. Os ns em DOM(n) so exatamente
aqueles que se encontram no caminho da raiz da rvore de dominadores at n, inclusive a raiz e n. A partir da rvore, podemos ler os seguintes conjuntos:

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

Esses conjuntos DOM correspondem queles calculados anteriormente; o smbolo


indica um valor indefinido.

FIGURA 9.8 Algoritmo para calcular fronteiras de dominncia.

Clculo de fronteiras de dominncia


Para tornar a insero- eficiente, precisamos calcular a fronteira de dominncia para
cada n no grafo de fluxo. Poderamos formular um problema de fluxo de dadospara
calcular DF(n) para cada n no grafo. Usando a rvore de dominadores e o CFG, podemos formular um algoritmo simples e direto, mostrado na Figura9.8. Como somente
os ns que so pontos de juno no CFG podem ser membros de uma fronteira de
dominncia, primeiro identificamos todos os pontos de juno no grafo. Para um ponto
de juno j, examinamos cada um de seus predecessores no CFG.
O algoritmo baseado em trs observaes. Primeiro, os ns em um conjunto DF devem
ser pontos de juno no grafo. Segundo, para um ponto de juno j, cada predecessor
k de j deve ter j DF(k), pois k no pode dominar j se j tiver mais de um predecessor.
Finalmente, se j DF(k) para algum predecessor k, ento j tambm deve estar em
DF(l) para cada l DOM(k), a menos que l DOM( j).
O algoritmo segue essas observaes. Localiza os ns j que so pontos de juno no
CFG. Depois, para cada predecessor p de j, percorre a rvore de dominadores a partir de
p at encontrar um n que domina j. Pelas segunda e terceira observaes no pargrafo
anterior, j pertence a DF(l) para cada n l que o algoritmo atravessa nesse percurso
da rvore de dominadores, exceto para o n final do percurso, pois este domina j. Um
pequeno eforo necessrio para garantir que qualquer n seja acrescentado fronteira
de dominncia de um n apenas uma vez.
Para ver como isso funciona, considere novamente o CFG de exemplo e sua rvore
de dominncia. O analisador examina os ns em alguma ordem, procurando ns com

9.3 Forma de atribuio nica esttica 419

mltiplos predecessores. Supondo que apanhe os ns em ordem de nome, ele encontra


os pontos de juno como B1, depois B3, depois B7.
1. B1. Para o predecessor do CFG B0, o algoritmo descobre que B0 IDOM(B1),
demodo que nunca entra no lao while. Para o predecessor no CFG B3, acrescenta B1 a DF(B3) e avana para B1. Acrescenta B1 a DF(B1) e avana para B0,
onde termina.
2. B3. Para o predecessor no CFG B2, acrescenta B3 a DF(B2), avana para B1 que
IDOM(B3), e termina. Para o predecessor no CFG B7, acrescenta B3 a DF(B7) e
avana para B5. Acrescenta B3 a DF(B5) e avana para B1, onde termina.
3. B7. Para o predecessor no CFG B6, acrescenta B7 a DF(B6), avana para B5 que
IDOM(B7), e termina. Para o predecessor no CFG B8, acrescenta B7 a DF(B8) e
avana para B5, onde termina.
Acumulando esses resultados, obtemos as seguintes fronteiras de dominncia:

DF

B0

B1
{B1}

B2
{B3}

B3
{B1}

B4

B5
{B3}

B6
{B7}

B7
{B3}

B8
{B7}

9.3.3 Posicionamento de funes-


O algoritmo simples posicionou uma funo- para cada varivel no incio de cada
n de juno. Com as fronteiras de dominncia, o compilador pode determinar mais
precisamente onde as funes- poderiam ser necessrias. A ideia bsica simples.
Uma definio de x no bloco b fora uma funo- em cada n em DF(b). Como essa
funo- uma nova definio de x, pode, por sua vez, forar a insero de funes-
adicionais.
O compilador pode ainda estreitar o conjunto de funes- que insere. Uma varivel que
s est viva dentro de um nico bloco pode nunca ter uma funo- viva. Para aplicar esta
observao, o compilador pode calcular o conjunto de nomes que esto vivos por meio
de mltiplos blocos um conjunto que chamaremos nomes globais. Ele pode inserir
funes- para esses nomes e ignorar qualquer nome que no esteja nesse conjunto.
(Essa restrio distingue a forma SSA semipodada de outras variedades de forma SSA.)
O compilador pode encontrar os nomes globais a um baixo custo. Em cada bloco,
procura nomes com usos expostos para cima o conjunto UEVAR a partir do clculo
de variveis vivas. Qualquer nome que aparea em um ou mais conjuntos LIVEOUT
deve estar no conjunto UEVAR de algum bloco. Tomar a unio de todos os conjuntos
UEVAR d ao compilador o conjunto de nomes que esto vivos na entrada de um ou
mais blocos e, consequentemente, vivos em mltiplos blocos.
O algoritmo, mostrado na Figura9.9a, derivado do algoritmo bvio para calcular
UEVAR. Ele constri um nico conjunto, Globals, onde a computao de LIVEOUT
precisa calcular um conjunto distinto para cada bloco. Ao construir o conjunto Globals, tambm constri, para cada nome, uma lista de todos os blocos que contm
uma definio desse nome. Essas listas de blocos servem como uma lista de trabalho
inicial para o algoritmo de insero-.
O algoritmo para inserir funes- aparece na Figura9.9b. Para cada nome global x,
ele inicializa Worklist com Blocks(x). Para cada bloco b na worklist, ele
insere funes- no incio de cada bloco d na fronteira de dominncia de b. Como todas
as funes- em um bloco so executadas simultaneamente, por definio, o algoritmo
pode inseri-las no incio de d em qualquer ordem. Depois de acrescentar uma funo-
para x a d, ele acrescenta d a worklist para refletir a nova atribuio a x em d.

A palavra global usada aqui para significar interesse


pelo procedimento inteiro.

420 CAPTULO 9 Anlise de fluxo de dados

FIGURA 9.9 Insero de funo-.

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.

FIGURA 9.10 SSA de exemplo para a insero de funo-.

9.3 Forma de atribuio nica esttica 421

O primeiro passo no algoritmo de insero de funo- encontra nomes globais e


calcula o conjunto Blocks para cada nome. Para o cdigo na Figura9.10a, os nomes
globais so {a,b,c,d,i} . A Figura9.10d mostra os conjuntos Blocks. Observe que
o algoritmo cria conjuntos Blocks para y e z, embora eles no estejam em Globals.
A separao da computao de Globals daquela de Blocks evitaria instanciar esses
conjuntos extras, custa de outra passagem pelo cdigo.
O algoritmo de reescrita de funo- funciona com base em cada nome. Considere suas aes para a varivel a no exemplo. Ele inicializa a worklist como
Blocks(a), que contm B1 e B5. A definio em B1 faz que ele insira uma funo- no incio de cada bloco em DF(B1)={B1}. Esta ao tambm inclui B1 de
volta na worklist. Em seguida, ele remove B5da worklist e insere uma funo-
em cada bloco de DF(B5)={B3}. A insero em B3 tambm coloca B3 na worklist.
Quando B 3 sai da worklist, ele tenta acrescentar uma funo- em B 1, pois B 1
DF(B3). O algoritmo observa que B 1 j tem essa funo-, de modo que no
realiza a insero. Assim, o processamento de a termina com uma worklist vazia.
O algoritmo segue a mesma lgica para cada nome em Globals, para produzir
as seguintes inseres:

funes-

a
{B1,B3 }

b
{B1,B3 }

c
{B1,B3,B7 }

d
{B1,B3,B7 }

i
{B1 }

O cdigo resultante aparece na Figura9.11.

FIGURA 9.11 Cdigo de exemplo com funes-, antes da renomeao.

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.

422 CAPTULO 9 Anlise de fluxo de dados

OS DIFERENTES TIPOS DE FORMA SSA


Vrios tipos distintos de forma SSA tm sido propostos na literatura, que diferem em
seus critrios para insero de funes-. Para um determinado programa, eles podem
produzir diferentes conjuntos de funes-.
SSA mnima insere uma funo- em qualquer ponto de juno onde duas definies
distintas para o mesmo nome original se encontram. Este o nmero mnimo
consistente com a definio da SSA. Algumas dessas funes-, porm, podem estar
mortas; a definio no diz nada sobre os valores estarem vivos quando se encontram.
SSA podada acrescenta um teste de vivncia ao algoritmo de insero- para evitar a
incluso de funes- mortas. A construo deve calcular conjuntos LIVEOUT, de modo
que o custo da criao de SSAs podadas mais alto do que o de construir a SSA mnima.
SSA semipodada um meio-termo entre as SSAs mnimas e as podadas. Antes de inserir
funes-, o algoritmo elimina quaisquer nomes que no estejam vivos em uma fronteira
de bloco, o que pode encolher o espao de nomes e reduzir o nmero de funes- sem
o overhead de calcular conjuntos LIVEOUT. Este o algoritmo dado na Figura9.9.
Naturalmente, o nmero de funes- depende do programa especfico que est
sendo convertido para a forma SSA. Para alguns programas, as redues obtidas por
SSAs semipodadas e podadas so significativas. O encolhimento da forma SSA pode
levar a uma compilao mais rpida, pois os passos que usam a forma SSA operam
sobre programas que contm menos operaes e menos funes-.

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,

9.3 Forma de atribuio nica esttica 423

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.

FIGURA 9.12 Renomeao aps insero-.

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.

424 CAPTULO 9 Anlise de fluxo de dados

FIGURA 9.13 Estados no exemplo de renomeao.

9.3 Forma de atribuio nica esttica 425

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.

426 CAPTULO 9 Anlise de fluxo de dados

Bloco B7. Rename entra em B7 com o estado apresentado na Figura9.13i. Ele,


primeiro, renomeia os alvos da funo- com novos nomes SSA, c5 e d6. Em
seguida, reescreve a atribuio a b com o novo nome SSA b4. Depois, reescreve
os argumentos da funo- no sucessor de B7 no CFG, B3, com seus nomes
atuais. Como B7 no tem filhos na rvore de dominadores, ele remove os topos
das pilhas e retorna.
j Bloco B8. Rename entra em B8 com o estado apresentado na Figura9.13j. B8 no
tem funes-. Rename reescreve a atribuio a c com o novo nome SSA c6.
Examina o sucessor de B8 no CFG, que B7, e reescreve os argumentos correspondentes da funo- com seus nomes atuais, c6 e d4. Como B8 no possui
filhos na rvore de dominadores, ele remove o topo das pilhas e retorna.
A Figura9.14 mostra o cdigo depois que Rename termina.

FIGURA 9.14 Exemplo aps a renomeao.

Uma melhoria final


Uma implementao inteligente de NewName pode reduzir o tempo e o espao despendidos em manipulao de pilha. O principal uso das pilhas reiniciar o espao
de nomes na sada de um bloco. Se um bloco redefine o mesmo nome bsico vrias
vezes, NewName s precisa manter o nome mais recente. Isto aconteceu com a e c
no bloco B1 do exemplo. NewName pode sobrescrever o mesmo slot de pilha vrias
vezes dentro de um nico bloco.
Isto torna os tamanhos mximos da pilha previsveis; nenhuma pilha pode ser maior do
que a profundidade da rvore de dominadores. Isto reduz os requisitos de espao global,
evita a necessidade de testes de overflow em cada empilhamento e diminui o nmero
de operaes de pilha (push e pop), e exige outro mecanismo para determinar em quais
pilhas o topo deve ser removido na sada de um bloco. NewName pode encadear as
entradas da pilha para um bloco. ReName pode usar o encadeamento para remover os
topos das pilhas apropriadas.

9.3.5 Traduo a partir da forma SSA


Como os processadores modernos no implementam funes-, o compilador precisa
traduzir a forma SSA de volta para cdigo executvel. Pelos exemplos, tentador

9.3 Forma de atribuio nica esttica 427

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

Espao original de nomes

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

Espao de nomes SSA


A tabela da esquerda mostra um bloco de quatro operaes e os resultados que a LVN
produz quando usa o prprio espao de nomes do cdigo. A tabela da direita mostra o
mesmo exemplo usando o espao de nomes SSA. Como este espao d a a0 um nome
distinto de a1, a LVN pode substituir a avaliao de x0+y0 na operao final por uma
referncia a a0.
Observe, porm, que simplesmente retirar os subscritos dos nomes de varivel produz
um cdigo incorreto, pois c recebe o valor 17. Transformaes mais agressivas, como
a movimentao de cdigo e o desdobramento de cpia, podem reescrever a forma
SSA de maneira que introduzem problemas mais sutis.
Para evitar tais problemas, o compilador pode manter o espao de nomes SSA intacto
e substituir cada funo- por um conjunto de operaes de cpia uma ao longo
de cada aresta de entrada. Para a funo- x i (x j , x k ) , o compilador deve inserir
x i x j ao longo da aresta que transporta o valor xj e x i x k ao longo da aresta que
transporta xk.
A Figura9.15 mostra o exemplo atual aps funes- terem sido substitudas
por operaes de cpia. As quatro funes- que estavam em B 3 foram substitudas porum conjunto de quatro cpias em cada um de B 2 e B 7 . De modo
semelhante, as duas funes- em B 7 induzem um par de cpias em cada um
de B 6 e B 8. Nesses dois casos, o compilador pode inserir as cpias nos blocos
predecessores.

428 CAPTULO 9 Anlise de fluxo de dados

FIGURA 9.15 Exemplo aps insero de cpia para eliminar funes-.

Se os nomes definidos pelas cpias no forem LIVEIN


em B4, ento as cpias seriam inofensivas. Entretanto,
a estratgia do compilador deve funcionar se os nomes
forem LIVEIN.

As funes- em B1 revelam uma situao mais complicada. O compilador pode inserir


cpias diretamente em seu predecessor B0, mas no em seu predecessor B3. Como
B3 tem vrios sucessores, a insero de cpias para as funes- de B1 no final de B3
tambm faria com que eles fossem executados ao longo do caminho de B3 para B4, onde
no so necessrios e poderiam produzir resultados incorretos. Para remediar este problema, o compilador pode dividir a aresta (B3, B1), inserir um novo bloco entre B3 e B1,
e colocar as cpias nesse novo bloco, rotulado como B9 na Figura9.15. Aps a insero
de cpia, o exemplo parece ter muitas cpias suprfluas. Felizmente, o compilador
pode remover a maioria ou todas essas cpias com otimizaes subsequentes, como o
desdobramento de cpia (ver Seo 13.4.6).

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.

O problema de cpia perdida


Muitos algoritmos baseados em SSA exigem que as arestas crticas sejam divididas.
Porm, por vezes o compilador no pode (ou no deve) dividi-las. Por exemplo, se
a aresta crtica for o ramo de fechamento de um lao bastante executado, a incluso

9.3 Forma de atribuio nica esttica 429

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.

FIGURA 9.16 Exemplo do problema de cpia perdida.

430 CAPTULO 9 Anlise de fluxo de dados

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.

O problema de troca (swap)


Este problema surge da definio de execuo de funo- . Quando um bloco
executado, todas as suas funes- so executadas simultaneamente antes de qualquer
outra instruo no bloco. Ou seja, todas as funes- leem simultaneamente seus
parmetros de entrada apropriados e depois redefinem tambm de forma simultnea
seus valores-alvo.
A Figura9.17 mostra um exemplo simples do problema de troca. O painel a mostra o
cdigo original, um lao simples que troca os valores de x e y. O painel b, o cdigo
aps a converso da forma SSA e o desdobramento de cpia agressivo. Nessa forma,
com as regras para avaliao de funes-, o cdigo retm seu significado original.
Quando o corpo do lao executado, os parmetros da funo- so lidos antes que
quaisquer alvos da funo- sejam definidos. Na primeira iterao, ele l x0 e y0 antes
de definir x1 e y1. Em iteraes subsequentes, o corpo do lao l x1 e y1 antes de
redefini-los. O painel c mostra o mesmo cdigo, aps o algoritmo de insero de cpia
simples ter sido executado. Como as cpias so executadas sequencialmente, e no
simultaneamente, tanto x1 quanto y1 recebem o mesmo valor, um resultado incorreto.
primeira vista, pode parecer que a diviso da aresta de volta uma aresta crtica
ajuda. Porm, a diviso da aresta simplesmente coloca as mesmas duas cpias,
na mesma ordem, em outro bloco. O reparo direto para este problema adotar um
protocolo de cpia em dois estgios. O primeiro estgio copia cada um dos argumentos
da funo- para o seu prprio nome temporrio, simulando o comportamento das
funes- originais. O segundo estgio, ento, copia esses valores para os alvos da
funo-.
Infelizmente, esta soluo dobra o nmero de operaes de cpia exigidas para traduzir
a partir da forma SSA. No cdigo da Figura9.17a, isto exigiria quatro atribuies:
s y1, t x1, x1 s e y1 t. Todas essas atribuies so executadas em cada iterao
do lao. Para evitar esta perda de eficincia, o compilador deve tentar minimizar o
nmero de cpias que insere.

FIGURA 9.17 Exemplo do problema de troca.

9.3 Forma de atribuio nica esttica 431

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-.

O cdigo mnimo para o exemplo usaria uma cpia


extra; ele semelhante ao cdigo na Figura9.17a.

9.3.6 Uso da forma SSA


Um compilador usa a forma SSA porque melhora a qualidade da anlise, da otimizao,
ou de ambas. Para ver como a anlise sobre a forma SSA difere das tcnicas clssicas
de anlise de fluxo de dados apresentadas na Seo 9.2, considere a realizao da propagao de constante global na forma SSA usando um algoritmo chamado propagao
de constante simples esparsa (SSCP Sparse Simple Constant Propagation).

Semirreticulado
Um conjunto L e um operador de reunio tal que
a, b e c L,

No algoritmo SSCP, o compilador inclui anotaes constitudas de um valor em cada


nome SSA. O conjunto de valores possveis forma um semirreticulado, que consiste
em um conjunto L de valores e um operador de reunio, . O operador de reunio deve
ser idempotente, comutativo e associativo; ele impe uma ordem sobre os elementos
de L da seguinte forma:

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

Um semirreticulado tem um elemento inferior, , com as propriedades

Alguns semirreticulados tambm tm um elemento superior, , com as propriedades

Na propagao de constantes, a estrutura do semirreticulado usado para modelar os


valores de programa desempenha papel crtico na complexidade de execuo do algoritmo. O semirreticulado para um nico nome SSA aparece na margem, e consiste em
, e um conjunto infinito de valores de constantes distintos. Para duas constantes
quaisquer, ci e cj , ci cj=.
Na SSCP, o algoritmo inicializa o valor associado a cada nome SSA como , que
indica que o algoritmo no tem conhecimento do valor do nome SSA. Se o algoritmo
mais tarde descobrir que o nome SSA x tem o valor constante conhecido ci, modela esse
conhecimento atribuindo Value(x) ao elemento do semirreticulado ci. Se descobrir
que x tem um valor mutvel, modela este fato com o valor .
O algoritmo para SSCP, apresentado na Figura9.18, consiste em uma fase deinicializao e uma fase de propagao. A primeira percorre os nomes SSA. Para cada nome
SSA n, o algoritmo examina a operao que define n e define Value(n) de acordo
com um conjunto simples de regras. Se n for definido por uma funo-, SSCP define
Value(n) como . Se o valor de n for uma constante conhecida ci, SSCP define
Value(n) como ci. Se o valor de n no puder ser conhecido por exemplo, definido lendo um valor a partir de um meio externo , SSCPdefine Value(n) como .

1. a a=a,
2. a b=b a, e

432 CAPTULO 9 Anlise de fluxo de dados

Finalmente, se o valor de n no for conhecido, SSCP define Value(n) como . Se


Value(n) no for , o algoritmo acrescenta n worklist.

FIGURA 9.18 Algoritmo de propagao de constante simples esparsa.

x=x
x= x
ci cj=ci se ci=cj
ci cj= se ci cj

Regras para reunio

A fase de propagao simples, remove um nome SSA n da worklist. O algoritmo examina


cada operao op que usa n, onde op define algum nome SSA m. Se Value(m) j tiver
alcanado , ento nenhuma outra avaliao necessria. Caso contrrio, ele modela a
avaliao de op interpretando a operao sobre os valores de reticulado de seus operandos.
Se o resultado for inferior no reticulado a Value(m), ele reduz Value(m) de modo
correspondente e acrescenta m worklist. O algoritmo termina quando a worklist est vazia.
A interpretao de uma operao sobre valores de reticulado exige algum cuidado. Para
uma funo-, o resultado simplesmente a reunio dos valores de reticulado de todos
os argumentos da funo-; as regras para reunio aparecem na margem, em ordem de
precedncia. Para outros tipos de operaes, o compilador deve aplicar o conhecimento especfico do operador. Se qualquer operando tiver o valor de reticulado , a avaliao retorna
. Se nenhum dos operandos tiver o valor , o modelo deve produzir um valor apropriado.
Para cada operao produtora de valor na IR, SSCP precisa de um conjunto de regras
que modele o comportamento dos operandos. Considere a operao ab. Se a=4
e b=17, o modelo deve produzir o valor 68 para ab. Porm, se a=, ele deve
produzir para qualquer valor de b, exceto 0. Como a0=0, independente do valor
de a, a0 deve produzir o valor 0.

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.

9.3 Forma de atribuio nica esttica 433

Otimismo: o papel do elemento superior


O algoritmo SSCP difere dos problemas de fluxo de dados na Seo9.2 porque inicializa valores desconhecidos para o elemento de reticulado . No reticulado para
valores constantes, um valor especial que representa uma falta de conhecimento
sobre o valor do nome SSA. Essa inicializao desempenha papel crtico na propagao
de constante; ela permite que os valores se propaguem em ciclos no grafo, que so
causados pelos laos no CFG.
Por inicializar valores desconhecidos como , ao invs de , pode propagar alguns
valores em ciclos no grafo laos no CFG. Os algoritmos que comeam com o valor
, ao invs de , normalmente so chamados algoritmos otimistas. A intuio por
trs deste termo que a inicializao como permite que o algoritmo propague informaes em uma regio cclica, supondo de forma otimista que o valor ao longo da
aresta de volta confirmar essa propagao inicial. Uma inicializao para , chamada
pessimista, rejeita esta possibilidade.
Para ver isso, considere o fragmento SSA na Figura9.19. Se o algoritmo inicializar
x1 e x2 de forma pessimista como , no propagar o valor 17 no lao. Ao avaliar
a funo- para x1, ele calcula 17 para gerar . Com x1 definido como , x2
tambm definido como , mesmo que i12 tenha um valor conhecido, como 0.
Se, por outro lado, o algoritmo inicializar valores desconhecidos de forma otimista
como , o algoritmo pode propagar o valor de x0 no lao. Ao calcular um valor para x1,
avalia 17 e atribui o resultado, 17, a x1. Como o valor de x1 mudou, o algoritmo
coloca x1 na worklist. E, ento, reavalia a definio de x2. Se, por exemplo, i12 tem
o valor 0, ento isso atribui a x2 o valor 17 e acrescenta x2 worklist. Ao reavaliar a
funo-, ele calcula 17 17 e prova que x1 17.
Considere o que aconteceria se, ao invs, i12 tivesse valor 2. Ento, quando SSCP
avaliar x1 + i12 , atribui a x2 o valor 19. Agora, x1 recebe o valor 17 19, ou , o
que, por sua vez, propaga de volta para x2, produzindo o mesmo resultado final do
algoritmo pessimista.

O valor da forma SSA


No algoritmo SSCP, a forma SSA leva a um algoritmo simples e eficiente. Para ver este
ponto, considere uma tcnica clssica de fluxo de dados para a propagao de constantes, que associaria um conjunto CONSTANTSIN a cada bloco no cdigo, definiria uma
equao para calcular CONSTANTSIN(bi) como uma funo dos conjuntos CONSTANTSOUT dos predecessores de bi e definiria um procedimento para interpretar o
cdigo em um bloco para obter CONSTANTSOUT(bi) a partir de CONSTANTSIN(bi).
Ao contrrio, o algoritmo na Figura9.18 relativamente simples. Ele ainda tem um
mecanismo idiossincrtico para interpretar operaes, mas fora isso um simples
algoritmo de ponto fixo iterativo sobre um reticulado particularmente superficial.

FIGURA 9.19 Exemplo de constante otimista.

434 CAPTULO 9 Anlise de fluxo de dados

Na forma SSA, a etapa de propagao esparsa; ela s avalia expresses de valores de


reticulado em operaes (e funes-) que usem esses valores. Igualmente importante,
a atribuio de valores a nomes SSA individuais torna a inicializao otimista natural,
ao invs de artificial e complicada. Resumindo, SSA leva a um algoritmo esparso
eficiente e inteligvel para a propagao de constante global.
REVISO DA SEO
A forma SSA codifica informaes sobre os fluxos de dados e de controle de uma forma
intermediria conceitualmente simples. Para utilizar a SSA, o compilador primeiro precisa
transformar o cdigo para a forma SSA. Esta seo concentrou-se nos algoritmos necessrios para criar a forma SSA semipodada. A construo um processo em duas etapas.
A primeira insere funes- no cdigo em pontos de juno onde definies distintas
podem convergir. O algoritmo conta bastante com fronteiras de dominncia para
ganhar eficincia. A segunda cria o espao de nomes SSA, acrescentando subscritos aos
nomes bsicos originais durante uma travessia sistemtica do procedimento inteiro.
Como as mquinas modernas no implementam diretamente as funes-, o
compilador deve traduzir o cdigo a partir da forma SSA antes que ele possa ser executado. A transformao do cdigo ainda na forma SSA pode complicar a traduo
a partir da SSA. A Seo9.3.5 examinou o problema de cpia perdida e o problema
de troca, descrevendo tcnicas para lidar com ambos. Finalmente, a Seo9.3.6 mostrou um algoritmo que realiza a propagao de constante global sobre a forma SSA.
QUESTES DE REVISO
1. A forma SSA mxima inclui funes- irrelevantes, que definem valores no
vivos, e funes- redundantes, que mesclam valores idnticos (por exemplo,
x8 (x 7 , x 7 ) ). Como a construo de SSA semipodada lida com essas funes- desnecessrias?
2. Suponha que a mquina-alvo do seu compilador implemente swap r1, r2, uma
operao que simultaneamente realiza r1 r2 e r2 r1 . Que impacto a operao
swap teria sobre a traduo a partir da SSA?

swap pode ser implementada com a sequncia de trs operaes:
r1 r1 +r2
r2 r1 -r2
r1 r1 -r2
Quais seriam as vantagens e desvantagens de usar essa implementao de swap na
traduo a partir da SSA?

9.4 ANLISE INTERPROCEDIMENTAL


As ineficincias introduzidas pelas chamadas de procedimento aparecem em duas
formas distintas: (1) perda de conhecimento na anlise e otimizao de procedimento
nico que surge pela presena de um local de chamada na regio que est sendo analisada e transformada, e (2) overhead especfico introduzido para manter as abstraes
inerentes na chamada de procedimento. A anlise interprocedimental foi introduzida
para resolver o primeiro problema. Vimos, na Seo 9.2.4, como o compilador pode
calcular conjuntos que resumem os efeitos colaterais de um local de chamada. Esta
seo explora questes mais complexas na anlise interprocedimental.

9.4.1 Construo do grafo de chamada


O primeiro problema que o compilador precisa resolver na anlise interprocedimental
a construo de um grafo de chamada. No caso mais simples, em que cada chamada
de procedimento invoca um procedimento nomeado por uma constante literal, como em
call foo(x, y,z) , o problema simples. O compilador cria um n do grafo de chamada

9.4 Anlise interprocedimental 435

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.

436 CAPTULO 9 Anlise de fluxo de dados

Variveis com valor de procedimento (procedure-valued variables)


Se o programa utiliza variveis com valor de procedimento, o compilador deve
analisar o cdigo para estimar o conjunto de potenciais procedimentos chamados
em cada local que invoca uma varivel com valor de procedimento. Para comear, o
compilador pode construir o grafo especificado pelas chamadas que utilizam constantes literais explcitas. Em seguida, ele pode rastrear a propagao de funes
como valores por esse subconjunto do grafo de chamada, acrescentando arestas
conforme indicado.
Em SSCP, inicialize parmetros formais com valor de
funo com valores constantes conhecidos. Os parmetros reais com os valores conhecidos revelam onde as
funes so passadas.

O compilador pode usar uma analogia simples da propagao de constante global


para transferir valores de funo de uma entrada de procedimento para os locais de
chamada que os utilizam, usando a unio de conjunto como seu operador de reunio.
Para melhorar sua eficincia, ele pode construir expresses para cada varivel com
valor de parmetro usada em um procedimento (veja a discusso sobre funes de
salto na Seo9.4.2).
Como mostra o cdigo na Figura9.20a, uma anlise direta pode superestimar o conjunto
de arestas do grafo de chamada. O cdigo chama compose para calcular a(c) e b(d) .
Porm, uma anlise simples concluir que o parmetro formal g em compose pode
receber c ou d, e que, como resultado, o programa poderia compor qualquer um de
a(c) , a(d) , b(c) ou b(d) , como mostra a Figura9.20c. Para criar o grafo de chamada
exato, o programa precisa rastrear conjuntos de parmetros que so passados juntos,
pelo mesmo caminho. O algoritmo poderia, ento, considerar cada conjunto de forma
independente para obter um grafo exato. Como alternativa, poderia marcar cada valor
com o caminho que os valores atravessam e usar a informao de caminho para evitar
a incluso de arestas irrelevantes, como (a, d) ou (b, c) .

Nomes resolvidos contextualmente


Algumas linguagens permitem que os programadores usem nomes que so resolvidos
pelo contexto. Em linguagens orientadas a objeto, com hierarquia de herana, a ligao
de um nome de mtodo a uma implementao especfica depende da classe do receptor
e do estado da hierarquia de herana.
Se a hierarquia de herana e todos os procedimentos estiverem fixos no momento da
anlise, ento o compilador pode usar a anlise interprocedimental da estrutura de
classes para estreitar o conjunto de mtodos que podem ser chamados em um dado
local de chamada qualquer. O construtor do grafo de chamada precisa incluir uma aresta
desse local de chamada para cada procedimento ou mtodo que pode ser chamado.
A ligao dinmica, usada em alguns sistemas operacionais para reduzir os requisitos de memria virtual,
introduz complicaes semelhantes. Se o compilador
no puder determinar qual cdigo ir executar, no
pode construir um grafo de chamada completo.

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).

9.4 Anlise interprocedimental 437

Aspesquisas em tempo de execuo para dar suporte ao despacho dinmico so muito


mais dispendiosas do que uma chamada direta.

Outras questes de linguagem


Na anlise intraprocedimental, consideramos que o grafo de fluxo de controle tem
uma nica entrada e uma nica sada; acrescentamos um n de sada artificial se o
procedimento tiver vrios retornos. Na anlise interprocedimental, caractersticas de
linguagem podem criar os mesmos tipos de problemas.
Por exemplo, Java tem inicializadores e finalizadores. A mquina virtual Java chama um
inicializador de classe depois que carrega e verifica a classe; chama um inicializador de
objeto depois de alocar espao para o objeto, mas antes de retornar o cdigo hash do objeto. Mtodos de partida de processo (thread-start), finalizadores e destruidores tambm
tm a propriedade de que so executados sem uma chamada explcita no programa-fonte.
O criador do grafo de chamada dever prestar ateno a esses procedimentos. Inicializadores podem ser conectados aos locais que criam objetos; finalizadores poderiam ser
conectados ao n de entrada do grafo de chamada. As conexes especficas dependero
da definio da linguagem e da anlise sendo realizada. A anlise MAYMOD, por
exemplo, poderia ignor-los como irrelevantes, enquanto a propagao de constante
interprocedimental precisa das informaes de mtodos de inicializao e de partida.

9.4.2 Propagao de constante interprocedimental


A propagao de constante interprocedimental rastreia valores constantes conhecidos
de variveis e parmetros globais medida que se propagam pelo grafo de chamada,
tanto por corpos de procedimento quanto pelas arestas do grafo de chamada. O objetivo
da propagao de constante interprocedimental descobrir situaes em que um procedimento sempre recebe um valor constante conhecido ou um procedimento sempre
retorna um valor constante conhecido. Quando a anlise descobre tal constante, pode
especializar o cdigo para esse valor.
Conceitualmente, a propagao de constante interprocedimental consiste em trs subproblemas: descobrir um conjunto inicial de constantes, propagar valores constantes
conhecidos pelo grafo de chamada e modelar a transmisso de valores por meio dos
procedimentos.

Descobrir um conjunto inicial de constantes


O analisador precisa identificar, em cada local de chamada, quais parmetros reais tm
valores constantes conhecidos. Diversas tcnicas so possveis. O mtodo mais simples
reconhecer valores constantes literais usados como parmetros. Uma tcnica mais
eficaz e dispendiosa poderia utilizar uma etapa completa de propagao de constante
global (ver Seo9.3.6) para identificar parmetros com valor constante.

Propagar valores constantes conhecidos pelo grafo de chamada


Dado um conjunto inicial de constantes, o analisador propaga os valores constantes
pelas arestas do grafo de chamada e por meio dos procedimentos, da entrada para cada
local de chamada no procedimento. Essa parte da anlise semelhante aos algoritmos
iterativos de fluxo de dados da Seo 9.2. Este problema pode ser resolvido com o
algoritmo iterativo, mas o algoritmo pode exigir muito mais iteraes do que seria necessrio para problemas mais simples, como variveis vivas ou expresses disponveis.

Modelar a transmisso de valores pelos procedimentos


Toda vez que processa um n do grafo de chamada, o analisador precisa determinar
como os valores constantes conhecidos na entrada do procedimento afetam o conjunto

438 CAPTULO 9 Anlise de fluxo de dados

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.

FIGURA 9.21 Algoritmo iterativo de propagao de constante interprocedimental.

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.

9.4 Anlise interprocedimental 439

A segunda fase seleciona repetidamente um parmetro formal da worklist e o propaga.


Para propagar o parmetro formal f do procedimento p, o analisador localiza cada local
de chamada s em p e cada parmetro formal x (que corresponde a um parmetro real
X
X
do local de chamada s) de modo que f Support( J S ). Ele avalia J S e o combina
com Value(x). Se isso mudar Value(x), ele acrescenta x worklist. A worklist deve ser
implementada com uma estrutura de dados, como um conjunto esparso, que permita
apenas uma cpia de x na worklist (ver Seo B.2.3).
A segunda fase termina porque cada conjunto Value pode assumir no mximo trs
valores de reticulado: , algum ci e . Uma varivel x s pode entrar na worklist
quando seu Value inicial for calculado ou quando seu Value mudar. Cada varivel
x pode aparecer na worklist no mximo trs vezes. Assim, o nmero total de mudanas
limitado e a iterao termina. Depois que a segunda fase termina, uma etapa de ps
-processamento constri os conjuntos de constantes conhecidas na entrada de cada
procedimento.

Implementao da funo de salto


As implementaes das funes de salto variam desde aproximaes estticas simples,
que no mudam durante a anlise, at pequenos modelos parametrizados, para esquemas
mais complexos que realizam uma extensa anlise em cada avaliao de funo de salto.
Em qualquer um desses esquemas, vrios princpios so mantidos. Se o analisador
determinar que o parmetro x no local de chamada s uma constante conhecida c, ento
J SX =c e Support( J SX )=. Se y Support( J SX ) e Value(y)=, ento J SX =. Se
X
X
o analisador determinar que o valor de J S no pode ser determinado, ento J S = .
X

O analisador pode implementar J S de vrias maneiras. Uma implementao simples


poderia propagar uma constante somente se x for o nome SSA de um parmetro formal
no procedimento contendo s. (Uma funcionalidade semelhante pode ser obtida usando a
informao de REACHES, da Seo 9.2.4.) Um esquema mais complexo poderia construir expresses compostas de nomes SSA de parmetros formais e constantes literais.
Uma tcnica eficaz e dispendiosa seria executar o algoritmo SSCP por demanda para
atualizar os valores das funes de salto.

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 = .

440 CAPTULO 9 Anlise de fluxo de dados

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?

9.5 TPICOS AVANADOS


A Seo9.2 focou a anlise iterativa de fluxo de dados. O texto enfatiza a abordagem
iterativa porque ela simples, robusta e eficiente. Outras tcnicas de anlise de fluxo
de dados costumam contar bastante com propriedades estruturais do grafo subjacente.
A Seo9.5.1 discute a redutibilidade do grafo de fluxo uma propriedade crtica
para a maioria dos algoritmos estruturais.
A Seo9.5.2 visita novamente o framework de dominncia iterativo da Seo 9.2.1. A
simplicidade desse framework o torna atraente; porm, algoritmos mais especializados e
complexos tm complexidades assintticas muito menores. Nesta seo, apresentaremos
um conjunto de estruturas de dados que torna a tcnica iterativa simples competitiva com
os velozes algoritmos de dominador para os grafos de fluxo de at vrios milhares de ns.

9.5.1 Algoritmos de fluxo de dados estruturais e redutibilidade


Neste e no Captulo8, apresentamos o algoritmo iterativo porque, em geral, funciona
sobre qualquer conjunto de equaes bem formadas sobre qualquer grafo. Existem outros algoritmos de anlise de fluxo de dados; muitos funcionam derivando um modelo
simples da estrutura de fluxo de controle do cdigo que est sendo analisado e usando
esse modelo para resolver as equaes. Frequentemente, esse modelo criado encontrando-se uma sequncia de transformaes para o grafo que reduza sua complexidade
combinando ns ou arestas de maneira cuidadosamente definida. Este processo de
reduo de grafo est no centro de quase todo algoritmo de fluxo de dados, exceto o
algoritmo iterativo.
Algoritmos de fluxo de dados no iterativos normalmente funcionam aplicando uma
srie de transformaes a um grafo de fluxo; cada transformao seleciona um subgrafo

9.5 Tpicos avanados 441

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

FIGURA 9.22Transformaes T1 e T2.

partir de a para algum n n, ela as consolida. A Figura9.22 mostra T2 aplicada a


a e b, indicado como T2(a, b). Qualquer grafo que possa ser reduzido a um nico
n pela aplicao repetida de T1 e T2 considerado redutvel. Para entender como
isto funciona, considere o CFG do nosso exemplo em andamento. A Figura9.23a
mostra uma sequncia de aplicaes de T1 e T2 que o reduz a um grafo de nico
n. Ele aplica T2 at que no haja mais oportunidades: T2(B1, B2), T2(B5, B6), T2(B5,
B8), T2(B5, B7), T2(B1, B5) e T2(B1, B3). Em seguida, usa T1(B1) para remover o lao,
seguido por T2(B0, B1) e T2(B0, B4) para completar a reduo. Como o grafo final
um nico n, o grafo original redutvel.
Outras ordens de aplicao tambm reduzem o grafo. Por exemplo, se comearmos
com T2(B1, B5), isto leva a uma srie diferente de transformaes. T1 e T2 tm a propriedade Church-Rosser finita, que garante que o resultado final independente da
ordem de aplicao e que a sequncia termina. Assim, o analisador pode aplicar T1
e T2 de modo oportunista encontrando locais no grafo onde uma delas se aplica
e usando-a.
A Figura9.23b mostra o que pode acontecer quando aplicamos T1 e T2 a um grafo
com laos de vrias entradas. O analisador usa T2(B0, B1) seguido por T2(B0, B5). Nesse
ponto, porm, nenhum n ou par de ns restantes um candidato para T1 ou T2. Assim,
o analisador no pode reduzir o grafo ainda mais. (Nenhuma outra ordem funcionaria.)
O grafo no redutvel a um nico n; ele irredutvel.

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.

442 CAPTULO 9 Anlise de fluxo de dados

FIGURA 9.23 Sequncias de reduo para grafos de exemplo.

A falha de T1 e T2 em reduzir esse grafo vem de uma propriedade fundamental do


grafo. O grafo irredutvel porque contm um lao, ou ciclo, que possui arestas que
entram nele em diferentes ns. Em termos da linguagem-fonte, o programa que gerou
o grafo tem um lao com vrias entradas. Podemos ver isso no grafo; considere o ciclo
formadopor B2 e B3. Ele tem arestas que entram nele por B1, B4 e B5. De modo semelhante, o ciclo formado por B3 e B4 tem arestas que entram nele por B2 e B5.
A irredutibilidade impe um problema srio para algoritmos baseados em transformaes
como T1 e T2. Se a sequncia de reduo no puder ser completada, produzindo um grafo
com nico n, ento o mtodo deve relatar a falha, modificar o grafo dividindo um ou mais
ns, ou usar uma tcnica iterativa para resolver o sistema no grafo reduzido. Em geral, os
mtodos baseados em reduzir o grafo de fluxo estruturalmente so limitados a grafos redutveis. O algoritmo iterativo, ao contrrio, funciona corretamente sobre um grafo irredutvel.

9.5 Tpicos avanados 443

Para transformar um grafo irredutvel em redutvel, o analisador pode dividir um ou


mais ns. A diviso mais simples para o grafo de exemplo, mostrado na margem, copia
B2 e B4 para criar B2 e B4, respectivamente. O analisador, ento, redireciona as arestas
(B3, B2) e (B3, B4) para formar um lao complexo, {B3, B2, B4}. O novo lao tem uma
nica entrada, atravs de B3.
Essa transformao cria um grafo redutvel que executa a mesma sequncia de operaes do grafo original. Os caminhos que, no grafo original, entravam em B3 a partir de
B2 ou B4, agora so executados como prlogos do lao {B3, B29, B49}. Tanto B2 quanto
B4 tm predecessores exclusivos no novo grafo. B3 tem vrios predecessores, mas esta
a nica entrada do lao, e o lao redutvel. Assim, a diviso de n produziu um
grafo redutvel, custa da clonagem de dois ns.
Tanto a cultura popular quanto os estudos publicados sugerem que os grafos irredutveis
raramente surgem na anlise de fluxo de dados global. O surgimento da programao
estruturada durante a dcada de 1970 reduziu a chance de os programadores usarem
transferncias de controle arbitrrias, como uma instruo goto. As construes de
lao estruturadas, como os laos do, for, while e until, no pode produzir grafos
irredutveis. Porm, a transferncia de controle para fora de um lao (por exemplo, a instruo break da linguagem C) cria um CFG que irredutvel para uma anlise para trs.
(Como o lao tem vrias sadas, o CFG reverso tem vrias entradas.) De modo semelhante,
grafos irredutveis podem surgir com mais frequncia na anlise interprocedimental
devido a sub-rotinas mutuamente recursivas. Por exemplo, o grafo de chamada de um
parser com descida recursiva, codificado mo, provavelmente ter subgrafos irredutveis.
Felizmente, um analisador iterativo pode lidar com eles de modo correto e eficaz.

9.5.2 Acelerando o framework de dominncia iterativo


O framework iterativo para calcular a dominncia particularmente simples. Enquanto
a maioria dos problemas de fluxo de dados tem equaes envolvendo vrios conjuntos,
as equaes para DOM envolvem calcular uma interseo aos pares sobre os conjuntos
DOM e a incluso de um nico elemento a esses conjuntos. A natureza simples dessas
equaes apresenta uma oportunidade para usar uma estrutura de dados particularmente
simples para melhorar a velocidade do clculo de DOM.
O framework DOM iterativo usa um conjunto DOM discreto em cada n. Podemos
reduzir a quantidade de espao exigida pelos conjuntos DOM observando que a mesma informao pode ser representada com um nico fato em cada n, seu dominador
imediato, ou IDOM. Pelos IDOMs para os ns, o compilador pode calcular todas as
outras informaes de dominncia de que precisa.
Lembre-se de nosso CFG de exemplo da Seo 9.2.1, repetido na margem, com sua
rvore de dominadores. Seus conjuntos IDOM so os seguintes:

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.

444 CAPTULO 9 Anlise de fluxo de dados

Assim, podemos usar os conjuntos IDOM como um substituto para os conjuntos


DOM, desde que possamos fornecer mtodos eficientes para inicializar os conjuntos e
fazer sua interseo. Para lidar com as inicializaes, vamos reformular ligeiramente
o algoritmo iterativo. Para fazer a interseo de dois conjuntos DOM a partir de seus
conjuntos IDOM, usaremos o algoritmo mostrado no procedimento Intersect, no
final da Figura9.24, que conta com dois fatos crticos:
1. Quando o algoritmo percorre o caminho de um n at a raiz para recriar um
conjunto DOM, encontra os ns em uma ordem consistente. A interseo de dois
conjuntos DOM simplesmente o sufixo comum dos rtulos nos caminhos dos
ns at a raiz.
2. O algoritmo precisa ser capaz de reconhecer o sufixo comum. Ele comea nos
dois ns, i e j, cujos conjuntos devem sofrer interseo, e percorre para cima a
partir de cada um at a raiz. Se nomearmos os ns com seus nmeros de RPO,
ento uma comparao simples permitir que o algoritmo descubra o ancestral
comum mais prximo o IDOM de i e j.
O algoritmo Intersect na Figura9.24 uma variante do algoritmo clssico de dois
dedos. Ele usa dois ponteiros para rastrear os caminhos para cima na rvore. Quando
eles combinam, ambos apontam para o n que representa o resultado da interseo.

FIGURA 9.24 O algoritmo iterativo de dominadores modificado.

O algoritmo atribui a IDOM(b0) o valor b0 para simplificar o restante do algoritmo.

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.

9.6 Resumo e perspectiva 445

Para ver como o algoritmo opera, considere o grafo da Figura9.25a. A Figura9.25b


mostra um RPO para esse grafo que ilustra os problemas causados pela irredutibilidade.
Usando essa ordem, o algoritmo falha na computao dos IDOMs de B3, e de B4 na
primeira iterao. So necessrias duas iteraes para que o algoritmo corrija esses
IDOMs, e uma iterao final para reconhecer que os IDOMs pararam de mudar.
Esse algoritmo melhorado executado rapidamente. Ele tem um pequeno requisito de
memria. Em qualquer grafo redutvel, ele termina em dois passos: o primeiro passo
calcula os conjuntos IDOM corretos, e o segundo confirma que nenhuma mudana
ocorreu. Um grafo irredutvel usar mais de dois passos. Na verdade, o algoritmo
fornece um teste de redutibilidade rpido se qualquer entrada IDOM mudar no
segundo passo, o grafo irredutvel.

FIGURA 9.25 Um grafo com uma forma mais complexa.

9.6 RESUMO E PERSPECTIVA


A maior parte da otimizao adapta o cdigo de uso geral ao contexto especfico
que ocorre no cdigo compilado. A capacidade do compilador de adaptar o cdigo
normalmente limitada pela falta de conhecimento sobre a variao de comportamentos
do programa em tempo de execuo.
A anlise de fluxo de dados permite que o compilador modele o comportamento de
runtime de um programa em tempo de compilao e retire o conhecimento importante
e especfico dos modelos. Muitos problemas de fluxo de dados tm sido propostos; este
captulo apresentou vrios. Muitos tm propriedades que levam a anlises eficientes.
Em particular, os problemas que podem ser expressos em frameworks iterativos tm
solues eficientes usando solucionadores iterativos simples.
SSA uma forma intermediria que codifica as informaes de fluxo de dados e as
informaes dependentes de controle para o espao de nomes do programa. Trabalhar
com a forma SSA normalmente simplifica a anlise e a transformao. Muitas transformaes modernas baseiam-se na forma SSA do cdigo.

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

446 CAPTULO 9 Anlise de fluxo de dados

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.

9.6 Resumo e perspectiva 447

2. Na anlise de varivel viva, como o compilador deve tratar um bloco contendo


uma chamada de procedimento? O que o conjunto UEVar do bloco deve conter?
O que seu conjunto VARKILL deve conter?
3. Na computao de expresses disponveis, a inicializao define

Construa um pequeno programa de exemplo que mostre por que a segunda


inicializao necessria. O que acontece no seu exemplo se os conjuntos
AVAILIN forem inicializados uniformemente como ?
4. Para cada um dos seguintes grafos de fluxo de controle:

a. Calcule as numeraes em ps-ordem reversa para o CFG e para o CFG


reverso.
b. Calcule a pr-ordem reversa no CFG.
c. A pr-ordem reversa no CFG equivalente ps-ordem no CFG reverso?
Seo 9.3
5. Considere os trs grafos de fluxo de controle mostrados a seguir.

a. Calcule as rvores de dominadores para os CFGs a, b e c.


b. Calcule as fronteiras de dominncia para os ns 3 e 5 do CFG a, os ns 4 e 5
do CFG b, e os ns 3 e 11 do CFG c.

448 CAPTULO 9 Anlise de fluxo de dados

6. Traduza o cdigo mostrado na Figura9.26 para a forma SSA. Mostre apenas


ocdigo final, aps a insero- e a renomeao.
7. Considere o conjunto de todos os blocos que recebem uma funo- devido
a uma atribuio x ... em algum bloco b. O algoritmo da Figura9.9 insere
uma funo- em cada bloco em DF(b). Cada um desses blocos acrescentado
worklist; eles, por sua vez, podem acrescentar ns em seus conjuntos DF
worklist. O algoritmo usa uma lista de verificao para evitar a incluso
de um bloco worklist mais de uma vez. Chame o conjunto de todos esses
blocos de DF+(b). Podemos definir DF+(b) como o limite da sequncia:

FIGURA 9.26 CFG para o Exerccio 6.

O uso desses conjuntos estendidos, DF+(b), leva a um algoritmo mais simples


para inserir funes-.
a. Desenvolva um algoritmo para calcular DF+(b).
b. Desenvolva um algoritmo para inserir funes- usando esses conjuntos
DF+.
c. Compare o custo geral do seu algoritmo, incluindo a computao de conjuntos DF+, com o custo do algoritmo de insero- dado na Seo 9.3.3.
8. A construo de SSA mxima simples e intuitiva. Porm, pode inserir muito
mais funes- do que o algoritmo semipodado. Em particular, ela pode inserir
funes- redundantes ( x i (x j ,x j ) ) e funes- mortas em que o resultado nunca utilizado.
a. Proponha um mtodo para detectar e remover as funes- extras
que a construo mxima insere.

9.6 Resumo e perspectiva 449

b. Seu mtodo pode reduzir o conjunto de funes- a apenas aquelas


que a construo semipodada insere?
c. Compare a complexidade assinttica do seu mtodo com a da construo
semipodada.
9. A informao de dominncia e a forma SSA nos permitem melhorar o algoritmo
de numerao de valor superlocal (SVN) da Seo 8.5.1. Suponha que o cdigo
esteja na forma SSA.
a. Para cada n no CFG com mltiplos predecessores, a SVN comea com uma
tabela hash vazia. Para tal bloco, bi, voc pode usar informaes de dominncia para selecionar um bloco cujos fatos precisam ser mantidos na entrada
debi?
b. Com quais propriedades da forma SSA esse algoritmo conta?
c. Supondo que o cdigo j esteja na forma SSA, com informaes de dominncia disponveis, qual o custo extra dessa numerao de valor baseada
emdominador?
Seo 9.4
10. Mostre, para cada um dos seguintes grafos de fluxo de controle, se ele redutvel
ou no:

11. Prove que a definio a seguir de um grafo redutvel equivalente definio


que usa as transformaes T1 e T2: Um grafo G redutvel se, e somente se,
para cada ciclo em G existe um n n no ciclo com a propriedade de que n
domina cada n nesse ciclo.
12. Mostre uma sequncia de redues, usando T1 e T2, que reduza o grafo a seguir:

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

452 CAPTULO 10 Otimizaes escalares

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.

Um otimizador alcana esses objetivos por meio de dois mecanismos principais:


elimina o overhead desnecessrio introduzido por abstraes da linguagem de programao e combina as necessidades do programa resultante com os recursos de
hardware e software disponveis da mquina-alvo. No sentido mais amplo, as transformaes podem ser classificadas como independentes ou dependentes de mquina.
Por exemplo, a substituio de uma computao redundante por um reso do valor j
calculado normalmente mais rpida do que recalcular o valor; assim, a eliminao de
redundncia considerada independente de mquina. Ao contrrio, a implementao
deuma operao de cpia de string de caracteres com o hardware do tipo espalhar-juntar
em um processador vetorial certamente dependente de mquina. A reescrita dessa
operao de cpia com uma chamada para a rotina bcopy, do sistema, otimizada
mo, poderia ser mais amplamente aplicvel.

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

10.1 Introduo 453

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

Eliminar cdigo intil e inalcanvel. O compilador pode descobrir


que uma operao intil ou inalcanvel. Na maioria dos casos, a eliminao
dessas operaes produz cdigo menor e mais rpido.
j Movimentar cdigo. O compilador pode mover uma operao para um local
onde execute menos vezes, mas produz a mesma resposta. Na maioria dos casos,
a movimentao de cdigo reduz o tempo de execuo, e, em alguns casos,
otamanho do cdigo.

A distino entre essas categorias pode no ser clara.


Chamamos uma transformao de independente de
mquina se ela deliberadamente ignora consideraes
da mquina-alvo, como seu impacto sobre a alocao
de registradores.

454 CAPTULO 10 Otimizaes escalares

Especializar uma computao. O compilador pode usar o contexto em torno


deuma operao para especializ-la, como no exemplo anterior que reescreveu
uma multiplicao como um shift. A especializao reduz o custo das sequncias
de cdigo geral.
j Eliminar uma computao redundante. O compilador pode provar que um valor
j foi calculado e reutilizar o valor anterior. Em muitos casos, a reutilizao custa menos do que a recomputao. A numerao de valor local captura este efeito.
j Habilitar outras transformaes. O compilador pode reescrever o cdigo de
modo que exponha novas oportunidades para outras transformaes. A substituio em linha, por exemplo, cria oportunidades para muitas outras otimizaes.
Esse conjunto de categorias abrange a maior parte dos efeitos independentes de mquina
que o compilador pode resolver. Na prtica, muitas transformaes atacam efeitos em
mais de uma categoria. A numerao de valor local, por exemplo, elimina computaes redundantes, especializa computaes com valores constantes conhecidos e usa
identidades algbricas para identificar e remover alguns tipos de computaes inteis.

OTIMIZAO COMO ENGENHARIA DE SOFTWARE


Ter um otimizador separado pode simplificar o projeto e a implementao de um
compilador. O otimizador simplifica o front end; que pode gerar um cdigo de uso
geral e ignorar casos especiais. O otimizador simplifica o back end; que pode se
concentrar no mapeamento da verso em IR do programa para a mquina-alvo.
Sem um otimizador, tanto o front end quanto o back end devem se preocupar em
encontrar oportunidades para melhoria e explor-las.
Em um otimizador estruturado por passos, cada passo contm uma transformao
e a anlise exigida para lhe dar suporte. Em princpio, cada tarefa que o otimizador
realiza pode ser implementada uma vez. Isso oferece um nico ponto de controle e
permite que o construtor de compiladores implemente funes complexas apenas
uma vez, ao invs de muitas. Por exemplo, a excluso de uma operao da IR pode
ser complicada. Se a operao excluda deixar um bloco bsico vazio, exceto para o
desvio ou salto ao final do bloco, ento a transformao tambm deve excluir o bloco
e reconectar os predecessores do bloco aos seus sucessores, conforme a necessidade.
Manter essa funcionalidade em um s local simplifica a implementao, compreenso
e manuteno.
Sob o ponto de vista da engenharia de software, a estrutura de passos, com uma
separao clara de interesses, faz sentido. Isto permite que cada passo se concentre
em uma nica tarefa, e fornece uma separao clara de interesses a numerao
de valor ignora a presso sobre o registrador, e o alocador de registradores ignora as
subexpresses comuns, permitindo que o construtor de compiladores teste os passos
independente e completamente, e tambm simplifica o isolamento de falhas.

10.2 ELIMINAO DE CDIGO INTIL E INALCANVEL

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

10.2 Eliminao de cdigo intil e inalcanvel 455

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.

10.2.1 Eliminao de cdigo intil


Os algoritmos clssicos para eliminar cdigo intil operam de maneira semelhante
aos coletores de lixo do tipo marcar-varrer, com o cdigo IR servindo como dados
(ver Seo 6.6.2). Assim como os coletores marcar-varrer, eles realizam duas passagens sobre o cdigo. A primeira comea limpando todos os campos de marca
e marcando operaes crticas como teis. Uma operao crtica se definir
valores de retorno para o procedimento, se for uma instruo de entrada/sada, ou
se afetar o valor em um local de armazenamento que pode ser acessvel de fora do
procedimento atual. Alguns exemplos de operaes crticas incluem o cdigo de
prlogo e eplogo de um procedimento e as sequncias de pr-chamada e ps-retorno
nas chamadas. Em seguida, o algoritmo rastreia os operandos de operaes teis de
volta s suas definies e marca essas operaes como teis. Esse processo continua,
em um esquema iterativo simples de worklist, at que mais nenhuma operao possa
ser marcada como til. A segunda passagem percorre o cdigo e remove qualquer
operao no marcada como til.
A Figura10.1 torna essas ideias concretas. O algoritmo, chamado de Dead, considera
que o cdigo est na forma SSA, que simplifica o processo, porque cada uso refere-se
a uma nica definio. Dead consiste em duas passagens. A primeira, chamada Mark,
descobre o conjunto de operaes teis. A segunda, chamada Sweep, remove operaes
inteis. Mark conta com fronteiras de dominncia reversas, que derivam das fronteiras
de dominncia usadas na construo da SSA (ver Seo 9.3.2).
O tratamento de operaes diferentes de desvios ou saltos simples. A fase de marcao
determina se uma operao til. A fase de varrio remove operaes que no foram
marcadas como teis.
O tratamento de operaes de fluxo de controle mais complexo. Cada salto considerado til. Os desvios so considerados teis somente se a execuo de uma operao
til depender da sua presena. medida que a fase de marcao descobre operaes
teis, tambm marca os desvios apropriados como teis. Para mapear uma operao

Uma operao pode definir um valor de retorno de


vrias maneiras, incluindo atribuio a um parmetro
de chamada por referncia ou a uma varivel global,
atribuio por meio de um ponteiro ambguo, ou
passagem de um valor de retorno por meio de uma
instruo return.

456 CAPTULO 10 Otimizaes escalares

FIGURA 10.1 Eliminao de cdigo intil.

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.

A definio de dependncia de controle baseia-se na ps-dominncia. Em um CFG,


o n j ps-domina o n i se cada caminho de i at o n de sada do CFG passar por
j. Usando a ps-dominncia, podemos definir a dependncia de controle da seguinte
forma: em um CFG, o n j dependente de controle do n i se, e somente se:
1. Houver um caminho no nulo de i at j tal que j ps-domina cada n no caminho
aps i. Quando a execuo comear nesse caminho, ela deve fluir por j paraalcanar a sada do CFG (a partir da definio da ps-dominncia).
2. j no ps-domina estritamente i. Outra aresta sai de i e o controle pode fluir
ao longo de um caminho para um n que no est no caminho at j. Deve haver
um caminho comeando com essa aresta que leve sada do CFG sem passar
porj.
Em outras palavras, duas ou mais arestas saem do bloco i. Uma ou mais levam a j
e uma ou mais no. Assim, a deciso tomada no desvio de trmino do bloco i pode
determinar se j executado ou no. Se uma operao em j for til, ento o desvio que
termina i tambm til.
Essa noo de dependncia de controle capturada exatamente pela fronteira de dominncia reversa de j, indicada por RDF(j). Essas fronteiras so simplesmente fronteiras
de dominncia calculadas sobre o CFG reverso. Quando Mark marca uma operao
no bloco b como til, esta visita cada bloco na fronteira de dominncia reversa de b e

10.2 Eliminao de cdigo intil e inalcanvel 457

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.

10.2.2 Eliminao de fluxo de controle intil


A otimizao pode mudar a forma IR do programa de modo que tenha fluxo de controle
intil. Se o compilador incluir otimizaes que possam produzir fluxo de controle intil
como um efeito colateral, ento deve incluir um passo que simplifica o CFG eliminando
fluxo de controle intil. Esta seo apresenta um algoritmo simples, chamado Clean,
que trata desta tarefa.
Clean opera diretamente sobre o CFG do procedimento; ele usa quatro transformaes, mostradas na margem, que so aplicadas na seguinte ordem:
1. Desdobrar um desvio redundante. Se Clean encontra um bloco que termina
emum desvio, e os dois lados do desvio visam o mesmo bloco, ele substitui
odesvio por um salto para o bloco de destino. Essa situao surge como resultado de outras simplificaes. Por exemplo, Bi poderia ter tido dois sucessores,
cada um com um salto para Bj. Se outra transformao j tivesse esvaziado esses
blocos, ento a remoo de bloco vazio, discutida a seguir, poderia produzir
ografo inicial mostrado na margem.
2. Remover um bloco vazio. Se Clean encontrar um bloco que contm somente
um salto, pode mesclar o bloco em seu sucessor. Essa situao surge quando
outras passagens removem todas as operaes de um bloco Bi. Considere o grafo
da esquerda do par mostrado na margem. Como Bi tem apenas um sucessor, Bj,
a transformao redireciona as arestas que entram em Bi para Bj e exclui Bi do
conjunto de predecessores de Bj. Isso simplifica o grafo, e tambm deve acelerar
a execuo. No grafo original, os caminhos por meio de Bi precisavam de duas
operaes de fluxo de controle para alcanar Bj. No grafo transformado, esses
caminhos usam uma operao para o mesmo objetivo.
3. Combinar blocos. Se Clean encontrar um bloco Bi que termine em um salto
para Bj, e Bj tiver apenas um predecessor, pode combinar os dois blocos,
comomostramos na margem. Essa situao pode surgir de vrias maneiras. Outra transformao poderia eliminar outras arestas que entraram em Bj, ou Bi e Bj
poderiam ser o resultado do desdobramento de um desvio redundante (descrito
acima). De qualquer forma, os dois blocos podem ser combinados em um nico,
eliminando assim o salto ao final de Bi.
4. Elevar um desvio. Se Clean encontrar um bloco Bi que termina com um salto
para um bloco vazio Bj, e Bj terminar com um desvio, pode substituir o salto de
final de bloco em Bi por uma cpia do desvio a partir de Bj. Com efeito, isso
eleva o desvio para Bi, como mostramos na margem. Essa situao surge quando

458 CAPTULO 10 Otimizaes escalares

outras passagens eliminam as operaes em Bj, deixando um salto para um


desvio. O cdigo transformado alcana o mesmo efeito com apenas um desvio.
Isso acrescenta uma aresta ao CFG. Observe que Bi no pode ser vazio, ou ento
a remoo de bloco vazio o teria eliminado. De modo semelhante, Bi no pode
ser o nico predecessor de Bj, ou ento Clean teria combinado os dois blocos.
(Aps a elevao, Bj ainda tem pelo menos um predecessor.)
Algum cuidado necessrio para implementar essas transformaes. Algumas das
modificaes so triviais. Para desdobrar um desvio redundante em um programa representado por ILOC e um CFG grfico, Clean simplesmente sobrescreve o desvio
de final de bloco com um salto e ajusta as listas de sucessor e predecessor dos blocos.
Outras so mais difceis. Mesclar dois blocos pode envolver alocar espao para o bloco
mesclado, copiar as operaes para o novo bloco, ajustar as listas de predecessor e
sucessor do novo bloco e seus vizinhos no CFG, e descartar os dois blocos originais.
Muitos compiladores e montadores tm includo um
passo ad hoc que elimina um salto para um salto ou um
salto para um desvio. Clean consegue o mesmo
efeito de um modo sistemtico.

Clean aplica essas quatro transformaes em um padro sistemtico. Ele percorre o


grafo em ps-ordem, de modo que os sucessores de Bi so simplificados antes de Bi,
a menos que o sucessor se encontre ao longo de uma aresta de volta com relao
numerao de ps-ordem. Neste caso, Clean visitar o predecessor antes do sucessor,
o que inevitvel em um grafo cclico. Simplificar sucessores antes de predecessores
reduz o nmero de vezes que a implementao deve mover algumas arestas.
Em algumas situaes, mais de uma das transformaes poder se aplicar. A anlise
cuidadosa de vrios casos leva ordem mostrada na Figura10.2, que corresponde
quela em que eles so apresentados nesta seo. O algoritmo usa uma srie de instrues if, ao invs de um if-then-else, para lhe permitir aplicar vrias transformaes em uma nica visita a um bloco.
Se o CFG contm arestas de volta, uma passagem de Clean pode criar oportunidades
adicionais a saber, sucessores no processados ao longo das arestas de volta. Tais
arestas, por sua vez, podem criar outras oportunidades. Por esse motivo, Clean repete
a sequncia de transformaes iterativamente at que o CFG pare de mudar. Ele precisa
calcular a nova numerao de ps-ordem entre as chamadas a OnePass porque cada
passagem muda o grafo subjacente. A Figura10.2 mostra o pseudocdigo para Clean.
Clean no pode, por si s, eliminar um lao vazio. Considere o CFG mostrado na
margem. Suponha que o bloco B2 esteja vazio. Nenhuma das transformaes de Clean
pode eliminar B2, pois o desvio que termina B2 no redundante. B2 no termina com

FIGURA 10.2 Algoritmo para Clean.

10.2 Eliminao de cdigo intil e inalcanvel 459

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.

10.2.3 Eliminao de cdigo inalcanvel


s vezes, o CFG contm cdigo que inalcanvel. O compilador deve encontrar os
blocos inalcanveis e remov-los. Um bloco pode ser inalcanvel por dois motivos
distintos: pode no haver caminho pelo CFG que leve ao bloco, ou os caminhos que
alcanam o bloco podem no ser executveis por exemplo, guardados por uma
condio que sempre avaliada como falsa.
O primeiro caso fcil de tratar. O compilador pode realizar uma anlise de alcance
no estilo marcar-varrer sobre o CFG. Primeiro, ele inicializa uma marca em cada
blococom o valor inalcanvel. Em seguida, comea com a entrada e marca cada n
do CFG que ele pode alcanar como alcanvel. Se todos os desvios e saltos forem
no ambguos, ento todos os blocos no marcados podem ser excludos. Com desvios
ou saltos ambguos, o compilador precisa preservar qualquer bloco que o desvio ou
salto possa alcanar. Essa anlise simples e no dispendiosa. E pode ser feita durante
travessias do CFG para outras finalidades ou durante a prpria construo do CFG.
O tratamento do segundo caso mais difcil. Exige que o compilador raciocine a respeito dos valores de expresses que controlam desvios. A Seo10.7.1 apresenta um
algoritmo que encontra alguns blocos que so inalcanveis porque os caminhos que
levam a eles no so executveis.

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.

460 CAPTULO 10 Otimizaes escalares

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.

Dica: escreva o cdigo para acessar A[i,j] onde


A dimensionado como A[1:N,1:M].

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?

10.3 MOVIMENTAO DE CDIGO


Mover uma computao para um ponto onde ele seja executado com menos frequncia
do que em sua posio original deve reduzir a contagem total de operaes do programa
em execuo. A primeira transformao apresentada nesta seo, movimentao de
cdigo pouco ativo, usa a movimentao de cdigo para acelerar a execuo. Como
os laos tendem a ser executados muito mais vezes do que o cdigo que os cerca,
grande parte do trabalho nessa rea tem focado a movimentao de expresses que
no variam no lao para fora dos laos. A movimentao de cdigo pouco ativo
realiza a movimentao de cdigo invariante no lao; estende as noes formuladas
originalmente no problema de fluxo de dados das expresses disponveis, para incluir
operaes que so redundantes ao longo de alguns, mas no de todos os caminhos;
insere cdigo para torn-las redundantes em todos os caminhos e remove a expresso
recm-redundante.
Alguns compiladores, porm, otimizam por outros critrios. Se o compilador se preocupa com o tamanho do cdigo executvel, pode realizar a movimentao de cdigo
para reduzir o nmero de cpias de uma operao especfica. A segunda transformao
apresentada nesta seo, elevao, usa a movimentao de cdigo para reduzir a
duplicao de instrues, e descobre casos em que a insero de uma operao torna
vrias cpias da mesma operao redundantes sem mudar os valores calculados pelo
programa.

10.3.1 Movimentao de cdigo pouco ativo


A movimentao de cdigo pouco ativo (LCM Lazy Code Motion) utiliza a anlise de
fluxo de dados para descobrir tanto operaes que so candidatas movimentao

10.3 Movimentao de cdigo 461

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.

As ideias fundamentais em que a LCM se baseia foram introduzidas na Seo 9.2.4.


A LCM calcula expresses disponveis e expresses antecipveis. Em seguida, usa
os resultados dessas anlises para incluir anotaes em cada aresta i,j do CFG com
um conjunto EARLIEST(i, j) que contenha as expresses para as quais essa aresta
o posicionamento vlido mais antigo. A seguir, ela resolve um terceiro problema de
fluxo de dados para encontrar os posicionamentos mais recentes, ou seja, situaes em
que a avaliao de uma expresso aps seu posicionamento mais antigo tem o mesmo
efeito. Os posicionamentos mais recentes so desejveis porque podem encurtar os
tempos de vida dos valores definidos pelas avaliaes inseridas. Finalmente, a LCM
calcula seus produtos finais, dois conjuntos INSERT e DELETE, que guiam sua etapa
de reescrita de cdigo.

Neste contexto, mais antigo significa a posio no CFG


mais prxima do n de entrada.

FIGURA 10.3 Converso de redundncias parciais em redundncias.

Parcialmente redundante
Uma expresso e parcialmente redundante em p se ela
ocorrer em alguns, mas no em todos os caminhos que
alcanam p.

462 CAPTULO 10 Otimizaes escalares

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.

FIGURA 10.4 Exemplo de movimentao de cdigo pouco ativo.

Essas regras de nomeao permitem que o compilador facilmente separe variveis de


expresses, encurtando o domnio dos conjuntos manipulados nas equaes de fluxo
de dados. Na Figura10.4, as variveis so r2 r4 e r8, cada qual definida por uma
operao de cpia. Todos os outros nomes, r1, r3, r5, r6, r7, r20 e r21, representam
expresses. A tabela a seguir mostra a informao local para os blocos no exemplo:

10.3 Movimentao de cdigo 463

DEEXPR(b) o conjunto de expresses expostas para baixo (downward-exposed) no


bloco b; UEEXPR(b) o conjunto de expresses expostas para cima (upward-exposed)
em b; e EXPRKILL(b) o conjunto de expresses mortas por alguma operao em b.
Vamos considerar, para simplificar, que os conjuntos para B3 esto todos vazios.

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:

e depois avalia iterativamente a seguinte equao, at alcanar um ponto fixo:

para o exemplo na Figura10.4, esse processo produz os seguintes conjuntos:

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:

Em seguida, calcula iterativamente conjuntos ANTIN e ANTOUT para cada bloco at


que o processo alcance um ponto fixo.

464 CAPTULO 10 Otimizaes escalares

Para o exemplo, esse processo produz os seguintes conjuntos:

ANTOUT fornece informaes sobre a segurana da elevao de uma avaliao para o


incio ou para o final do bloco atual. Se x ANTOUT(b), o compilador pode colocar
uma avaliao de x ao final de b, com duas garantias. Primeiro, a avaliao ao final
de b produzir o mesmo valor que a prxima avaliao de x ao longo de qualquer
caminho de execuo no procedimento. Segundo, ao longo de qualquer caminho de
execuo saindo de b, o programa avaliar x antes de redefinir qualquer um de seus
argumentos.

Posicionamento mais cedo


Dadas as solues para disponibilidade e antecipao, o compilador pode determinar,
para cada expresso, o ponto mais cedo no programa em que ele pode avaliar a expresso. Para simplificar as equaes, a LCM considera que colocar a avaliao em
uma aresta do CFG, ao invs de no incio ou final de um bloco especfico. O clculo
do posicionamento de aresta permite que o compilador adie a deciso de colocar a
avaliao no final da origem da aresta, no incio de seu destino ou em um novo bloco
no meio da aresta. (Veja a discusso das arestas crticas na Seo 9.3.5.)
Para uma aresta i, j do CFG, uma expresso e est em EARLIEST(i,j) se, e somente
se, o compilador puder legalmente mover e para i, j, e no puder mov-la para
qualquer aresta anterior no CFG. A equao EARLIEST codifica esta condio como
a interseo de trs termos:

Esses termos definem um posicionamento mais cedo para e da seguinte forma:


1. e Antin(j) significa que o compilador pode seguramente mover e para oincio
de j. As equaes de antecipao garantem que e produzir o mesmo valor de
sua prxima avaliao em qualquer caminho que sai de j, e que cada umdesses
caminhos avalia e.
2. e AvailOut (i) mostra que nenhuma computao anterior de e est disponvel na sada de i. Se e Availout(i), a insero de e em i, j seria
redundante.
3. A terceira condio codifica dois casos. Se e ExprKill(i), o compilador no
pode mover e por meio do bloco i, devido a uma definio em i. Se e
AntOut(i), o compilador no pode mover e para i porque e AntIn(k)
paraalguma aresta i,k. Se um destes for verdadeiro, ento e no pode ser
movido para alm de i, j.

10.3 Movimentao de cdigo 465

O n de entrada do CFG, n0, apresenta um caso especial. A LCM no pode movimentar


uma expresso para antes de n0, de modo que pode ignorar o terceiro termo na equao
para EARLIEST (n0,k), para qualquer k. Os conjuntos EARLIEST para o exemplo em
andamento so os seguintes:

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:

Em seguida, calcula iterativamente conjuntos LATERIN e LATER para cada bloco.


A computao termina quando alcana um ponto fixo.

Assim como na disponibilidade e na antecipao, essas equaes tm uma nica


soluo de ponto fixo.
Uma expresso e LATERIN(k) se, e somente se, cada caminho que alcana k incluir
uma aresta p,q tal que e EARLIEST(p,q) e o caminho de q a k no redefine os
operandos de e nem contm uma avaliao de e que um posicionamento anterior de
e antecipasse. O termo EARLIEST na equao para LATER garante que LATER(i,j)
inclui EARLIEST(i, j). O restante dessa equao coloca e em LATER(i, j) se e puder
ser movido para a frente de i (e LATERIN (i)) e um posicionamento na entrada de
i no antecipar um uso em i (e UEEXPR(i)).
Dados os conjuntos LATER e LATERIN, e LATERIN(i) implica que o compilador pode mover a avaliao de e para a frente por meio de i sem perder qualquer
benefcio ou seja, no existe avaliao de e em i que uma avaliao anterior
anteciparia, e e LATER (i, j) implica que o compilador pode mover uma avaliao
de e em i para j.
Para o exemplo em andamento, essas equaes produzem os seguintes conjuntos:

466 CAPTULO 10 Otimizaes escalares

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.

Se i tem apenas um sucessor, a LCM pode inserir as computaes no final de i. Se j tem


apenas um predecessor, pode inseri-las na entrada de j. Se nenhuma dessas condies
se aplica, a aresta i, j uma aresta crtica e o compilador deve dividi-la inserindo um
bloco no meio da aresta para avaliar as expresses em INSERT(i,j).
O conjunto DELETE especifica, para um bloco, quais computaes a LCM deve
excluir do bloco.

DELETE(n0) vazio, naturalmente, pois nenhum bloco precede n0. Se e DELETE(i),


ento a primeira computao de e em i redundante aps todas as inseres terem
sido feitas. Qualquer avaliao subsequente de e em i que tenha os usos expostos para
cima ou seja, os operandos no forem definidos entre o incio de i e a avaliao
tambm pode ser excluda. Como todas as avaliaes de e definem o mesmo nome,
o compilador no precisa reescrever referncias subsequentes avaliao excluda.
Elas simplesmente se referiro a avaliaes anteriores de e que a LCM tenha provado
produzir o mesmo resultado.
Para o nosso exemplo, os conjuntos INSERT e DELETE so simples.

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.

10.3.2 Elevao de cdigo


As tcnicas de movimentao de cdigo tambm podem ser usadas para reduzir o
tamanho do cdigo compilado. Uma transformao chamada elevao de cdigo

10.3 Movimentao de cdigo 467

FIGURA 10.5 Exemplo aps movimentao de cdigo pouco ativo.

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.

468 CAPTULO 10 Otimizaes escalares

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.

A implementao comum do abaixamento chamada cross jumping.

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

10.4 Especializao 469

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.

10.4.1 Otimizao de chamada de cauda


Quando a ltima ao que um procedimento realiza uma chamada, referimo-nos a esta
como uma chamada de cauda (tail-call). O compilador pode especializar as chamadas
decauda aos seus contextos de forma que eliminem grande parte do overhead da ligao de
procedimento. Para entender como surge essa oportunidade de melhoria, considere o
que acontece quando o chama p e p chama q. Quando q retorna, executa sua sequncia
de eplogo e salta de volta para a sequncia de ps-retorno de p. A execuo continua
em p at que p retorna, ponto em que p executa sua sequncia de eplogo e salta para
a sequncia de ps-retorno de o.
Se a chamada de p para q for uma chamada de cauda, ento nenhuma computao til
ocorre entre a sequncia de ps-retorno e a sequncia de eplogo em p. Assim, qualquer
cdigo que preserve e restaure o estado de p, alm do que necessrio para o retorno
de p para o, intil. Uma ligao padro, conforme descrita na Seo 6.5, gasta muito de
seu esforo para preservar o estado que intil no contexto de uma chamada de cauda.
Na chamada de p para q, a sequncia mnima de pr-chamada deve avaliar os parmetros
reais na chamada de p para q e ajustar os links de acesso ou o display, se necessrio. Ela
no precisa preservar quaisquer registradores de salvamento do procedimento chamador,
pois eles no podem estar vivos, nem alocar um novo AR, pois q pode usar o AR de p.
Deve deixar intacto o contexto criado para um retorno para o, a saber, o endereo de retorno e o ARP do chamador que o passou para p e quaisquer registradores de salvamento
do procedimento chamado que p preservou escrevendo-os no AR. (Esse contexto far
com que o cdigo de eplogo para q retorne o controle diretamente para o.) Finalmente,
a sequncia de pr-chamada deve saltar para uma sequncia de prlogo adaptada para q.
Nesse esquema, q deve executar uma sequncia de prlogo personalizada para combinar
com a sequncia de pr-chamada mnima em p. Ele s salva aquelas partes do estado
de p que permitem um retorno para o. A sequncia de pr-chamada no preserva os
registradores de salvamentos do procedimento chamado por dois motivos. Primeiro,
os valores de p nesses registradores no esto mais vivos. Segundo, os valores que p
deixou na rea de salvamento de registradores do AR so necessrios para o retorno
para o. Assim, a sequncia de prlogo em q deve inicializar as variveis locais e os
valores que q precisa, e, ento, desviar para o cdigo de q.
Com essas mudanas na sequncia de pr-chamada em p e a sequncia de prlogo em
q, a chamada de cauda evita preservar e restaurar o estado de p e elimina grande parte
do overhead da chamada. Naturalmente, uma vez que a sequncia de pr-chamada
em p tiver sido adaptada desta forma, as sequncias de ps-retorno e eplogo so
inalcanveis. As tcnicas padro como Dead e Clean no descobriro este fato, pois
assumem que os saltos interprocedimentais aos seus rtulos so executveis. Quando
o otimizador adaptar a chamada, ele poder eliminar essas sequncias mortas.
Com um pouco de cuidado, o otimizador pode planejar para que as operaes no
prlogo adaptado para q apaream como ltimas operaes em seu prlogo mais geral.
Nesse esquema, a chamada de cauda de p para q simplesmente salta para um ponto mais
adiante na sequncia de prlogo do que uma chamada normal de alguma outra rotina.
Se a chamada de cauda for do tipo autorrecursiva ou seja, p e q so o mesmo
procedimento , ento a otimizao de chamada de cauda pode produzir um cdigo
particularmente eficiente. Em uma recurso de cauda, a sequncia inteira de pr-

470 CAPTULO 10 Otimizaes escalares

-chamada devolvida para avaliao do argumento e um desvio de volta ao topo da


rotina. Um retorno eventual da recurso exige um desvio, ao invs de um desvio por
invocao recursiva. O cdigo resultante compete com um lao tradicional em termos
de eficincia.

10.4.2 Otimizao de chamada de folha


Parte do overhead envolvido em uma chamada de procedimento surge da necessidade de preparar para chamadas que o procedimento chamado poderia fazer. Um
procedimento que no faz chamadas, chamado procedimento folha, cria oportunidades
para especializao. O compilador pode facilmente reconhecer a oportunidade; o
procedimento no chama outros procedimentos.
O outro motivo para armazenar o endereo de retorno
permitir que um depurador ou um monitor de
desempenho retroceda a pilha de chamada. Quando
essas ferramentas esto em uso, o compilador deve
deixar a operao de salvamento intacta.

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.

10.4.3 Promoo de parmetros


Referncias de memria ambguas impedem o compilador de manter valores em registradores. s vezes, o compilador pode provar que um valor ambguo tem apenas um
local de memria correspondente por meio da anlise detalhada de valores de ponteiro
ou valores de subscrito de array, ou pela anlise de caso especial. Nesses casos, pode
reescrever o cdigo para mover esse valor para uma varivel escalar local, onde o

10.4 Especializao 471

alocador de registradores pode mant-la em um registrador. Esse tipo de transformao


normalmente chamado promoo. A anlise para promover referncias de array ou
referncias baseadas em ponteiro est fora do escopo deste livro. Porm, um caso mais
simples pode ilustrar essas transformaes igualmente bem.
Considere o cdigo gerado para um parmetro ambguo de chamada por referncia. Esses
parmetros podem surgir de vrias maneiras. O cdigo poderia passar o mesmo parmetro real em dois slots de parmetros distintos, ou passar uma varivel global como um
parmetro real. A menos que o compilador realize anlise interprocedimental para rejeitar
essas possibilidades, deve tratar todos os parmetros de referncia como potencialmente
ambguos. Assim, cada uso do parmetro exige um load, e cada definio um store.
Se o compilador puder provar que o parmetro real deve ser no ambguo no procedimento chamado, pode promover o valor do parmetro para um valor escalar local, o que
permite que o procedimento chamado o mantenha em um registrador. Se o parmetro
real no for modificado pelo procedimento chamado, o parmetro promovido pode ser
passado por valor. Se o procedimento chamado modificar o parmetro real e o resultado
estiver vivo no chamador, ento o compilador deve usar a semntica de valor-resultado
para passar o parmetro promovido (ver Seo 6.4.1).
Para aplicar essa transformao a um procedimento p, o otimizador deve identificar
todos os locais de chamada que possam invocar p. Ele pode, ou provar que a transformao se aplica a todos esses locais de chamada, ou clonar p para criar uma cpia
que trate dos valores promovidos (ver Seo10.6.2). A promoo de parmetros mais
atraente em uma linguagem que usa a vinculao de chamada por referncia.

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.

472 CAPTULO 10 Otimizaes escalares

10.5 ELIMINAO DE REDUNDNCIA


Um clculo x+y redundante em algum ponto p no cdigo se, ao longo de cada caminho que alcana p, x+y j tiver sido avaliado e x e y no tiverem sido modificados
desde a avaliao. Os clculos redundantes normalmente surgem como artefatos da
traduo ou da otimizao.
J apresentamos trs tcnicas eficazes para eliminao de redundncia: numerao de
valor local (LVN) na Seo 8.4.1, numerao de valor superlocal (SVN) na 8.5.1 e
movimentao de cdigo pouco ativo (LCM) na Seo 10.3.1. Esses algoritmos cobrem
uma faixa que vai desde simples e rpido (LVN) at complexo e abrangente (LCM).
Embora todos as trs tcnicas sejam diferentes no escopo que cobrem, a principal
distino entre eles est no mtodo que usam para estabelecer que dois valores so
idnticos. A prxima seo explora essa questo com detalhes. A segunda seo
apresenta mais uma verso da numerao de valor, uma tcnica baseada em dominador.

10.5.1 Identidade de valor versus identidade de nome


A LVN introduziu um mecanismo simples para provar que duas expresses tinham
o mesmo valor. Ela baseia-se em dois princpios: atribui a cada valor um nmero
de identificao exclusivo seu nmero de valor, e considera que duas expresses
produzem o mesmo valor se eles tiverem o mesmo operador e seus operandos os
mesmos nmeros de valor. Essas regras simples permitem que a LVN encontre uma
grande classe de operaes redundantes qualquer operao que produza um nmero
de valor preexistente redundante.
Com essas regras, a LVN pode provar que 2+a tem o mesmo valor que a+2 ou que
2+b quando a e b tm o mesmo nmero de valor. Mas no pode provar que a+a
e 2a tm o mesmo valor, pois possuem operadores diferentes. De modo semelhante,
ela no pode provar que a+0 e a tm o mesmo valor. Assim, estendemos a LVN com
identidades algbricas que podem lidar com os casos bem definidos no cobertos pela regra original. A tabela na Figura8.3 mostra a faixa de identidades que a LVN pode tratar.
Ao contrrio, a LCM baseia-se em nomes para provar que dois valores tm o mesmo
nmero. Se encontrar a+b e a+c, considera que tm valores diferentes porque b e c
tm nomes diferentes. E baseia-se, tambm, em uma comparao lxica identidade
de nome. As anlises de fluxo de dados subjacentes no podem acomodar diretamente
a noo de identidade de valor; os problemas de fluxo de dados operam em um espao
de nomes predefinidos e propagam fatos sobre esses nomes pelo CFG. O tipo das
comparaes ad hoc usadas na LVN no se ajusta ao framework de fluxo de dados.
Conforme descrevemos na Seo 10.6.4, um modo de melhorar a eficcia da LCM
codificar a identidade de valor no espao de nomes do cdigo antes de aplic-la. LCM
reconhece redundncias que nem a LVN nem a SVN podem encontrar. Em particular,
encontra redundncias que se encontram nos caminhos por meio de pontos de juno
no CFG, incluindo aqueles que fluem ao longo de desvios de fechamento de lao, e,
encontra ainda, redundncias parciais. Por outro lado, tanto LVN quanto SVN encontram redundncias e simplificaes baseadas em valor que a LCM no consegue.
Assim, codificar a identidade de valor no espao de nomes permite que o compilador
tire proveito dos pontos fortes de ambas as tcnicas.

10.5.2 Numerao de valor baseada em dominador


O Captulo8 apresentou a numerao de valor local (LVN) e sua extenso para blocos
bsicos estendidos (EBBs), chamada numerao de valor superlocal (SVN). Embora
a SVN descubra mais redundncias do que a LVN, ela ainda perde algumas opor-

10.5 Eliminao de redundncia 473

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.

474 CAPTULO 10 Otimizaes escalares

FIGURA 10.6 Tcnica de numerao de valor baseada em 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

Lembre-se, pela construo de SSA, que nomes no


inicializados no so permitidos.

A DVNT percorre as atribuies em B e as processa de maneira semelhante LVN e


SVN. Uma sutileza aparece com o uso de nomes SSA como nmeros de valor. Quando
o algoritmo encontra uma instruo x y op z, pode simplesmente substituir y por
VN[y], pois o nome em VN[y] mantm o mesmo valor de y.

10.6 Habilitando outras transformaes 475

Propagar as informaes aos sucessores de B


Quando a DVNT tiver processado todas as funes- e atribuies em B, visita cada um
dos sucessores CFG s de B e atualiza os argumentos da funo- que correspondem a
valores fluindo pela aresta (B, s); registra o nmero do valor atual para o argumento na
funo- sobrescrevendo o nome SSA do argumento (observe a semelhana entre essa
etapa e a correspondente na fase de renomeao da construo de SSA); em seguida, o
algoritmo recorre sobre os filhos de B na rvore de dominadores, e finalmente desaloca
o escopo da tabela hash que usada para B.
Esse esquema de recurso faz com que a DVNT siga um percurso em pr-ordem na
rvore de dominadores, que garante que as tabelas apropriadas foram construdas
antes de visitar um bloco. Essa ordem pode produzir uma travessia contraintuitiva;
para o CFG na margem, o algoritmo poderia visitar B4 antes de B2 ou B3. Como os
nicos fatos que o algoritmo pode usar em B4 so aqueles descobertos processando B0
e B1, a ordenao relativa de B2, B3 e B4 no apenas no especificada, mas tambm
irrelevante.

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 HABILITANDO OUTRAS TRANSFORMAES


Normalmente, um otimizador inclui passos cuja principal finalidade criar ou expor
oportunidades para outras transformaes. Em alguns casos, uma transformao muda a
forma do cdigo para torn-lo mais receptivo otimizao. Em outros, ela cria um ponto
no cdigo onde condies especficas so mantidas para tornar outra transformao

476 CAPTULO 10 Otimizaes escalares

segura. Criando diretamente a forma de cdigo necessria, essas transformaes


habilitadoras reduzem a sensibilidade do otimizador forma do cdigo de entrada.
Vrias transformaes habilitadoras so descritas em outras partes do livro. O desenrolamento de lao (Seo 8.5.2) e a substituio em linha (Seo 8.7.1) obtm a maior
parte dos seus benefcios pela criao de contexto para outra otimizao. (Em cada
caso, a transformao elimina algum overhead, mas o efeito maior vem da aplicao
subsequente de outras otimizaes.) O algoritmo de balanceamento de altura de rvore
(Seo 8.4.2) no elimina quaisquer operaes, mas cria uma forma de cdigo que
pode produzir melhores resultados com o escalonamento de instrues. Esta seo
apresenta quatro transformaes habilitadoras: clonagem de superbloco, clonagem de
procedimento, remoo de condicionais de lao (loop unswitching) e renomeao.

10.6.1 Clonagem de superbloco


Geralmente, a capacidade do otimizador de transformar o cdigo limitada pela
informao especfica de caminho no cdigo. Imagine usar a SVN no CFG mostrado
na margem. O fato de que os blocos B3 e B7 tm vrios predecessores pode limitar
a capacidade do otimizador de melhorar o cdigo nesses blocos. Se, por exemplo, o
bloco B6 atribusse a x o valor 7 e o bloco B8 atribusse a x o valor 13, um uso de x em
B7 pareceria receber o valor , embora o valor seja conhecido e previsvel ao longo
de cada caminho levando a B7.
Nessas circunstncias, o compilador pode clonar blocos para criar cdigo que seja
mais adequado para a transformao. Neste caso, ele poderia criar duas cpias de B7,
digamos, B7a e B7b, e redirecionar as arestas que chegam em B7 como B6, B7a e
B8, B7b. Com essa mudana, o otimizador poderia propagar o valor 7 para x em B7a
e o valor 13 para x em B7b.
Como um benefcio adicional, como B7a e B7b possuem predecessores exclusivos, o
compilador pode realmente mesclar os blocos para criar um nico de B6 e B7a e outro de
B8 e B7b. Essa transformao elimina o salto de final de bloco em B6 e B8 e, potencialmente, permite melhorias adicionais na otimizao e no escalonamento de instrues.

Desvio para trs


Aresta do CFG cujo destino tem um nmero de
profundidade menor do que sua origem, com relao a
alguma travessia em profundidade do CFG.

Um problema neste tipo de clonagem : quando o compilador deve parar de clonar?


Uma tcnica, chamada clonagem de superbloco, muito usada para criar contexto
adicional para o escalonamento de instrues dentro de laos. Na clonagem de superbloco, o otimizador comea com um cabealho de lao a entrada em um lao e
clona cada caminho at alcanar um desvio para trs.
A aplicao desta tcnica ao CFG de exemplo produz o CFG modificado mostrado
na margem. B1 o cabealho de lao. Cada um dos ns no corpo do lao tem um
predecessor exclusivo. Se o compilador aplicar uma otimizao superlocal (baseada
em blocos bsicos estendidos), cada caminho que ele encontrar abranger uma nica
iterao do corpo do lao. (Para encontrar caminhos maiores, o otimizador precisaria
desenrolar o lao de modo que a clonagem de superbloco abrangesse vrias iteraes.)
A clonagem de superbloco pode melhorar os resultados da otimizao de trs principais
formas:
1. Cria blocos maiores. Blocos maiores permitem que a otimizao local trate
de mais contexto. No caso de numerao de valor, as verses superlocal e de
dominador so to fortes quanto a verso local. Para algumas tcnicas, porm,
este no o caso. Para o escalonamento de instrues, por exemplo, as verses
superlocal e de dominador so mais fracas do que o mtodo local. Neste caso,
aclonagem, seguida pela otimizao local, pode produzir um cdigo melhor.

10.6 Habilitando outras transformaes 477

2. Elimina desvios. A combinao de dois blocos elimina um desvio entre eles.


Os desvios exigem tempo para sua execuo, e tambm interrompem alguns
dos mecanismos crticos ao desempenho no processador, como a busca de instrues e muitas das funes de pipeline. O efeito final da remoo de desvios
reduzir o tempo de execuo, eliminando operaes e tornando mais eficazes
os mecanismos de hardware para previso de comportamento.
3. Cria pontos onde a otimizao possa ocorrer. Quando a clonagem elimina
umponto de juno do fluxo de controle, cria novos pontos no programa onde
ocompilador pode obter um conhecimento mais preciso sobre o contexto
emtempo de execuo. O cdigo transformado pode apresentar oportunidades
para especializao e eliminao de redundncia que no existem em outro lugar
no cdigo original.
Naturalmente, a clonagem tambm tem custos, porque cria vrias cpias de operaes
individuais, o que leva a um cdigo maior. O cdigo maior pode ser executado mais
rapidamente, pois evita alguns saltos de final de bloco. Ele pode executar mais lentamente se o tamanho causar falhas adicionais na cache de instruo. Em aplicaes nas
quais o usurio se importa mais com o espao do cdigo do que com a velocidade em
tempo de execuo, a clonagem de superbloco pode ser contraprodutiva.

10.6.2 Clonagem de procedimento


A substituio em linha, descrita na Seo 8.7.1, tem efeitos semelhantes clonagem
de superbloco. Para uma chamada de p para q, ela cria uma cpia nica de q e a mescla com o local de chamada em p. Os mesmos efeitos que surgem com a clonagem
de superbloco aparecem com a substituio em linha, incluindo a especializao a
um contexto em particular, eliminao de algumas operaes de fluxo de controle e
tamanho de cdigo aumentado.
Em alguns casos, o compilador pode alcanar alguns dos benefcios da substituio em
linha com menos aumento de cdigo clonando o procedimento. A ideia semelhante
clonagem de bloco que ocorre na clonagem de superbloco. O compilador cria vrias
cpias do procedimento chamado e atribui algumas das chamadas a exemplares do clone.
A atribuio cuidadosa de chamadas aos clones pode criar situaes em que cada
chamada tem um contexto semelhante para otimizao. Considere, por exemplo, o
grafo de chamada simples mostrado na margem. Suponha que P3 seja uma rotina
de biblioteca cujo comportamento dependa fortemente de um de seus parmetros de
entrada; para um valor de um, o compilador pode gerar cdigo que fornece acesso
eficiente memria, enquanto, para outros valores, produz um cdigo muito maior
e mais lento. Alm do mais, suponha que tanto P0 quanto P1 lhe passem o valor 1,
enquanto P2 lhe passa o valor 17.
A propagao de constante pelo grafo de chamada no ajuda aqui porque ela precisa calcular o parmetro como 1 1 17=. Apenas com esta propagao, o
compilador ainda deve gerar o cdigo totalmente genrico para P3. A clonagem de
procedimento pode criar um local onde o parmetro sempre 1; P3a no grafo na
margem. A chamada que inibe a otimizao, (P2,P3) no grafo de chamada original,
atribuda a P3b. O compilador pode gerar cdigo otimizado para P3a e o cdigo
geral para P3b.

10.6.3 Remoo de condicionais de lao (loop unswitching)


O loop unswitching retira as operaes de fluxo de controle invariantes de lao para
fora de um lao. Se o predicado em uma construo if-then-else for invariante

478 CAPTULO 10 Otimizaes escalares

de lao, ento o compilador pode reescrever o lao puxando o if-then-else para


fora do lao e gerando uma cpia adaptada do lao dentro de cada metade do novo if-then-else. A Figura10.7 mostra essa transformao para um lao curto.

FIGURA 10.7 Loop unswitching de um lao curto.

A remoo de condicionais de lao uma transformao habilitadora; permite que


o compilador adapte corpos de lao que de outra forma so difceis de se conseguir.
Aps a remoo de condicionais, os laos restantes contm menos fluxo de controle.
Executam menos desvios e outras operaes para dar suporte a esses desvios. Issopode
levar a um escalonamento melhor, melhor alocao de registradores e execuo mais
rpida. Se o lao original tivesse cdigo invariante de lao que estivesse dentro do
if-then-else, ento a LCM no poderia mov-lo para fora do lao. Aps o loop
unswitching, a LCM facilmente localiza e remove tais redundncias.
A remoo de condicionais de lao tambm tem um efeito simples e direto, que
pode melhorar um programa: move a lgica de desvio que controla o condicional invariante de lao para fora do lao. difcil mover fluxo de controle para fora de laos.
Tcnicas baseadas em anlise de fluxo de dados, como LCM, tm dificuldade para
mover essasconstrues porque a transformao modifica o CFG em que a anlise
se baseia. As tcnicas baseadas em numerao de valor podem reconhecer casos em
que os predicados controlando as construes if-then-else so idnticos, mas
normalmente no podem remover a construo de um lao.

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

10.6 Habilitando outras transformaes 479

identidade de valor na identidade lxica, o compilador expe mais redundncia


LCM e a torna mais eficaz.
De modo semelhante, os nomes importam para o escalonamento de instrues.
Em um escalonador, os nomes codificam as dependncias de dados que restringem
o posicionamento de operaes no cdigo escalonado. Quando a reutilizao de
um nome reflete o fluxo real de valores, ela fornece informaes crticas exigidas
para a exatido. Se a reutilizao de um nome ocorre porque um passo anterior
comprimiu o espao de nomes, ento a reutilizao pode restringir desnecessariamente o escalonamento. Por exemplo, o alocador de registradores coloca valores
distintos no mesmo registrador fsico para melhorar a utilizao do registrador.
Se o compilador realizar alocao antes do escalonamento, o alocador poder introduzir restries aparentes sobre o escalonador que no so exigidas pelo cdigo
original.
A renomeao uma questo sutil. Transformaes individuais podem se beneficiar
dos espaos de nomes com propriedades diferentes. Os construtores de compilador
h muito tm reconhecido que operaes de movimentao e reescrita podem
melhorar os programas. Da mesma maneira, devem reconhecer que a renomeao
pode melhorar a eficcia do otimizador. Como a SSA mostrou, o compilador no
precisa estar limitado pelo espao de nomes introduzido pelo programador ou
pelo front end do compilador. A renomeao um terreno frtil para trabalho
futuro.

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?

A iluso de uma restrio introduzida


pela nomeao normalmente chamada
compartilhamento falso.

480 CAPTULO 10 Otimizaes escalares

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.

10.7 TPICOS AVANADOS


A maioria dos exemplos neste captulo foi escolhida para ilustrar um efeito especfico
que o compilador pode usar para acelerar o cdigo executvel. s vezes, a realizao
de duas otimizaes juntas pode produzir resultados que no seriam obtidos com
qualquer combinao de suas aplicaes em separado. A prxima subseo mostra
um exemplo deste tipo: combinao de propagao de constante com eliminao de
cdigo inalcanvel. A Seo10.7.2 apresenta um segundo exemplo mais complexo
deespecializao: reduo de fora de operador com substituio de teste de funo
linear. O algoritmo que apresentamos, OSR, mais simples do que os anteriores, pois
conta com as propriedades da forma SSA. Finalmente, a Seo10.7.3 discute algumas
das questes que surgem na escolha de uma ordem de aplicao especfica para o
conjunto de transformaes do otimizador.

10.7.1 Combinao de otimizaes


s vezes, a reformulao de duas otimizaes distintas em um framework unificado e
sua soluo conjunta pode produzir resultados que no podem ser obtidos por qualquer
combinao das otimizaes executadas separadamente. Como um exemplo, considere
o algoritmo de propagao de constante simples esparsa (SSCP) descrito na Seo
9.3.6. Ele atribui um valor de reticulado ao resultado de cada operao na forma SSA
do programa. Quando ele termina, ter marcado cada definio com um valor de
reticulado que pode ser , ou uma constante. Uma definio s pode ter o valor
se contar com uma varivel no inicializada ou se ocorrer em um bloco no alcanvel.
A SSCP atribui um valor de reticulado ao operando usado por um desvio condicional.
Se o valor for , ento qualquer alvo de desvio alcanvel. Se o valor no for
nem , ento o operando deve ter um valor conhecido, e o compilador pode reescrever
o desvio com um salto para um de seus dois alvos, simplificando o CFG. Como isto
remove uma aresta do CFG, pode tornar inalcanvel o bloco que era o alvo do desvio.
A propagao de constante pode ignorar quaisquer efeitos de um bloco inalcanvel. A
SSCP no tem um mecanismo para tirar proveito deste conhecimento.
Podemos estender o algoritmo SSCP para aproveitar essas observaes. O algoritmo
resultante, chamado propagao de constante condicional esparsa (SCCP Sparse
Conditional Constant Propagation), aparece nas Figuras10.8,10.9 e10.10.

10.7 Tpicos avanados 481

FIGURA 10.8 Propagao de constante condicional esparsa.

FIGURA 10.9 Avaliao de atribuies e condicionais.

482 CAPTULO 10 Otimizaes escalares

FIGURA 10.10 Avaliao de funes-.

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),

10.7 Tpicos avanados 483

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.

Sutilezas na avaliao e na reescrita de operaes


Algumas questes sutis aparecem na modelagem de operaes individuais. Por exemplo, se o algoritmo encontrar uma operao de multiplicao com operandos e ,
pode concluir que a operao produz . Porm, esta uma deciso prematura. A
anlise subsequente poderia reduzir constante 0, de modo que a multiplicao
produz um valor 0. Se a SCCP usar a regra x , introduzir o potencial para o
comportamento no monotnico o valor da multiplicao poderia seguir a sequncia
, , 0, que aumentaria o tempo de execuo da SCCP. Igualmente importante, isto
poderia incorretamente levar outros valores a e fazer com que a SCCP perdesse
oportunidades para melhoria.
Para resolver isto, a SCCP deve usar trs regras para multiplicaes que envolvam da
seguinte forma: x , ax para a e a 0, e 0x 0. Este mesmo
efeito ocorre para qualquer operao para a qual o valor de um argumento pode determinar
completamente o resultado. Outros exemplos incluem um shift por mais do que o tamanho
da palavra, um AND lgico com zero e um OR lgico com todos os bits iguais a um.
Algumas reescritas tm consequncias imprevistas. Por exemplo, substituir 4s,
para um s no negativo, por um shift substitui uma operao comutativa por uma
no comutativa. Se o compilador mais tarde tentar rearrumar as expresses usando a
comutatividade, essa reescrita antecipada impede uma oportunidade. Este tipo de interao pode ter efeitos observveis sobre a qualidade do cdigo. Para decidir quando

Nesta discusso, um bloco alcanvel se, e somente


se, alguma aresta do CFG que entra neste bloco for
marcada como executvel.

484 CAPTULO 10 Otimizaes escalares

o compilador deve converter 4s em um shift, o construtor de compiladores deve


considerar a ordem em que as otimizaes sero aplicadas.

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.

10.7.2 Reduo de fora


A reduo de fora de operador uma transformao que substitui uma srie repetida de
operaes dispendiosas (fortes) por outra de operaes pouco dispendiosas (fracas),
que calculam os mesmos valores. O exemplo clssico substitui multiplicaes de inteiros baseadas em um ndice de lao por adies equivalentes. Este caso em particular
surge rotineiramente com a expanso de endereos de array e de estrutura em laos. A
Figura10.11a mostra a ILOC que poderia ser gerada para o lao a seguir:

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.

10.7 Tpicos avanados 485

Os valores em r1, r2 e r3 existem unicamente para calcular o endereo para a operao


load. Se o programa calculasse cada valor de r3 a partir do anterior, poderia eliminar
as operaes que definem r1 e r2. Naturalmente, r3 ento precisaria de uma inicializao e uma atualizao, o que o tornaria um nome no local, de modo que tambm
precisaria de uma funo- em l1 e l2.
A Figura10.11b mostra o cdigo aps a reduo de fora, substituio do teste de
funo linear e eliminao de cdigo morto. Ele calcula aqueles valores que antes estavam em r3 diretamente em rt7 e usa rt7 na operao load. O teste de fim de lao,
que usava r1 no cdigo original, foi modificado para usar rt8. Isto torna as computaes
de r1, r2, r3, ri0, ri1 e ri2 todas mortas. Elas foram removidas para produzir o cdigo
final. Agora, o lao contm apenas cinco operaes, ignorando funes-, enquantoo
cdigo original continha oito. (Na traduo da forma SSA de volta para cdigo executvel, as funes- tornam-se operaes de cpia que o alocador de registradores
normalmente pode remover.)

FIGURA 10.11 Exemplo de reduo de fora.

Se a operao multI for mais dispendiosa do que um addI, as economias sero


maiores. Historicamente, o alto custo da multiplicao justificou a reduo de fora.
Porm, mesmo que a multiplicao e a adio tenham custos iguais, a forma de fora
reduzida do lao pode ser preferida, pois cria uma forma de cdigo melhor para transformaes posteriores e para gerao de cdigo. Em particular, se a mquina alvo tem
um modo de endereamento de autoincremento, ento a operao addI no lao pode
ser desdobrada para a operao de memria. Esta opo simplesmente no existe para
a multiplicao original.
O restante desta seo apresenta um algoritmo simples para reduo de fora, que
chamamos OSR, seguido por um esquema para substituio de teste de funo
linear que desloca os testes de fim de lao para longe de variveis que de outra
forma estariam mortas. OSR opera sobre a forma SSA do cdigo, considerada
como um grafo. A Figura10.12 mostra o cdigo para o nosso exemplo, junto com
seu grafo SSA.

486 CAPTULO 10 Otimizaes escalares

FIGURA 10.12 Relacionando SSA em ILOC com o grafo SSA.

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

10.7 Tpicos avanados 487

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

488 CAPTULO 10 Otimizaes escalares

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.)

FIGURA 10.13 Algoritmo de reduo de fora de operador.

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.

10.7 Tpicos avanados 489

Replace reescreve o cdigo, conforme descrito na prxima seo. Se o SCC contm


vrios ns, Process passa o SCC para ClassifyIV para determinar se ele ou
no uma varivel de induo.
ClassifyIV examina cada n no SCC para verific-lo contra o conjunto de atualizaes vlidas para uma varivel de induo. Se todas as atualizaes forem vlidas, o
SCC uma varivel de induo, e Process define o campo de cabealho de cada n
para conter o n no SCC com o menor nmero de ps-ordem reversa. Se o SCC no
for uma varivel de induo, ClassifyIV visita novamente cada n no SCC para
test-lo como uma operao candidata, passando-o para Replace ou definindo seu
cabealho para mostrar que no uma varivel de induo.

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.

FIGURA 10.14 Algoritmo para a etapa de reescrita.

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.

Quando Process identifica n como uma


operao candidata, encontra tanto a varivel de
induo, iv, quanto a constante de regio, rc.

490 CAPTULO 10 Otimizaes escalares

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.

10.7 Tpicos avanados 491

FIGURA 10.15 Grafo SSA transformado para o exemplo.

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.

Substituio de teste de funo linear


A reduo de fora constantemente elimina todos os usos de uma varivel de induo,
exceto para um teste de fim de lao. Neste caso, o compilador pode ser capaz de reescrever o teste de fim de lao para usar outra varivel de induo encontrada no lao. Se
o compilador puder remover este ltimo uso, ele pode eliminar a varivel de induo
original como cdigo morto. Essa transformao chamada de substituio de teste
de funo linear (LFTR Linear-Function Test Replacement).
Para realizar LFTR, o compilador deve (1) localizar comparaes que contam com
variveis de induo de outra forma desnecessrias, (2) localizar uma nova varivel de
induo apropriada que a comparao pudesse usar, (3) calcular a constante de regio
correta para o teste reescrito, e (4) reescrever o cdigo. Fazer com que LFTR coopere com
OSR pode simplificar todas essas tarefas para produzir uma transformao rpida e eficaz.

492 CAPTULO 10 Otimizaes escalares

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.

10.7.3 Escolhendo uma sequncia de otimizao


A eficcia de um otimizador sobre um dado cdigo depende da sequncia de otimizaes que ele aplica ao cdigo tanto as transformaes especficas que usa quanto

FIGURA 10.16 Exemplo aps LFTR.

10.7 Tpicos avanados 493

a ordem em que as aplica. Os compiladores otimizadores tradicionais tm oferecido


ao usurio a escolha de vrias sequncias (por exemplo, 0, 01, 02,...). Essas
sequncias fornecem um compromisso entre tempo de compilao e a quantidade
de otimizao que o compilador tenta realizar. Porm, um esforo de otimizao
aumentado no garante melhoria.

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.

Neste contexto, uma boa sequncia aquela


queproduz resultados dentro de 5% dos melhores
resultados.

494 CAPTULO 10 Otimizaes escalares

10.8 RESUMO E PERSPECTIVA


O projeto e implementao de um compilador otimizador um empreendimento complexo. Este captulo introduziu um framework conceitual para reflexo a respeito de
transformaes a taxonomia de efeitos. Cada categoria na taxonomia representada
por vrios exemplos, seja neste captulo ou em outros pontos do livro.
O desafio para o construtor de compiladores selecionar um conjunto de transformaes que funcionem bem juntas para produzir um bom cdigo cdigo que atenda
s necessidades do usurio. As transformaes especficas implementadas em um
compilador determinam, em grande parte, os tipos de programas para os quais ele
produzir um bom cdigo.

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].

10.8 Resumo e perspectiva 495

A combinao de otimizaes, como no SCCP, normalmente leva a melhorias que no


podem ser obtidas pela aplicao independente das otimizaes originais. A numerao
de valor combina eliminao de redundncia, propagao de constante e simplificao de
identidades algbricas [53]. LCM combina eliminao de redundncias e redundncias parciais com movimentao de cdigo [225]. Click e Cooper [86] combinam o
algoritmo de particionamento de Alpern [21] com SCCP [347]. Muitos autores tm
combinado alocao de registradores e escalonamento de instrues [48,163,269,
276,277,285,308].
O algoritmo SCCP deve-se a Wegman e Zadeck [346,347]. Seu trabalho esclareceu
a distino entre algoritmos otimista e pessimista; Click discute a mesma questo do
ponto de vista da criao de conjunto [84].
A reduo de fora de operador tem uma histria rica. Uma famlia de algoritmos de reduo de fora desenvolveu-se do trabalho de Allen, Cocke e Kennedy
[19,88,90,216,256]. O algoritmo OSR est nesta famlia [107]. Outra famlia de
algoritmos cresceu da tcnica de fluxo de dados para a otimizao exemplificada pelo
algoritmo LCM; diversas fontes oferecem tcnicas nessa famlia [127,129,131,178,
209,220,226]. A verso do OSR na Seo10.7.2 s reduz multiplicaes. Allen e outros mostram as sequncias de reduo para muitos outros operadores [19]; a extenso
do OSR para tratar desses casos simples. Uma forma mais fraca de reduo de fora
reescreve multiplicaes de inteiros por operaes mais rpidas [243].

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.

496 CAPTULO 10 Otimizaes escalares

b. D exemplo de uma expresso redundante que a LCM no descobrir,


masum algoritmo baseado em valor sim.
5. A eliminao de redundncia tem uma srie de efeitos sobre o cdigo
que o compilador gera.
a. Como a LCM afeta a demanda por registradores no cdigo que est sendo
transformado? Justifique sua resposta.
b. Como a LCM afeta o tamanho do cdigo gerado para um procedimento?
(Voc pode considerar que a demanda por registradores inalterada.)
c. Como a elevao afeta a demanda por registradores no cdigo que est sendo
transformado? Justifique sua resposta.
d. Como a elevao afeta o tamanho do cdigo gerado para um procedimento?
(Use as mesmas suposies.)
Seo 10.4
6. Uma forma simples de reduo de fora de operador substitui uma nica ocorrncia de uma operao dispendiosa por uma sequncia de operaes que so
menos dispendiosas de se executar. Por exemplo, algumas operaes de multiplicao de inteiros podem ser substitudas por uma sequncia de shifts e adds.
a. Que condies devem existir para que o compilador substitua com segurana
uma operao com inteiros x yxz por uma nica operao shift?
b. Esboce um algoritmo que substitua uma multiplicao de uma constante conhecida e um inteiro sem sinal por uma sequncia de shifts e adds em casos
em que a constante no uma potncia de dois.
7. Tanto a otimizao de chamada de cauda quanto a substituio em linha tentam
reduzir o overhead causado pela ligao de procedimentos.
a. O compilador pode substituir em linha uma chamada de cauda? Quais so
osobstculos? Como voc poderia contorn-los?
b. Compare o cdigo produzido no seu esquema em linha modificado
comaquele produzido pela otimizao de chamada de cauda.
Seo 10.5
8. Um compilador pode encontrar e eliminar computaes redundantes de muitas
maneiras diferentes. Entre elas esto DVNT e LCM.
a. D dois exemplos de redundncias eliminadas por DVNT que no podem
serencontradas por LCM.
b. D um exemplo que a LCM encontra e que perdido pela DVNT.

Dica: lembre-se do algoritmo de posicionamento


debloco, visto no Captulo8.

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

498 CAPTULO 11 Seleo de instrues

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.

Grande parte da responsabilidade por tratar de alvos diversificados est no seletor


de instruo. Um compilador tpico usa uma IR comum para todos os alvos e, at o
mximo possvel, para todas as linguagens-fonte; otimiza a forma intermediria com
base em um conjunto de suposies que so verdadeiras na maioria, se no em todas
as mquinas-alvo; e, finalmente, usa um back end em que o construtor de compiladores
procura isolar e extrair os detalhes dependentes do alvo.
Embora o escalonador e o alocador de registradores precisem de informaes dependentes do alvo, o bom projeto pode isolar esse conhecimento em uma descrio concreta
da mquina-alvo e sua ISA. Essa descrio poderia incluir tamanhos de conjunto de
registradores; nmero, capacidades e latncias de operao das unidades funcionais;
restries de alinhamento de memria; e a conveno de chamada de procedimento.
Os algoritmos para escalonamento e alocao so ento parametrizados por essas
caractersticas do sistema e reutilizados para diferentes ISAs e sistemas.
Assim, a chave para o redirecionamento de alvo est na implementao do seletor
de instruo. Um seletor de instruo redirecionvel consiste em um mecanismo de
casamento de padres acoplado a um conjunto de tabelas que codificam o conhecimento necessrio sobre o mapeamento da IR para a ISA alvo. O seletor consome a
IR do compilador e produz cdigo assembly para a mquina-alvo. Em tal sistema, o
construtor de compiladores cria uma descrio da mquina-alvo e executa o gerador
de back end (s vezes chamado gerador de cdigo). O gerador de back end, por
sua vez, usa a especificao para derivar as tabelas necessrias para o casamento de
padres. Assim como um gerador de parser, o gerador de back end trabalha off-line
durante o desenvolvimento do compilador. Assim, podemos usar algoritmos para criar
as tabelas que exigem mais tempo do que os algoritmos normalmente empregados
em um compilador.
Embora o objetivo seja isolar todo o cdigo dependente de mquina no seletor de instruo, escalonador e alocador de registradores, a realidade quase sempre fica aqum
desse ideal. Alguns detalhes dependentes de mquina aparecem, inevitavelmente, em
partes anteriores do compilador. Por exemplo, as restries de alinhamento nos registros de ativao podem diferir entre mquinas-alvo, alterando deslocamentos (offsets)
para valores armazenados nos registros de ativao (ARs). O compilador pode precisar
representar explicitamente recursos, como execuo predicada, slots de atraso de desvio
e operaes de memria multipalavra se precisar fazer bom uso destes recursos. Ainda
assim, empurrar detalhes dependentes de alvo para a seleo de instrues pode reduzir

11.2 Gerao de cdigo 499

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.

SELEO, ESCALONAMENTO E ALOCAO


Os trs processos principais no back end so: seleo de instrues, escalonamento e
alocao de registradores. Todos eles tm impacto direto sobre a qualidade do cdigo
gerado, e todos interagem entre si.
A seleo muda diretamente o processo de escalonamento, e dita tanto o tempo
exigido para uma operao quanto as unidades funcionais nas quais ela pode ser
executada. O escalonamento pode afetar a seleo de instrues. Se o gerador
decdigo pode implementar uma operao da IR com qualquer uma de duas
operaes assembly, e essas operaes usam recursos diferentes, o gerador de cdigo
pode precisar entender o escalonamento final para garantir a melhor escolha.
A seleo interage com a alocao de registradores de vrias maneiras.
Seoprocessador-alvo tem um conjunto de registradores uniforme, ento o seletor de
instruo pode assumir um estoque ilimitado de registradores e contar com oalocador
para inserir os loads e stores necessrios para encaixar os valores no conjunto
deregistradores. Se, por outro lado, a mquina-alvo tiver regras que restringem
ousoderegistradores, ento o seletor precisa prestar muita ateno aos registradores
fsicos especficos, o que pode complicar a seleo e predeterminar algumas outodas
as decises de alocao. Nessa situao, o gerador de cdigo poderia usar uma corrotina
para realizar a alocao de registradores local durante a seleo de instrues.
Manter seleo, escalonamento e alocao separados o mximo possvel pode
simplificar a implementao e a depurao de cada processo. Porm, como cada
um desses processos pode restringir os outros, o construtor de compiladores precisa
tomar cuidado para evitar a incluso de restries desnecessrias.

11.2 GERAO DE CDIGO


O back end do compilador precisa solucionar trs problemas para gerar cdigo executvel para um programa na forma IR: converter as operaes da IR em operaes na ISA
do processador-alvo, processo chamado seleo de instrues, que o assunto deste
captulo; selecionar uma ordem em que essas operaes devero ser executadas, processo chamado escalonamento de instrues, assunto do Captulo12; e deve determinar,
em cada ponto no cdigo final, quais valores devero residir em registradores e quais
devem residir na memria, processo chamado alocao de registradores, assunto do
Captulo13. A maioria dos compiladores trata desses trs processos separadamente.
Esses trs processos distintos, porm relacionados, normalmente so reunidos no termo
gerao de cdigo, embora o seletor de instruo tenha a responsabilidade principal
por gerar instrues da mquina-alvo.

500 CAPTULO 11 Seleo de instrues

Cada um desses trs problemas, por si s, uma questo computacionalmente difcil.


Embora no seja claro como definir a seleo de instrues tima, o problema de gerar
a sequncia de cdigo mais rpida para um CFG com fluxo de controle envolve
um grande nmero de alternativas. O escalonamento de instrues NP-completo
para um bloco bsico sob a maioria dos modelos de execuo realsticos; passar para
regies de cdigo maiores no simplifica o problema. A alocao de registradores, em
sua forma geral, tambm um problema NP-completo em procedimentos com fluxo de
controle. A maioria dos compiladores trata desses problemas de modo independente.
O nvel de detalhe exposto no programa IR faz diferena. Uma IR com um nvel de
abstrao mais alto do que a ISA exige que o seletor de instruo fornea detalhes
adicionais. (A gerao mecnica desse detalhe nesse ltimo estgio da compilao
pode levar a um cdigo tipo template com baixo nvel de personalizao.) Uma IR com
nvel de abstrao mais baixo do que a ISA permite que o seletor adapte suas selees
de modo correspondente. Compiladores que realizam pouca ou nenhuma otimizao
geram cdigo diretamente a partir da IR produzida pelo front end.
A complexidade da seleo de instrues vem do fato de que um processador tpico
oferece muitas e distintas maneiras de realizar a mesma computao. Desconsidere,
por enquanto, as questes de escalonamento de instruo e alocao de registradores;
voltaremos a eles nos dois captulos seguintes. Se cada operao da IR tivesse apenas
uma implementao na mquina-alvo, o compilador poderia simplesmente reescrever
cada uma delas como uma sequncia equivalente de operaes de mquina. Na maioria
dos contextos, porm, uma mquina-alvo fornece vrias maneiras de implementar cada
construo da IR.
Considere, por exemplo, uma construo da IR que copia um valor de um registrador
de uso geral, ri, para outro, rj. Suponha que o processador-alvo use ILOC como seu
conjunto de instruo nativo. Conforme veremos, at mesmo ILOC tem complexidade
suficiente para expor muitos dos problemas de gerao de cdigo. A implementao
bvia de r i r j usa i2i r i r j ; essa cpia de registrador-para-registrador
normalmente uma das operaes menos dispendiosas que um processador oferece.
Porm, existem muitas outras implementaes. Estas incluem, por exemplo, cada uma
das seguintes operaes:

Ainda existem outras possibilidades. Se o processador mantm um registrador cujo


valor sempre 0, outro conjunto de operaes funciona, usando add, sub, lshift,
rshift, or e xor. Um conjunto maior de sequncias de duas operaes, incluindo
um store seguido por um load, tambm funciona.
Um programador humano rapidamente no consideraria a maioria, se no todas essas sequncias alternativas. O uso de i2i simples, rpido e bvio. Um processo
automatizado, porm, pode ter que considerar todas as possibilidades e fazer as escolhas apropriadas. A capacidade de uma ISA especfica de realizar o mesmo efeito
de vrias maneiras aumenta a complexidade da seleo de instrues. Para ILOC, a
ISA fornece apenas algumas operaes simples de baixo nvel para cada efeito em
particular. Mesmo assim, ela admite inmeras maneiras de implementar a cpia de
registrador para registrador.

11.2 Gerao de cdigo 501

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

Como uma sequncia de cdigo mais curta busca


menos bytes da RAM, a reduo do espao do cdigo
tambm pode reduzir o consumo de energia.

502 CAPTULO 11 Seleo de instrues

frequentemente determinar a eficincia do cdigo gerado. Por exemplo, considere trs


cenrios para gerar cdigo a partir de uma IR tipo ILOC.

Passar de um cdigo de um endereo para outro de trs


endereos acarreta problemas semelhantes.

1. Mquina RISC simples, escalar. O mapeamento da IR para o assembly direto.


O gerador de cdigo poderia considerar apenas uma ou duas sequncias emlinguagem assembly para cada operao da IR.
2. Processador CISC. Para fazer uso eficaz de um conjunto de instrues CISC, o compilador pode ter que agregar vrias operaes da IR em uma operao damquina-alvo.
3. Mquina de pilha. O gerador de cdigo precisa traduzir do estilo computacional de registrador-para-registrador da ILOC para um estilo baseado em pilha
comseus nomes implcitos e, em alguns casos, operaes destrutivas.
medida que a lacuna na abstrao entre a IR e a ISA alvo aumenta, tambm aumenta
a necessidade de ferramentas para ajudar a criar geradores de cdigo.
Embora a seleo de instrues possa desempenhar papel importante para determinar a
qualidade do cdigo, o construtor de compiladores precisa ter em mente o enorme tamanho
do espao de busca que o seletor de instruo poderia explorar. Conforme veremos, at mesmo
os conjuntos de instrues com tamanho moderado podem produzir espaos de busca que
contm centenas de milhes de estados. Claramente, o compilador no tem condies de
explorar esses espaos exaustivamente. As tcnicas que descrevemos exploram o espao
de sequncias de cdigo alternativas em um padro disciplinado e, ou limitam sua pesquisa,
ou pr-calculam informaes suficientes para tornar eficiente uma pesquisa profunda.

11.3 EXTENSO DO ESQUEMA SIMPLES


DEPERCURSOEMRVORE
Para tornar a discusso concreta, considere as questes que podem surgir na gerao
de cdigo para uma instruo de atribuio como a b 2c. Ela poderia ser
representada por uma rvore sinttica abstrata (AST), como mostramos esquerda, ou
por uma tabela de qudruplas, como mostramos direita

A seleo de instrues precisa produzir um programa em linguagem assembly a partir


de representaes IR como essas duas. Por questo de discusso, suponha que ela deva
gerar operaes no subconjunto da ILOC apresentado na Figura11.1.

FIGURA 11.1 Subconjunto ILOC.

11.3 Extenso do esquema simples depercursoemrvore 503

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).

504 CAPTULO 11 Seleo de instrues

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

FIGURA 11.2 Variaes na multiplicao.

11.3 Extenso do esquema simples depercursoemrvore 505

caso, ef, a ineficincia vem do fato de que o esquema de percurso em rvore no


gera operaes loadAI. O cdigo mais complicado no caso IDENT pode resolver
este problema.
O segundo caso, e2, mais difcil. O gerador de cdigo poderia implementar a
multiplicao com uma operao multI. Porm, para reconhecer este fato, ele precisa
examinar alm do contexto local. Para trabalhar isto no esquema de percurso em rvore,
o caso parapoderia reconhecer que uma subrvore avaliada como uma constante.
Alternativamente, o cdigo que trata do n NUM poderia determinar que seu pai pode
ser implementado com uma operao imediata. De qualquer forma, isto exige contexto
no local que viola o paradigma simples de percurso em rvore.
O terceiro caso, gh, tem outro problema no local. As duas subrvores dereferem-se a uma varivel com deslocamento 4 a partir de seu endereo de base. As
referncias tm diferentes endereos de base. O esquema de percurso em rvore original
gera uma operao loadI explcita para cada constante @G, 4, @H e 4. Uma verso
corrigida para usar loadAI, conforme mencionamos, geraria loadIs separados para
@G e @H, ou dois loadIs para 4. (Naturalmente, os tamanhos dos valores de @G e @H
entram em cena. Se forem muito longos, o compilador precisa usar 4 como operando
imediato para as operaes loadAI.)
O problema fundamental com este terceiro exemplo est no fato de que o cdigo
final contm uma subexpresso comum que foi ocultada na AST. Para descobrir a
redundncia e tratar dela de modo apropriado, o gerador de cdigo exigiria um cdigo
que verificasse explicitamente o endereo de base e os valores de deslocamento das
subrvores e gerasse sequncias apropriadas para todos os casos. Este tratamento seria
complicado. O tratamento de todos os casos semelhantes que podem surgir exigiria
uma quantidade proibitiva de codificao adicional.
Um modo melhor de capturar esse tipo de redundncia expor os detalhes redundantes
na IR e permitir que o otimizador os elimine. Para a atribuio de exemplo, a b
2c, o front end poderia produzir a rvore de baixo nvel mostrada na Figura11.3,
que tem vrios novos tipos de ns. Um n Val representa um valor conhecido para
residir em um registrador, como o ARP em rarp.

FIGURA 11.3 AST de baixo nvel para a b 2c.

506 CAPTULO 11 Seleo de instrues

GERAO DE CDIGO TIMO


O esquema de percurso em rvore para selecionar instrues produz a mesma sequncia
de cdigo toda vez que encontra um tipo particular de n AST. Esquemas mais realistas
consideram vrios padres e usam modelos de custo para escolher entre eles. Isto leva,
naturalmente, pergunta: um compilador pode fazer escolhas timas?
Se cada operao tem um custo associado, e se ignorarmos os efeitos
doescalonamento de instruo e alocao de registradores, ento a seleo
deinstrues tima possvel. Os geradores de cdigo de casamento de
padresdervore, descritos na Seo 11.4, produzem sequncias localmente
timas ouseja, cada subrvore calculada por uma sequncia de custo mnimo.
A dificuldade de capturar o comportamento em tempo de execuo em um nico
nmero de custo questiona a importncia desta afirmao. O impacto da ordem
deexecuo, recursos de hardware limitados e o comportamento sensvel ao contexto
na hierarquia de memria complicam o problema de determinar o custo real de
qualquer sequncia de cdigo especfica.
Na prtica, os compiladores mais modernos ignoram bastante o escalonamento
ea alocao durante a seleo de instrues, e assumem que os custos associados
a diversas regras de reescrita so precisos. Com essas suposies, o compilador
procura sequncias localmente timas aquelas que minimizam o custo estimado
parauma subrvore inteira. O compilador, ento, realiza o escalonamento e a alocao
emumaou mais ps-passagens pelo cdigo produzido pela seleo de instrues.

Um n Lab representa um smbolo relocvel, normalmente um rtulo de nvel assembly


usado para cdigo ou dados. Um n significa um nvel de indireo; seu filho um
endereo e ele produz o valor armazenado neste endereo. Esses novos tipos de n
exigem que o construtor de compiladores especifique mais regras de correspondncia.
Porm, em retorno, o detalhe adicional pode ser otimizado, como as referncias duplicadas a 4 em gh.
Esta verso da rvore expe detalhes em um nvel de abstrao mais baixo do que o
conjunto de instrues ILOC alvo. A inspeo dessa rvore revela, por exemplo, que
a uma varivel local armazenada no deslocamento 4 a partir do ARP, que b um
parmetro de chamada por referncia (observe os dois ns ) e que c est armazenado
no deslocamento 12 a partir do rtulo @G. Alm do mais, as adies que so implcitas
nas operaes loadAI e storeAI aparecem explicitamente na rvore como uma
subrvore de um n , ou como o filho da esquerda de um n .
A exposio de mais detalhes na AST deve levar a um cdigo melhor, assim como o
aumento no nmero de operaes da mquina-alvo que o gerador de cdigo considera.
Juntos, porm, esses fatores criam uma situao em que o gerador de cdigo pode descobrir muitas maneiras diferentes de implementar determinada subrvore. O esquema
simples de percurso em rvore tinha uma opo para cada tipo de n AST. Para fazer uso
eficaz do conjunto de instrues da mquina-alvo, o gerador de cdigo deve considerar
tantas possibilidades quanto possvel.
Essa complexidade aumentada no surge por uma metodologia em particular ou um
algoritmo de casamento especfico; ao invs, isto reflete um aspecto fundamental
do problema subjacente qualquer mquina poderia oferecer vrias maneiras de
implementar uma construo da IR. Quando o gerador de cdigo considera vrias
combinaes possveis para determinada subrvore, precisa de um modo para escolher
entre elas. Se o construtor de compiladores puder associar um custo a cada padro,
ento o esquema de casamento pode selecionar padres de um modo que minimize os

11.4 Seleo de instrues por casamento depadres de rvore 507

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?

11.4 SELEO DE INSTRUES POR CASAMENTO


DEPADRES DE RVORE
O construtor de compiladores pode usar as ferramentas de casamento de padres de
rvore para enfrentar a complexidade da seleo de instrues. Para transformar a
gerao de cdigo em casamento de padres de rvore, tanto a forma IR do programa
quanto o conjunto de instrues da mquina-alvo devem ser expressos como rvores.

508 CAPTULO 11 Seleo de instrues

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

11.4 Seleo de instrues por casamento depadres de rvore 509

baixo para cima e na generalidade de seus modelos de custo custos fixos estticos
versus custos que podem variar durante o processo de casamento.

11.4.1 Regras de reescrita


O construtor de compiladores codifica os relacionamentos entre rvores de operao
e subrvores na AST como um conjunto de regras de reescrita. O conjunto de regras
inclui uma ou mais regras para cada tipo de n da AST. Uma regra de reescrita consiste
em uma produo em uma gramtica de rvore, um gabarito de cdigo e um custo associado. A Figura11.4 mostra um conjunto de regras de reescrita para ladrilhar nossa
AST de baixo nvel com operaes ILOC

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

510 CAPTULO 11 Seleo de instrues

As regras na Figura11.4 formam uma gramtica de rvore semelhante quelas que


usamos para especificar a sintaxe das linguagens de programao. Cada regra de reescrita, ou produo, tem um smbolo no terminal como seu lado esquerdo. Na regra 16,
o no terminal Reg, que representa uma coleo de subrvores que a gramticade
rvore pode gerar, neste caso, usando as regras de 6 a 25. O lado direito de uma regra
um padro de rvore linearizado. Na regra 16, este padro +(Reg1, Num2), representando a adio de dois valores, um Reg e um Num.
As regras na Figura11.4 usam Reg tanto como um smbolo terminal quanto como
no terminal do conjunto de regras. Este fato reflete uma abreviao no exemplo. Um
conjunto completo de regras incluiria um conjunto de produes que reescreve Reg
com um nome de registrador especfico, como Reg r0, Reg r1, ... e Reg rk.
Os no terminais na gramtica permitem a abstrao. Eles servem para conectar as
regras da gramtica, alm de codificar o conhecimento sobre onde o valor correspondente armazenado em tempo de execuo e que forma ele tem. Por exemplo, Reg
representa um valor produzido por uma subrvore e armazenado em um registrador,
enquanto Val representa um valor j armazenado em registrador. Um Val poderia ser
um valor global, como o ARP. Ele poderia ser o resultado de uma computao realizada
em uma subrvore disjunta uma subexpresso comum.
O custo associado a uma produo deve fornecer ao gerador de cdigo uma estimativa
realista do custo de execuo do cdigo apresentado no gabarito. Para a regra 16, o custo
1 para refletir o fato de que a rvore pode ser implementada com uma nica operao
que exige apenas um ciclo para sua execuo. O gerador de cdigo usa os custos para
escolher entre as alternativas possveis. Algumas tcnicas de casamento restringem os
custos a nmeros; outras permitem custos que variem durante o casamento para refletir
o impacto das escolhas anteriores sobre o custo das alternativas atuais.
Trs padres podem capturar contexto de um modo que o gerador de cdigo de percurso
em rvore simples no consegue. Cada uma das regras de 10 a 14 realiza o casamento
com dois operadores ( e +). Essas regras expressam as condies em que os operadores
ILOC loadAO e loadAI podem ser usados. Qualquer subrvore que corresponda a
uma dessas cinco regras pode ser ladrilhada com uma combinao de outras regras.
Uma subrvore que case com a regra 10 tambm pode ser ladrilhada com o casamento
da regra 15 para produzir um endereo, e com a regra 9 para carregar o valor. Essa
flexibilidade torna o conjunto de regras de reescrita ambgua. A ambiguidade reflete
o fato de que a mquina-alvo tem vrias maneiras de implementar essa subrvore em
particular. Como o gerador de cdigo de percurso em rvore realiza o casamento de um
operador por vez, ele no pode gerar diretamente qualquer uma dessas operaes ILOC.
Para aplicar essas regras a uma rvore, procuramos uma sequncia de etapas de reescrita
que reduz a rvore a um nico smbolo. Para uma AST que representa um programa
completo, esse smbolo deve ser o smbolo inicial. Para um n interior, este smbolo
normalmente representa o valor produzido pela avaliao da subrvore enraizada na
expresso. O smbolo tambm deve especificar onde o valor existe normalmente
em um registrador, em um local da memria ou como um valor constante conhecido.
A Figura11.5 mostra uma sequncia de reescrita para a subrvore que referencia a varivel c na Figura11.3. (Lembre-se de que c estava no deslocamento 12 a partir do rtulo
@G.) O painel mais esquerda mostra a subrvore original. Os painis restantes mostram
uma sequncia de reduo para essa subrvore. O primeiro casamento na sequncia reconhece que a folha da esquerda (um n Lab) combina com a regra 6, o que nos permite
reescrev-la como um Reg. A rvore agora reescrita combina com o lado direito da regra
11, (+ (Reg1, Num2)), de modo que podemos reescrever arvore inteira enraizada em
como Reg. Essa sequncia, indicada como 6,11, reduz a subrvore inteira para Reg.

11.4 Seleo de instrues por casamento depadres de rvore 511

FIGURA 11.5 Sequncia simples de reescrita de rvore.

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.6 Potenciais casamentos.

512 CAPTULO 11 Seleo de instrues

Para produzir cdigo assembly, o seletor usa os gabaritos de cdigo associados


a cada regra. O gabarito de cdigo de uma regra consiste em uma sequncia de
operaes de cdigo assembly que implementa a subrvore gerada pela produo.
Por exemplo, a regra 15 mapeia o padro de rvore+(Reg1, Reg2) ao gabarito de
cdigo add r1, r2 rnew. O seletor substitui cada um de r1 e r2 pelo nome de
registrador que mantm o resultado da subrvore correspondente, e aloca um novo
nome de registrador virtual para rnew. Um ladrilhamento para uma AST especifica
quais regras o gerador de cdigo deve usar. Este usa os gabaritos associados para
gerar cdigo assembly em um percurso de baixo para cima; fornece nomes, conforme
a necessidade, para unir os locais de armazenamento e emite as operaes correspondentes ao percurso.
O seletor de instruo deve escolher um ladrilhamento que produz a sequncia de
cdigo assembly com menor custo. A Figura11.7 mostra o cdigo que corresponde
a cada ladrilhamento em potencial. Os nomes de registrador arbitrrios foram substitudos onde apropriado. Tanto 6,11 quanto 8,14 produzem o custo mais baixo
dois, e levam a sequncias de cdigo diferentes, porm equivalentes. Como tm
custos idnticos, o seletor est livre para escolher entre elas. As outras sequncias so,
conforme esperado, mais dispendiosas.
Se o loadAI s aceitar argumentos em um intervalo limitado, a sequncia 8,14 pode
no funcionar, pois o endereo que eventualmente substituir @G pode ser muito grande
para o campo imediato da operao. Para lidar com este tipo de restrio, o construtor
de compiladores pode introduzir na gramtica de reescrita a noo de uma constante
com um intervalo de valores adequadamente limitado. Isso pode tomar a forma de um
novo smbolo terminal que s pode representar inteiros em um determinado intervalo,
tal como 0i<4096 para um campo de 12 bits. Com tal distino, e com cdigo que
verifica cada ocorrncia de um inteiro para classific-la, o gerador de cdigo poderia
evitar a sequncia 8,14, a menos que @G caia no intervalo permitido para um operando
imediato de loadAI.
O modelo de custo orienta o gerador de cdigo a selecionar uma das melhores sequncias. Por exemplo, observe que a sequncia 6,8,10 usa duas operaes loadI,
seguidas por um loadAO. O gerador de cdigo prefere as sequncias de menor custo,
cada uma das quais evita uma das operaes loadI e emite menos operaes. De
modo semelhante, o modelo de custo evita as quatro sequncias que usam uma adio
explcita preferindo, ao invs disso, realizar a adio implicitamente no hardware
de endereamento.

FIGURA 11.7 Sequncias de cdigo para os casamentos.

11.4 Seleo de instrues por casamento depadres de rvore 513

11.4.2 Determinao de um ladrilhamento


Para aplicar essas ideias gerao de cdigo, precisamos de um algoritmo que possa
construir um bom ladrilhamento, ou seja, um que produza cdigo eficiente. Dado um
conjunto de regras que codificam as rvores de operadores e as relacionem estrutura
de uma AST, o gerador de cdigo deve descobrir um ladrilhamento eficiente para uma
AST especfica. Existem vrias tcnicas para esta construo, semelhantes em conceito,
mas diferentes nos detalhes.
Para simplificar o algoritmo, fazemos duas suposies sobre a forma das regras de
reescrita. Primeiro, cada operao tem, no mximo, dois operandos. A extenso do
algoritmo para lidar com o caso geral simples, mas os detalhes complicam a explicao. Segundo, o lado direito de uma regra contm no mximo uma operao. Esta
restrio simplifica o algoritmo de casamento, com nenhuma perda de generalidade. Um
procedimento mecnico simples pode transformar o caso irrestrito a este mais simples.
Para uma produo a op1(b,op2(g,d)), reescreva-a como a op1(b,a9) e a9
op2(g,d), onde a9 um novo smbolo que s ocorre nessas duas regras. O crescimento
resultante linear no tamanho da gramtica original.
Para tornar isto concreto, considere a regra 11, Reg (+ (Reg1,Num2)). A transformao a reescreve como Reg (R11P2) e R11P2 +(Reg1,Num2), onde
R11P2 um novo smbolo. Observe que a nova regra para R11P2 duplica a regra 16
para addI. A transformao acrescenta outra ambiguidade gramtica. Porm, rastrear
e realizar o casamento das duas regras independentemente permite que o algoritmo de
casamento de padres considere o custo de cada uma. O par de regras que substitui a
regra 11 deve ter um custo de um, o custo da regra original. (Cada regra poderia ter
custo fracionrio, ou uma delas ter custo zero.) Isto reflete o fato de que a reescrita
com a regra 16 produz uma operao addI, enquanto a regra para R11P2 inclui a
adio na gerao de endereo de uma operao loadAI. O casamento de duas regras,
pelo menor custo, levar o algoritmo de casamento de padres sequncia de cdigo
loadAI quando possvel especializando o cdigo para aproveitar o acrscimo
pouco dispendioso fornecido no modo de endereo AI.
O objetivo do ladrilhamento rotular cada n da AST com um conjunto de padres que
o compilador possa usar para implement-lo. Como os nmeros de regra correspondem
diretamente aos padres do lado direito, o gerador de cdigo pode us-los como uma
abreviao para os padres. O compilador pode calcular sequncias de nmeros de regra, ou padres, para cada n em uma travessia em ps-ordem da rvore. A Figura11.8
esboa um algoritmo, Tile, que encontra ladrilhamentos para a rvore enraizada no
n n da AST. Ela inclui em cada n AST n um conjunto Label(n) que contm todos
os nmeros de regra que podem ser usados para ladrilhar a rvore enraizada no n n, e
calcula os conjuntos Label em uma travessia em ps-ordem para garantir que rotula
os filhos do n antes de rotular o n.
Considere o lao interno para o caso de um n binrio. Para calcular Label(n), ele
examina cada regra r que implementa a operao especificada por n, e usa as funes
left e right para percorrer a AST e os padres de rvore (ou lados direitos das
regras). Como Tile j rotulou os filhos de n, pode usar um teste simples de pertinncia
para comparar os filhos de r com os de n. Se left(r) Label(left(n)),
ento Tile j descobriu que pode gerar cdigo para a subrvore esquerda de n de
um modo compatvel com o uso de r para implementar n. Um argumento semelhante
vale para as subrvores da direita de r e n. Se as duas subrvores combinarem, ento
r pertence a Label(n).
Um gerador de cdigo de casamento de padres de rvore criado a partir desse algoritmo gastar a maior parte do seu tempo nos dois laos for calculando combinaes

Cada regra especifica um operador e no mximo dois


filhos. Assim, para uma regra r, left(r) e right(r) tm
significados claros.

514 CAPTULO 11 Seleo de instrues

para operadores binrios ou unrios. Para acelerar o gerador de cdigo, o construtor


de compiladores pode pr-calcular todas as combinaes possveis e armazenar os
resultados em uma tabela tridimensional, indexada por uma operao (n no algoritmo)
e os conjuntos de rtulos de seus filhos da esquerda e da direita. Se substituirmos cada
um dos laos for por uma simples pesquisa em tabela, o algoritmo se torna um percurso
de custo linear sobre a rvore.
As tabelas neste esquema podem se tornar muito grandes. Por exemplo, a tabela de
pesquisa para operadores binrios tem tamanho |rvores de operao||conjuntos
dertulos|2; para operadores unrios, tem apenas duas dimenses, com tamanho
|rvores de operao||conjuntos de rtulos|. Os conjuntos de rtulos so limitados
em tamanho. Se R o nmero de regras, ento |Label(n)|R, e no poder haver
mais do que 2R conjuntos de rtulos distintos.
Para uma mquina com 200 operaes e uma gramtica com 1024 conjuntos de rtulos
distintos (R=10), a tabela resultante tem mais de 200.000.000 de entradas. Como a
estrutura da gramtica elimina muitas possibilidades, as tabelas construdas para esta
finalidade so esparsas e podem ser codificadas de modo eficiente. Na verdade, a descoberta de maneiras de criar e codificar essas tabelas de modo eficiente foi um dos
principais avanos que tornaram o casamento de padres de rvore uma ferramenta
prtica para a gerao de cdigo.

Determinao de casamentos de baixo custo


O algoritmo da Figura11.8 encontra todos os casamentos possveis dentro do conjunto
de padres. Na prtica, queremos que o gerador de cdigo encontre o casamento de
menor custo. Embora ele possa obter o casamento de menor custo a partir do conjunto
de todos os casamentos, existem maneiras mais eficientes de calcular o casamento.
Conceitualmente, o gerador de cdigo pode descobrir o casamento de menor custo
para cada subrvore em uma passagem de baixo para cima pela AST. Esta travessia
pode calcular o custo de cada casamento alternativo o custo da regra casada mais
os custos dos casamentos de subrvore associados. Em princpio, isto pode descobrir
casamentos como na Figura11.8 e reter os de menor custo, ao invs de todos os casamentos. Na prtica, o processo ligeiramente mais complexo.

FIGURA 11.8 Clculo dos conjuntos Label para ladrilhar uma AST.

11.4 Seleo de instrues por casamento depadres de rvore 515

A funo de custo depende, inerentemente, do processador-alvo; ela no pode ser


derivada automaticamente da gramtica. Ao invs disso, deve codificar propriedades da
mquina alvo e refletir as interaes que ocorrem entre as operaes em um programa
assembly particularmente, o fluxo de valores de uma operao para outra.
Um valor no programa compilado pode ter diferentes formas e residir em diferentes
locais. Por exemplo, um valor poderia residir em um local da memria ou em um registrador; alternativamente, poderia ser uma constante que seja pequena o suficiente para
caber em algumas ou todas as operaes imediatas. (Um operando imediato reside no
fluxo de instrues.) As escolhas entre as formas e os locais importa para o seletor de
instruo porque elas mudam o conjunto de operaes da mquina-alvo que podem
usar o valor.
Quando o seletor de instruo constri o conjunto de casamentos para determinada
subrvore, precisa saber o custo de avaliar cada um dos operandos da subrvore. Se
esses operandos puderem estar em diferentes classes de armazenamento como registradores, locais da memria ou constantes imediatas , o gerador de cdigo precisa
saber o custo de avaliar o operando para cada um dessas classes de armazenamento.
Assim, precisa rastrear as sequncias de menor custo que geram cada uma dessas
classes. Ao fazer a travessia de baixo para cima para calcular os custos, o gerador de
cdigo pode facilmente determinar o casamento de menor custo para cada classe. Isto
acrescenta uma pequena quantidade de espao e tempo ao processo, mas o aumento
limitado por um fator igual ao nmero de classes de armazenamento nmero que
depende inteiramente da mquina-alvo, e no do nmero de regras de reescrita.
Uma implementao cuidadosa pode acumular esses custos enquanto ladrilha a rvore.
Se, em cada casamento, o gerador de cdigo retiver os casamentos de menor custo,
produzir um ladrilhamento localmente timo. Ou seja, em cada n no existe uma
alternativa melhor, dado o conjunto de regras e as funes de custo. Esse acmulo de
custos de baixo para cima implementa uma soluo de programao dinmica para
encontrar o ladrilhamento de custo mnimo.
Se exigirmos que os custos sejam fixos, o clculo do custo pode ser includo na construo do algoritmo de casamento de padres. Essa estratgia transfere a computao
do tempo de compilao para o algoritmo de construo e quase sempre produz um
gerador de cdigo mais rpido. Se permitirmos que os custos variem e considerarmos
o contexto em que um casamento feito, ento o clculo e a comparao de custo
devem ser feitos em tempo de compilao. Embora esse esquema possa tornar mais
lento o gerador de cdigo, permite mais flexibilidade e preciso nas funes de custo.

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.

516 CAPTULO 11 Seleo de instrues

informao de estado exigida. Vrios sistemas diferentes tm sido criados usando


essa tcnica, normalmente chamados sistemas de reescrita de baixo paracima
(BURS Bottom-Up Rewrite Systems).
3. A forma das regras como de uma gramtica sugere o uso de tcnicas de parsing
(anlise sinttica). Os algoritmos de parsing devem ser estendidos para lidar
com as gramticas altamente ambguas que resultam de descries de mquina
eparaescolher anlises de menor custo.
4. Linearizando a rvore em uma string de prefixos, o problema pode ser traduzido
para um problema de casamento de strings. Ento, o compilador pode usar algoritmos de casamento de padres de strings para encontrar os potenciais casamentos.
Existem ferramentas disponveis para implementar cada uma das trs ltimas abordagens. O construtor de compiladores produz uma descrio do conjunto de instrues de
uma mquina-alvo e um gerador de cdigo cria o cdigo executvel a partir da descrio.
As ferramentas automatizadas diferem nos detalhes. O custo por instruo emitida
varia com a tcnica. Algumas so mais rpidas, outras so mais lentas; nenhuma
lenta o suficiente para que tenha impacto importante sobre a velocidade do compilador
resultante. As abordagens permitem diferentes modelos de custo. Alguns sistemas restringem o construtor de compiladores a um custo fixo para cada regra; como retorno,
podem realizar alguma ou toda a programao dinmica durante a gerao de tabela.
Outros permitem modelos de custo mais genricos, que podem variar o custo durante
o processo de casamento; esses sistemas precisam realizar a programao dinmica
durante a gerao de cdigo. Em geral, porm, todas essas abordagens produzem
geradores de cdigo que so eficientes e eficazes.
REVISO DA SEO
A seleo de instrues por meio do casamento de padres de rvore conta com
ofato simples de que as rvores so uma representao natural para as operaes
emum programa e para as operaes na ISA da mquina-alvo. O construtor de
compiladores desenvolve uma biblioteca de padres de rvore que mapeia as construes da IR do compilador em operaes na ISA alvo. Cada padro consiste em
uma pequena rvore de IR, um gabarito de cdigo e um custo. O seletor encontra
um ladrilhamento de baixo custo para a rvore; em um percurso em ps-ordem da
rvore ladrilhada, gera o cdigo a partir dos gabaritos dos ladrilhos selecionados.
Vrias tecnologias tm sido usadas para implementar os passos de ladrilhamento.
Estas incluem algoritmos de casamento codificados mo, conforme o apresentado
na Figura11.8, algoritmos de casamento baseados em parser operando sobre
gramticas ambguas, algoritmos de casamento lineares baseados em algoritmos
decasamento de strings para as formas linearizadas de rvores e algoritmos de casamento baseados em autmatos. Todas essas tecnologias tm funcionado bem em um
ou mais sistemas. Os seletores de instruo resultantes so executados rapidamente e
produzem cdigo de alta qualidade.

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 Seleo de instrues por meio da otimizao peephole 517

11.5 SELEO DE INSTRUES POR MEIO DA OTIMIZAO


PEEPHOLE
Outra tcnica para realizar as operaes de casamento que se encontram no centro da
seleo de instrues baseada em uma tecnologia desenvolvida para a otimizao de
ltimo estgio, chamada otimizao peephole. Para evitar a complexidade de codificao no gerador de cdigo, esta tcnica combina a otimizao local sistemtica em uma
IR de baixo nvel com um esquema simples de casamento da IR com as operaes da
mquina-alvo. Esta seo introduz a otimizao peephole, explora seu uso como um
mecanismo para seleo de instrues e descreve as tcnicas que foram desenvolvidas
para automatizar a construo dos otimizadores peephole.

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 o otimizador peephole reconhecesse que essa reescrita tornava a operao store


morta (ou seja, o load era o nico uso para o valor armazenado na memria), tambm
poderia eliminar a operao store. Em geral, porm, reconhecer stores mortos exige a
anlise global, que est alm do escopo de um otimizador peephole. Outros padres
receptivos melhoria pela otimizao peephole incluem identidades algbricas simples, como

e casos em que o destino de um desvio , por si s, um desvio

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).

518 CAPTULO 11 Seleo de instrues

CASAMENTO DE PADRES DE RVORE SOBRE QUDRUPLAS?


Os termos usados para descrever essas tcnicas casamento de padres de rvore e
otimizao peephole contm suposies implcitas sobre os tipos de IR aos quais
podem ser aplicados. A teoria BURS lida com a reescrita de operaes em rvores.
Isto cria a impresso de que os geradores de cdigo baseados em BURS exigem IRs
emforma de rvore. De modo semelhante, os otimizadores peephole foram propostos
inicialmente como um passo final de melhoria de assembly para assembly. A ideia
deuma janela de instruo em movimento sugere fortemente uma IR linear, de baixo
nvel, para um gerador de cdigo baseado em peephole.
As duas tcnicas podem ser adaptadas para a maioria das IRs. Um compilador pode
interpretar uma IR linear de baixo nvel, como a ILOC, como se fossem rvores. Cada
operao torna-se um n da rvore; as arestas so implicadas pela reutilizao
deoperandos. De modo semelhante, se o compilador atribui um nome a cada
n, pode interpretar rvores como uma forma linear realizando um percurso
dervore emps-ordem. Um implementador inteligente pode adaptar os mtodos
apresentados neste captulo a uma grande variedade de IRs reais.

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.

Estruturalmente, isto se parece com um compilador. A expanso reconhece o cdigo


de entrada na forma IR e cria uma representao interna. A simplificao realiza
algumas operaes de reescrita sobre essa IR. O casamento transforma a IR em cdigo
da mquina-alvo, normalmente cdigo assembly (ASM). Se as linguagens de entrada e sada forem a mesma, este sistema um otimizador peephole. Com diferentes
linguagens como entrada e sada, os mesmos algoritmos podem realizar seleo de
instrues, conforme veremos na Seo 11.5.2.
A expanso reescreve a IR, operao por operao, para uma sequncia de operaes
de IR de nvel mais baixo (LLIR Lower-Level IR) que representa todos os efeitos
diretos de uma operao pelo menos, todos aqueles que afetam o comportamento
do programa. Se a operao add ri,rj rk define o cdigo de condio, ento sua
representao LLIR deve incluir operaes que atribuem ri+rj a rk e definem o
cdigo de condio para o valor apropriado. Normalmente, a expanso tem uma estrutura simples. As operaes podem ser expandidas individualmente, sem considerar
o contexto. O processo usa um gabarito para cada operao da IR e substitui os nomes
de registrador, constantes e rtulos apropriados nos gabaritos.
A simplificao faz uma passagem sobre a LLIR, examinando as operaes em uma
pequena janela e tentando melhor-las sistematicamente. Os mecanismos bsicos de
simplificao so substituio direta, simplificao algbrica (por exemplo, x+0 x),

11.5 Seleo de instrues por meio da otimizao peephole 519

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

FIGURA 11.9 Expanso, Simplificao e Casamento aplicados ao exemplo.

520 CAPTULO 11 Seleo de instrues

FIGURA 11.10 Sequncias produzidas pela simplificao.

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.

Reconhecimento de valores mortos


Quando o simplificador confronta uma sequncia como a mostrada na margem, pode
incluir o valor 2 no lugar do uso de r12 na segunda operao. Porm, no pode eliminar
a primeira operao, a menos que saiba que r12 no est vivo aps o uso na segunda
operao ou seja, o valor est morto. Assim, a capacidade de reconhecer quando
um valor no est mais vivo desempenha papel crtico na operao do simplificador.

11.5 Seleo de instrues por meio da otimizao peephole 521

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:

A primeira atribuio a cc est morta. Se a simplificao elimin-la, poder combinar


as operaes restantes em uma operao de multiplicao-adio, supondo que a
mquina alvo tenha tal instruo. Porm, se no puder eliminar cc fx(ri, rj), o
casamento no poder usar a multiplicao-adio, pois no pode definir o cdigo de
condio duas vezes.

Operaes de fluxo de controle


A presena de operaes de fluxo de controle complica a simplificao. O modo mais
fcil de tratar delas limpar a janela do simplificador quando esta alcanar um desvio,
um salto ou uma instruo rotulada. Isto impede que o simplificador mova efeitos para
caminhos onde no estavam presentes.

ltimo uso
Uma referncia a um nome aps a qual o valor
representado por este nome no est mais vivo.

522 CAPTULO 11 Seleo de instrues

O simplificador pode alcanar resultados melhores examinando o contexto ao redor


de desvios, mas isto introduz vrios casos especiais ao processo. Se a linguagem de
entrada codificar desvios com um nico destino e um caminho fall-through, ento o
simplificador deve rastrear e eliminar os rtulos mortos. Se eliminar o ltimo uso de
um rtulo e o bloco anterior tiver uma sada fall-through, ento pode remover o rtulo,
combinar os blocos e simplificar por meio da fronteira antiga. Se a linguagem de entrada
codifica desvios com dois destinos, ou o bloco anterior terminar com um salto, ento um
rtulo morto implica um bloco inalcanvel, que pode ser completamente eliminado.
De qualquer forma, o simplificador deve rastrear o nmero de usos para cada rtulo e
eliminar os que no podem mais ser referenciados. (A expanso pode contar referncias
de rtulo, permitindo que o simplificador use um esquema simples de contagem de
referncia para rastrear o nmero de referncias restantes.)
Uma tcnica mais agressiva poderia considerar as operaes nos dois lados de um
desvio. Algumas simplificaes podem ser possveis por meio do desvio, combinando
os efeitos da operao imediatamente antes do desvio com aqueles da operao no
destino do desvio. Porm, o simplificador precisa considerar todos os caminhos que
alcanam a operao rotulada.
As operaes predicadas exigem algumas dessas mesmas consideraes. Em runtime,
os valores predicados determinam quais operaes realmente so executadas. Com
efeito, os predicados especificam um caminho atravs de um CFG simples, embora
sem rtulos ou desvios explcitos. O simplificador precisa reconhecer esses efeitos e
trat-los da mesma forma cuidadosa que usa para as operaes rotuladas.

Janelas fsicas versus janelas lgicas


A discusso, at o momento, se concentrou em uma janela contendo operaes adjacentes na IR de baixo nvel. Essa noo tem uma boa intuio fsica e torna o conceito
concreto. Porm, operaes adjacentes na IR de baixo nvel podem no operar sobre os
mesmos valores. De fato, medida que as mquinas-alvo oferecem mais paralelismo
em nvel de instruo, o front end e o otimizador de um compilador devem gerar programas IR que tenham computaes mais independentes e intercaladas, para manter
ocupadas as unidades funcionais da mquina-alvo. Neste caso, o otimizador peephole
pode encontrar muito poucas oportunidades para melhorar o cdigo.
Para melhorar esta situao, o otimizador peephole pode usar uma janela lgica,
ao invs de uma janela fsica. Com uma janela lgica, ele considera operaes que
esto conectadas pelo fluxo de valores dentro do cdigo ou seja, considera juntas
operaes que definem e usam o mesmo valor, criando a oportunidade de combinar e
simplificar operaes relacionadas, mesmo que no sejam adjacentes no cdigo.
Durante a expanso, o otimizador pode vincular cada definio com o prximo uso do
seu valor no bloco. O simplificador usa esses vnculos para preencher sua janela. Quando o simplificador alcana a operao i, constri uma janela para i puxando operaes
vinculadas ao resultado de i. (Como a simplificao conta, em grande parte, com a
substituio para a frente, h pouco motivo para considerar a operao fsica seguinte,
a menos que use o resultado de i.) O uso de uma janela lgica dentro de um bloco pode
tornar o simplificador mais eficaz, reduzindo tanto o tempo de compilao exigido
quanto o nmero de operaes restantes aps a simplificao. Em nosso exemplo, uma
janela lgica permitiria que o simplificador inclusse a constante 2 na multiplicao.
A extenso dessa ideia para escopos maiores acrescenta alguma complicao. O
compilador pode tentar simplificar operaes que so logicamente adjacentes, mas
muito distantes para caber na janela peephole juntas seja dentro do mesmo bloco

11.5 Seleo de instrues por meio da otimizao peephole 523

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.

RISC, CISC E SELEO DE INSTRUES


Os primeiros proponentes das arquiteturas RISC sugeriram que os RISCs levariam
acompiladores mais simples. As primeiras mquinas RISC, como IBM 801, tinham
muito menos modos de endereamento do que as CISC contemporneas (comoVAX11da DEC). Elas possuam operaes de registrador-para-registrador, comoperaes
load e store separadas para mover dados entre registradores e a memria.
Aocontrrio, oVAX-11 acomodava operandos de registrador e de memria; muitas
operaes eram admitidas nas formas de dois e de trs endereos.
As mquinas RISC simplificavam a seleo de instrues. E ofereciam menos
formas de implementar determinada operao, e tinham menos restries sobre
ousoderegistrador. Porm, suas arquiteturas load-store aumentavam a importncia
da alocao de registradores.
Ao contrrio, as mquinas CISC tinham operaes que encapsulavam funcionalidade
mais complexa em uma nica operao. Para fazer uso eficaz dessas operaes,
oseletor de instruo precisa reconhecer padres maiores sobre fragmentos de
cdigo maiores, o que aumenta a importncia da seleo sistemtica deinstrues; as
tcnicas automatizadas descritas neste captulo so mais importantes paramquinas
CISC, mas igualmente aplicveis s mquinas RISC.

O advento de ferramentas para gerar grandes bibliotecas de padres necessrios para


descrever o conjunto de instrues de um processador tornou a otimizao peephole
uma tecnologia competitiva para a seleo de instrues. Um detalhe final simplifica
ainda mais este quadro. Se o compilador j usa a LLIR para otimizao, ento no
precisa de um expansor explcito. De modo semelhante, se o compilador otimizou a

524 CAPTULO 11 Seleo de instrues

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.

11.6 Tpicos avanados 525

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?

11.6 TPICOS AVANADOS


Seletores de instruo, tanto baseados em BURS quanto em peephole, tm sido projetados para eficincia em tempo de compilao. Porm, as duas tcnicas so limitadas
pelo conhecimento contido nos padres que o construtor de compiladores fornece. Para
encontrar as melhores sequncias de instruo, este construtor de compiladores poderia
considerar o uso de tcnicas de busca. A ideia simples. Combinaes de instrues s
vezes tm efeitos surpreendentes. Como os resultados so inesperados, elas raramente
so previstas por um construtor de compiladores e, portanto, no esto includas na
especificao produzida para uma mquina-alvo.
Duas abordagens distintas que usam a pesquisa exaustiva para melhorar a seleo de instrues apareceram na literatura. A primeira envolve um sistema baseado em peephole
que descobre e otimiza novos padres medida que compila o cdigo. A segunda,
envolve uma pesquisa de fora bruta no espao de possveis instrues.

11.6.1 Aprendizado de padres peephole


Uma questo importante que surge na implementao ou uso de um otimizador peephole o compromisso entre o tempo gasto especificando o conjunto de instrues da
mquina-alvo e a velocidade e qualidade do otimizador ou seletor de instrues resultante. Com um conjunto de padres completo, o custo da simplificao e casamento
pode ser mantido a um mnimo usando uma tcnica eficiente de casamento de padres.
Naturalmente, algum precisa gerar todos esses padres. Por outro lado, os sistemas
que interpretam as regras durante a simplificao ou casamento tm um overhead maior
por operao da LLIR. Este sistema pode operar com um conjunto de regras muito
menor, o que torna o sistema mais fcil de criar. Porm, o simplificador e algoritmo de
casamento de padres resultantes so executados mais lentamente.
Um modo efetivo de gerar a tabela explcita de padres necessria para um otimizador
rpido, de casamento de padres peephole, emparelh-lo com um otimizador que
tenha um simplificador simblico. Neste esquema, o simplificador simblico registra
todos os padres que simplifica. Toda vez que simplifica um par de operaes, registra o
par inicial e o par simplificado. Depois, pode registrar o padro resultante numa tabela
de pesquisa para produzir um otimizador de casamento de padres rpido.
Executando o simplificador simblico sobre um conjunto de aplicaes de treinamento,
o otimizador pode descobrir a maior parte dos padres de que precisa. Depois, o
compilador pode usar a tabela como base de um otimizador de casamento de padres
rpido. Isto permite que o construtor de compiladores gaste tempo de computao
durante o projeto para acelerar o uso rotineiro do compilador, diminuindo bastante a
complexidade dos padres que devem ser especificados.
Aumentar a interao entre os dois otimizadores pode melhorar ainda mais a qualidade do
cdigo. Em tempo de compilao, o casamento de padres rpido encontrar alguns pares

O simplificador precisa verificar um padro proposto


em comparao com a descrio de mquina
paragarantir que a simplificao proposta seja
amplamente aplicvel.

526 CAPTULO 11 Seleo de instrues

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.

11.6.2 Gerao de sequncias de instrues


A tcnica de aprendizagem tem um vis inerente: considera que os padres de baixo
nvel devem orientar a busca por uma sequncia de instrues equivalente. Alguns
compiladores tm tomado uma abordagem exaustiva para o mesmo problema bsico.
Ao invs de tentar sintetizar a sequncia de instrues desejada a partir de um modelo
de baixo nvel, adotam uma abordagem do tipo gerar-e-testar.
A ideia simples. O compilador, ou construtor de compiladores, identifica uma curta
sequncia de instrues em linguagem assembly que deve ser melhorada. O compilador,
ento, gera todas as sequncias em linguagem assembly de custo um, substituindo os
argumentos originais na sequncia gerada. Testa cada uma para determinar se tem o mesmo
efeito da sequncia-alvo. Quando tiver exaurido todas as sequncias de determinado custo,
ele incrementa o custo das sequncias e continua. Esse processo continua at ele (1) encontrar
uma sequncia equivalente, (2) atingir o custo da sequncia alvo original, ou (3) alcanar um
limite imposto externamente sobre o custo ou o tempo de compilao.
Embora esta tcnica seja inerentemente dispendiosa, o mecanismo usado para testar
a equivalncia tem forte impacto sobre o tempo exigido para testar cada sequncia
candidata. Uma tcnica formal, usando um modelo de baixo nvel de efeitos de mquina,
claramente necessrio para filtrar divergncias sutis, mas um teste mais rpido pode
capturar as divergncias mais grosseiras que ocorrem com mais frequncia. Se o
compilador simplesmente gerar e executar a sequncia candidata, poder comparar
os resultados com aqueles obtidos a partir da sequncia-alvo. Esta tcnica simples,
aplicada a algumas entradas bem conhecidas, deve eliminar a maioria das sequncias
candidatas no aplicveis com um teste de baixo custo.
Esta tcnica, obviamente, muito dispendiosa para ser usada rotineiramente ou para
fragmentos de cdigo grandes. Em algumas circunstncias, porm, ela merece considerao. Se o construtor de aplicaes ou o compilador puderem identificar uma
pequena seo de cdigo com desempenho crtico, os ganhos de uma sequncia de
cdigo excelente podem justificar o custo da busca exaustiva. Por exemplo, em algumas
aplicaes embutidas, o cdigo com desempenho crtico consiste em um nico lao

11.7 Resumo e perspectiva 527

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.

11.7 RESUMO E PERSPECTIVA


Em seu ncleo, a seleo de instrues um problema de casamento de padres. A
dificuldade da seleo de instrues depende do nvel de abstrao da IR do compilador,
da complexidade da mquina alvo e da qualidade do cdigo desejado do compilador.
Em alguns casos, uma tcnica simples de percurso em rvore produzir resultados
adequados. Entretanto, para casos mais difceis, a busca sistemtica realizada pelo
casamento de padres de rvore ou pela otimizao peephole pode gerar resultados melhores. A criao manual de um gerador de cdigo de percurso em rvore que consegue
os mesmos resultados exigiria muito mais trabalho. Embora essas duas tcnicas sejam
diferentes em quase todos os seus detalhes, elas compartilham uma viso comum o
uso do casamento de padres para encontrar uma boa sequncia de cdigo entre as
milhares de sequncias possveis para qualquer programa IR dado.
Os algoritmos de casamento de padres de rvore descobrem ladrilhamentos de baixo
custo usando a escolha de menor custo em cada ponto de deciso. O cdigo resultante implementa a computao especificada pelo programa IR. Os transformadores
peephole simplificam sistematicamente o programa IR e procuram casar o que restar
a um conjunto de padres para a mquina-alvo. Por no possurem modelos de custo
explcitos, nenhum argumento pode ser dado sobre sua otimalidade. Eles geram cdigo
para uma computao com os mesmos efeitos do programa em IR, ao invs de uma
implementao literal do programa IR. Devido a esta distino sutil nas duas tcnicas,
no podemos comparar diretamente as afirmaes sobre sua qualidade. Na prtica,
resultados excelentes tm sido obtidos com cada uma delas.
Os benefcios prticos dessas tcnicas tm sido demonstrados em compiladores reais.
Tanto LCC quanto GCC so executados em muitas plataformas. O primeiro usa o
casamento de padres de rvore; o segundo, um transformador peephole. O uso de
ferramentas automatizadas nos dois sistemas os tornou fceis de entender, de redirecionar e, por fim, bastante aceitos na comunidade.
Igualmente importante, o leitor deve reconhecer que as duas famlias de casamentos de
padres automticos podem ser aplicadas a outros problemas na compilao. A otimizao peephole originou-se como uma tcnica para melhorar o cdigo final produzido
por um compilador. De modo semelhante, o compilador pode aplicar o casamento de
padres de rvore para reconhecer e reescrever computaes em uma AST. A tecnologia
BURS pode fornecer um modo particularmente eficaz de reconhecer e melhorar padres
simples, incluindo as identidades algbricas reconhecidas pela numerao de valor.

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

528 CAPTULO 11 Seleo de instrues

seu repertrio limitado de operaes [356]. Os pequenos conjuntos de instrues dos


primeiros computadores e minicomputadores deixavam os pesquisadores e construtores
de compilador ignorar alguns dos problemas que surgem nas mquinas modernas.
Por exemplo, Sethi e Ullman [311] e, mais tarde, Aho e Johnson [5], consideraram o
problema de gerar cdigo timo para rvores de expresso. Aho, Johnson e Ullman
estenderam suas ideias para DAGs de expresso [6]. Os compiladores baseados nesse
trabalho usavam mtodos ad hoc para as estruturas de controle e algoritmos inteligentes
para rvores de expresso.
No final da dcada de 1970, duas tendncias distintas na arquitetura trouxeram o
problema de seleo de instrues para a vanguarda da pesquisa em compiladores.
A passagem de arquiteturas de 16 para 32 bits precipitou uma exploso no nmero
de operaes e modos de endereo que o compilador tinha que considerar. Para um
compilador explorar at mesmo uma grande frao das possibilidades, precisava de
uma abordagem mais formal e poderosa. Ao mesmo tempo, o sistema operacional Unix
que nascia comeou a aparecer em diversas plataformas, o que fez surgir uma demanda
natural por compiladores C e aumentou o interesse em compiladores redirecionveis
[206]. A capacidade de redirecionar facilmente o seletor de instrues desempenha
um papel importante para determinar a facilidade de transportar um compilador para
novas arquiteturas. Essas duas tendncias iniciaram um alvoroo de pesquisa sobre
seleo de instrues, que iniciou na dcada de 1970 e continuou at os anos 1990 [71,
72,132,160,166,287,288].
O sucesso da automao na anlise lxica e sinttica tornou a seleo de instrues orientada por especificao uma ideia atraente. Glanville e Graham mapearam o casamento de
padres da seleo de instrues na anlise sinttica controlada por tabela [160,165,167].
Ganapathi e Fischer atacaram o problema com gramticas de atributo [156].
Os geradores de cdigo de casamento de padres de rvore cresceram a partir do trabalho
inicial sobre gerao de cdigo orientado por tabela [9,42,167,184,240] e no casamento de
padres de rvore [76,192]. Pelegr-Llopart formalizou muitas dessas noes na teoria
de BURS [281]. Autores subsequentes basearam-se nesse trabalho para criar uma srie de
implementaes, variaes e algoritmos de gerao de tabela [152,153,288]. O sistema
Twig combinava o casamento de padres de rvore com a programao dinmica [2,334].
O primeiro otimizador peephole parece ser o sistema de McKeeman [260]. Bagwell
[30], Wulf e outros [356] e Lamb [237] descrevem os primeiros sistemas peephole. O
ciclo de expanso, simplificao e casamento, descrito na Seo 11.5.1, vem do trabalho de Davidson [115,118]. Kessler tambm trabalhou na obteno de otimizadores
peephole diretamente a partir de descries de baixo nvel das arquiteturas alvo [222].
Fraser e Wendt adaptaram a otimizao peephole para realizar a gerao de cdigo
[154,155]. A tcnica de aprendizagem de mquina descrita na Seo11.6.1 foi descrita
por Davidson e Fraser [116].
Massalin props a abordagem exaustiva descrita na Seo11.6.2 [258]. Ela foi aplicada
de um modo limitado no GCC por Granlund e Kenner [170].

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.

11.7 Resumo e perspectiva 529

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

532 CAPTULO 12 Escalonamento de instrues

regies de cdigo maiores do que os blocos bsicos; esses escalonadores regionais e de


lao simplesmente criam condies em que o compilador pode aplicar o escalonamento
de lista a uma sequncia maior de operaes.

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.

Microprocessadores comerciais normalmente possuem operaes com diferentes


latncias. Os valores tpicos podem ser um ciclo para adio ou subtrao de inteiros,
trs para multiplicao de inteiros ou adio ou subtrao de ponto flutuante, cinco
para multiplicao de ponto flutuante, 12 a 18 para diviso de ponto flutuante, e 20
a 40 para diviso de inteiros. Como uma complicao adicional, algumas operaes
tm latncias variveis. A latncia de um load depende de onde na hierarquia de
memria ele encontra o valor; essas latncias podem variar desde alguns ciclos,
digamos, um a cinco para a cache mais prxima, at dezenas ou centenas de ciclos
para valores na memria principal. As operaes aritmticas tambm podem ter
latncias variveis. Por exemplo, as unidades de multiplicao e diviso de ponto
flutuante podem tomar uma sada antecipada quando reconhecem que os operandos
reais tornam alguns estgios do processamento irrelevantes (por exemplo, multiplicao por zero ou um).
Para complicar as coisas ainda mais, muitos processadores comerciais tm a propriedade de poder iniciar a execuo de mais de uma operao em cada ciclo. Os
chamados processadores superescalares exploram o paralelismo no nvel de instruo
operaes independentes que podem ser executadas simultaneamente sem conflito.
Em ambiente superescalar, a tarefa do escalonador manter as unidades funcionais
ocupadas o mximo possvel. Como o hardware de despacho de instruo tem uma
quantidade limitada de antecipao (lookahead), o escalonador pode ter de prestar
ateno ao ciclo em que cada operao emitida e a ordenao relativa das operaes
dentro de cada ciclo.
Considere, por exemplo, um processador simples com uma unidade funcional de inteiros e outra de ponto flutuante. O compilador deseja escalonar um lao que consiste
em 100 operaes de inteiros e 100 operaes de ponto flutuante. Se o compilador
ordenar as operaes de modo que as 75 primeiras sejam de inteiros, a unidade de
ponto flutuante ficar ociosa at que o processador finalmente alcance algum trabalho
para ela. Se todas as operaes forem independentes (uma suposio irreal), a melhor
ordem poderia ser alternar operaes entre as duas unidades.

12.1 Introduo 533

Informalmente, escalonamento de instrues o processo pelo qual um compilador


reordena as operaes no cdigo compilado em uma tentativa de diminuir seu tempo
de execuo. Conceitualmente, um escalonador de instruo se parece com

O escalonador de instrues usa como entrada uma lista de instrues parcialmente


ordenada; e produz como sada uma lista ordenada de instrues construdas a partir do
mesmo conjunto de operaes. O escalonador considera um conjunto fixo de operaes,
no reescreve o cdigo (alm de incluir nops para manter a execuo correta), e
considera uma alocao fixa de valores para registradores; embora podendo renomear
registradores, ele no muda as decises de alocao.

MEDIO DE DESEMPENHO EM TEMPO DE EXECUO


O objetivo principal do escalonamento de instrues melhorar o tempo de execuo
do cdigo gerado. As discusses sobre desempenho usam muitas e diferentes
mtricas; as duas mais comuns so:
Instrues por segundo. Em geral, a mtrica utilizada para anunciar computadores
e comparar o desempenho de sistemas o nmero de instrues executadas em um
segundo, que pode ser medido como instrues emitidas ou retiradas por segundo.
Tempo para completar uma tarefa fixa. Esta mtrica usa um ou mais programas
cujo comportamento conhecido e compara o tempo exigido para completar essas
tarefas fixas. Esta tcnica, chamada benchmarking, fornece informaes sobre o
desempenho geral do sistema, tanto em hardware quanto em software, sobre uma
particular carga de trabalho.
Nenhuma mtrica isolada contm informaes suficientes para permitir a avaliao
daqualidade de cdigo gerada pelo back end do compilador. Por exemplo, se a
medida for instrues por segundo, o compilador obtm crdito extra por deixar
informaes irrelevantes (porm independentes) no cdigo? A simples mtricade
temporizao no fornece informaes sobre o que alcanvel para um
determinado programa. Assim, ela permite que um compilador funcione melhor
doque outro, mas no mostra a distncia entre o cdigo gerado e o cdigo timo
para a mquina-alvo.
Os nmeros que o construtor de compiladores pode querer medir incluem
aporcentagem de instrues executadas cujos resultados so realmente usados
eaporcentagem de ciclos gastos em adiamentos e interbloqueios. A primeira d ideia
sobre alguns aspectos da execuo predicada; a segunda, mede diretamente alguns
aspectos da qualidade do escalonamento.

O escalonador de instrues tem trs objetivos principais. Primeiro, precisa preservar o


significado do cdigo que recebe como entrada. Segundo, deve minimizar o tempo de
execuo, evitando adiamentos ou nops. Terceiro, evitar aumentar os tempos de vida
de valor alm do ponto onde derramamentos de registrador adicionais so necessrios.
Naturalmente, o escalonador deve operar de modo eficiente.
Muitos processadores podem emitir vrias operaes por ciclo. Embora os mecanismos variem entre arquiteturas, o desafio bsico para o escalonador o mesmo: fazer boa
utilizao dos recursos de hardware. Em um processador com palavra de instruo muito
longa (VLIW Very Long Instruction Word), o processador emite uma operao para cada

534 CAPTULO 12 Escalonamento de instrues

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.

12.2 O PROBLEMA DO ESCALONAMENTO DE INSTRUES


Considere o pequeno cdigo de exemplo mostrado na Figura12.1, que reproduz um
exemplo usado na Seo 1.3. A coluna rotulada como Incio mostra o ciclo em que
cada operao inicia a execuo. Suponha que o processador tenha uma nica unidade
funcional, loads e stores usem trs ciclos, uma multiplicao use dois ciclos e todas

FIGURA 12.1 Bloco de exemplo do Captulo1.

12.2 O problema do escalonamento de instrues 535

as outras operaes sejam completadas em um nico ciclo. Com essas suposies, o


cdigo original, mostrado esquerda, usa 22 ciclos.
O cdigo escalonado, na Figura12.1b, executado em muito menos ciclos. Ele separa
operaes de longa latncia das operaes que referenciam seus resultados. Essa
separao permite que operaes que no dependam desses resultados sejam executadas
simultaneamente com as de longa latncia. O cdigo emite operaes load nos trs
primeiros ciclos; os resultados esto disponveis nos ciclos 4, 5 e 6, respectivamente.
Esse escalonamento exige um registrador extra, r3, para manter o resultado da terceira
operao load em execuo simultnea, mas permite que o processador realize um trabalho til enquanto espera que o primeiro operando aritmtico chegue. A sobreposio
de operaes efetivamente oculta a latncia das operaes da memria. A mesma ideia,
aplicada pelo bloco, oculta a latncia da operao mult. A reordenao reduz o tempo
de execuo para 13 ciclos, uma melhoria de 41%.
Todos os exemplos que vimos at aqui tratam, implicitamente, com uma mquina-alvo
que emite uma nica operao em cada ciclo. Quase todos os processadores comerciais possuem vrias unidades funcionais e emitem vrias operaes em cada ciclo.
Apresentaremos o algoritmo de escalonamento de lista para uma mquina de emisso
nica e explicaremos como estender o algoritmo bsico para lidar com instrues de
mltiplas operaes.
O problema de escalonamento de instrues definido sobre o grafo de dependncia
D de um bloco bsico. D, por vezes, chamado grafo de precedncia. As arestas em D
representam o fluxo de valores no bloco. Adicionalmente, cada n tem dois atributos,
tipo de operao e atraso. Para um n n, a operao correspondente a n deve ser
executada em uma unidade funcional especificada por seu tipo de operao; ela exige
delay(n) ciclos para concluir. A Figura12.2b mostra o grafo de dependncia para o
cdigo em nosso exemplo em andamento. Substitumos nmeros concretos por @a,
@b, @c e @d, para evitar confuso com os rtulos usados para identificar operaes.

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.

Os ns sem predecessores em D, como a, c, e e g no exemplo, so chamados folhas do


grafo. Como as folhas no dependem de outras operaes, podem ser escalonadas o
mais cedo possvel. Os ns sem sucessores em D, como i no exemplo, so chamados
razes do grafo. As razes so, de certa forma, os ns mais restritos no grafo, pois no
podem ser executadas at que todos os seus ancestrais o tenham sido. Com essa terminologia, parece que desenhamos D de cabea para baixo pelo menos com relao
s rvores, ASTs e DAGs usados anteriormente neste livro. Porm, colocar as folhas
no topo da figura cria uma correspondncia aproximada entre os posicionamentos no
desenho e o posicionamento eventual no cdigo escalonado. Uma folha est no topo
da rvore porque pode ser executada mais cedo no escalonamento. Uma raiz est na
parte de baixo da rvore porque deve ser executada aps cada um de seus ancestrais.

D no uma rvore, mas uma floresta de DAGs. Assim,


os ns podem ter vrios pais, e D vrias razes.

Dado um grafo de dependncia D para um fragmento de cdigo, um escalonamento S


mapeia cada n n N a um inteiro no negativo que indica o ciclo em que dever ser
emitido, supondo que a primeira operao seja emitida no ciclo 1. Isso fornece uma
definio clara e concisa de uma instruo, a saber, a i-sima instruo o conjunto de
operaes {n | S(n)=i}. O escalonamento precisa atender a trs restries.
1. S(n) 1, para cada n N. Esta restrio probe operaes que so emitidas
antes que a execuo seja iniciada. O escalonamento que viola esta restrio no
bem formado. Por questo de uniformidade, o escalonamento tambm precisa
ter pelo menos uma operao n com S(n)=1.
2. Se (n1, n2) E, ento S(n1)+delay(n1)S(n2). Esta restrio garante
oresultado correto. Uma operao no pode ser emitida at que seus operandos

536 CAPTULO 12 Escalonamento de instrues

FIGURA 12.2 Grafo de dependncia para o exemplo.

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:

Se considerarmos que delay captura todas as latncias operacionais, o escalonamento S


dever ser executado no tempo L(S). Com a noo de tamanho de escalonamento vem
a noo de um escalonamento de tempo timo. Um escalonamento Si de tempo timo
se L(Si)L(S j) para todos os outros Sj que contm o mesmo conjunto de operaes.
O grafo de dependncia captura propriedades importantes do escalonamento. O clculo
do atraso total ao longo dos caminhos pelo grafo expe detalhes adicionais sobre o
bloco. A incluso de anotaes no grafo de dependncia D para nosso exemplo com
informaes sobre latncia cumulativa gera o grafo mostrado na margem. O tamanho
do caminho de um n at o final da computao aparece como um sobrescrito no n.
Os valores claramente mostram que o caminho abdfhi o mais longo o caminho
crtico que determina o tempo de execuo geral para este exemplo.
Caminho crtico
Caminho de latncia mais longa por meio de um grafo
dedependncia.

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.

12.2 O problema do escalonamento de instrues 537

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

As operaes mult precisam de dois ciclos cada. A cadeia de dependncias, da


figura a seguir, entre as multiplicaes impede que o escalonador melhore o cdigo.
(Se outras operaes independentes estiverem disponveis, o escalonador poderia
coloc-las entre as multiplicaes.

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.

538 CAPTULO 12 Escalonamento de instrues

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.

Entretanto, os relacionamentos de dependncia so expressos de forma no ambgua


no cdigo, que no contm antidependncias, de modo que restries de nomeao
no podero surgir.

12.2.1 Outras medidas de qualidade de escalonamento


Escalonamentos podem ser medidos em termos que no sejam o tempo. Dois escalonamentos Si e Sj para o mesmo bloco poderiam produzir demandas diferentes por
registradores ou seja, o nmero mximo de valores vivos em Sj pode ser menor que
em Si. Se o processador exigir que o escalonador insira nops para unidades funcionais
ociosas, ento Si poderia ter menos operaes que Sj, e buscar menos instrues como
resultado. Isto no precisa depender unicamente do tamanho do escalonamento. Por
exemplo, em um processador com um nop de ciclo varivel, junt-los produz menos operaes e, potencialmente, menos instrues. Finalmente, Sj poderia exigir menosenergia
do que Si para ser executado no sistema-alvo, porque nunca usa uma das unidades
funcionais, busca menos instrues ou causa menos transies de bit na lgica de busca
e decodificao do processador.

12.2 O problema do escalonamento de instrues 539

INTERAES ENTRE ESCALONAMENTO E ALOCAO


Antidependncias entre operaes podem limitar a capacidade do escalonador
dereordenar operaes. O escalonador pode evitar antidependncias pela
renomeao; porm, a renomeao cria uma necessidade do compilador realizar
alocao de registradores aps o escalonamento. Este exemplo apenas uma das
interaes entre escalonamento de instrues e alocao de registradores.
A funo bsica do escalonador reordenar operaes. Como a maioria das operaes
utiliza e define valores, mudar a ordem relativa de duas operaes x e y pode mudar
os tempos de vida dos valores. Mover y de baixo de x para cima de x estende o tempo
devida do valor que y define. Se um dos operandos de x for um ltimo uso, mover x
para baixo de y estende seu tempo de vida. Simetricamente, se um dos operandos
dey for um ltimo uso, mover y para cima de x encurta seu tempo de vida.
O efeito final da reordenao de x e y depende dos detalhes de ambos, alm do cdigo ao
redor. Se nenhum dos usos envolvidos for um ltimo uso, a troca no tem qualquer efeito sobre
a demanda por registradores. (Cada operao define um registrador; sua troca muda ostempos
de vida de registradores especficos, mas no a demanda agregada por registradores.)
De modo semelhante, a alocao de registradores pode mudar o problema
deescalonamento de instrues. As funes bsicas de um alocador de registradores
so renomear referncias e inserir operaes de memria quando a demanda por
registradores for maior do que o conjunto de registradores. Essas duas funes
afetam a capacidade do escalonador de produzir cdigo rpido. Quando o alocador
mapeia um espao de nome virtual grande para o espao de nome dos registradores
damquina-alvo, pode introduzir antidependncias que restringem o escalonador.
Demodo semelhante, quando o alocador insere cdigo de derramamento, acrescenta
operaes ao cdigo que, por si s, devem ser escalonadas em instrues.
Sabemos, matematicamente, que a soluo conjunta desses problemas poderia
produzir solues que no podem ser obtidas executando o escalonador seguido pelo
alocador, ou vice-versa. Porm, os dois problemas so complexos o suficiente para que
a maioria dos compiladores do mundo real os trate separadamente.

12.2.2 O que torna o escalonamento difcil?


A operao fundamental no escalonamento reunir operaes em grupos com base no
ciclo em que elas iniciaro sua execuo. Para cada operao, o escalonador precisa
escolher um ciclo; e para cada ciclo, um conjunto de operaes. Balanceando esses
dois pontos de vista, ele deve garantir que cada operao seja emitida somente quando
seus operandos estiverem disponveis.
Quando o escalonador coloca uma operao i no ciclo c, essa deciso afeta o posicionamento mais cedo possvel de qualquer operao que conta com o resultado de i
qualquer operao em D que seja alcanvel a partir de i. Se mais de uma operao
puder legalmente ser executada no ciclo c, a escolha do escalonador pode mudar o
posicionamento mais cedo de muitas operaes todas essas operaes dependem
(direta ou transitivamente) de cada uma das escolhas possveis.
O escalonamento de instrues local NP-completo para todas as arquiteturas, exceto
para as mais simples. Na prtica, os compiladores produzem solues aproximadas para
problemas de escalonamento usando uma heurstica gulosa. Quase todos os algoritmos de
escalonamento usados nos compiladores so baseados em uma nica famlia de tcnicas
heursticas, chamadas escalonamento de lista. A prxima seo descreve o escalonamento
de lista em detalhes. Sees subsequentes mostram como estender o paradigma para escopos maiores.

540 CAPTULO 12 Escalonamento de instrues

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 ESCALONAMENTO DE LISTA LOCAL


O escalonamento de lista uma abordagem heurstica, gulosa, para o escalonamento
das operaes em um bloco bsico. Ele tem sido o paradigma dominante para escalonamento de instrues desde o final da dcada de 1970, em grande parte porque
descobre escalonamentos razoveis e se adapta facilmente a mudanas nas arquiteturas
de computador. Porm, o escalonamento de lista uma tcnica, no um algoritmo especfico. Existem muitas variaes no modo como ele implementado e como tenta
priorizar instrues para escalonamento. Esta seo explora o framework bsico do
escalonamento de lista, alm de algumas das variaes sobre a ideia.

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

12.3 Escalonamento de lista local 541

o escalonador encontre alguns escalonamentos que as antidependncias teriam


impedido e simplifica a implementao do escalonador.
2. Montar um grafo de dependncia, D. Para montar este grafo, o escalonador
percorre o bloco de baixo para cima. Para cada operao, constri um n para representar o valor recm-criado. Acrescenta arestas desse n para cada n que usa
ovalor. Cada aresta anotada com a latncia da operao atual. (Se o escalonador
no realizar a renomeao, D tambm dever representar as antidependncias.)
3. Atribuir prioridades a cada operao. O escalonador usa essas prioridades como
um guia para quando for escolher a partir do conjunto de operaes disponveis
em cada etapa. Muitos esquemas de prioridade tm sido usados nos escalonadores de lista. O escalonador pode calcular vrios escores diferentes para cada
n,usando um como ordenao principal e os outros para desempates entre
ns com classificao igual. Um esquema de prioridade clssico usa o tamanho
docaminho ponderado pela latncia mais longo, do n at a raiz de D. Outros
esquemas de prioridade so descritos na Seo 12.3.4.
4. Selecionar iterativamente uma operao e escalon-la. Para escalonar operaes,
o algoritmo comea no primeiro ciclo do bloco e escolhe o mximo de operaes
possvel para emitir nesse ciclo. Depois, incrementa seu contador de ciclos,
atualiza sua noo de quais operaes esto prontas para ser executadas e escalona o prximo ciclo. E repete este processo at que cada operao tenha sido
escalonada. O uso inteligente de estruturas de dados torna este processo eficiente.
Renomear e montar D so etapas simples. Computaes de prioridade tpicas percorrem o
grafo de dependncia D e calculam alguma mtrica sobre ele. O ncleo do algoritmo, e a chave
para entend-lo, est na etapa final o algoritmo de escalonamento. A Figura12.3 mostra
o framework bsico para esta etapa, supondo que o alvo tenha uma nica unidade funcional.
O algoritmo de escalonamento realiza uma simulao abstrata da execuo do bloco; ignora os detalhes dos valores e operaes para focalizar as restries de tempo impostas

FIGURA 12.3 Algoritmo de escalonamento de lista.

542 CAPTULO 12 Escalonamento de instrues

pelas arestas em D. Para restrear o tempo, ele mantm um relgio de simulao na


varivel Cycle. Inicializa Cycle em 1 e a incrementa enquanto prossegue pelo bloco.
O algoritmo usa duas listas para rastrear operaes. A lista Ready mantm todas as
operaes que podem ser executadas no ciclo atual; se uma operao estiver em Ready,
todos os seus operandos foram calculados. Inicialmente, Ready contm todas as folhas de
D, pois elas no dependem de outras operaes no bloco. A lista Active mantm todas
as operaes que foram emitidas em um ciclo anterior mas ainda no terminaram. Toda
vez que o escalonador incrementa Cycle, remove de Active qualquer operao op que
termina antes de Cycle. Depois, verifica cada sucessor de op em D para determinar se
pode passar para a lista Ready ou seja, se todos os seus operandos esto disponveis.
O algoritmo de escalonamento de lista segue uma disciplina simples. Em cada etapa de
tempo, considera quaisquer operaes completadas no ciclo anterior, escalona uma operao
para o ciclo atual e incrementa Cycle. O processo termina quando o relgio simulado
indica que cada operao foi completada. Se todos os tempos especificados pelo delay forem
precisos e todos os operandos das folhas de D estiverem disponveis no primeiro ciclo, esse
tempo de execuo simulado deve corresponder ao de execuo real. Uma ps-passagem
simples pode rearrumar as operaes e inserir nops conforme a necessidade.
O algoritmo deve respeitar uma restrio final. Qualquer final de bloco ou salto deve ser escalonado de modo que o contador de programa no mude antes que o bloco termine. Assim,
se i for o desvio de final de bloco, ele no poder ser escalonado antes do cicloL(S)+1
delay(i). Assim, um desvio de nico ciclo deve ser escalonado no ltimo ciclo do
bloco, e um de dois ciclos no dever ser escalonado antes do penltimo ciclo no bloco.
A qualidade do escalonamento produzido por este algoritmo depende principalmente
do mecanismo usado para escolher uma operao da fila Ready. Considere o cenrio
mais simples, em que a lista Ready contm no mximo um item em cada iterao.
Neste caso restrito, o algoritmo precisa gerar um escalonamento timo. Somente
uma operao pode ser executada no primeiro ciclo. (Deve haver pelo menos uma
folha em D, e nossa restrio garante que haja exatamente uma.) Em cada ciclo
subsequente, o algoritmo no tem escolhas a fazer ou Ready contm uma operao e o algoritmo a escalona, ou Ready est vazio e ele no escalona nada para
ser emitido nesse ciclo. A dificuldade surge quando, em algum ciclo, a fila Ready
contm mltiplas operaes.
Quando o algoritmo tiver que escolher entre vrias operaes prontas, essa escolha crtica.
O algoritmo deve tomar a operao com o maior escore de prioridade. No caso de um empate,
deve usar um ou mais outros critrios para desempatar (ver Seo12.3.4). A mtrica sugerida
anteriormente, distncia ponderada pela latncia mais longa at uma raiz em D, corresponde
a sempre escolher o n no caminho crtico para o ciclo atual no escalonamento que est sendo
construdo. At o ponto em que o impacto de uma prioridade de escalonamento previsvel,
esse esquema deve fornecer uma busca balanceada pelos caminhos mais longos.

12.3.2 Escalonamento de operaes com atrasos variveis


Operaes de memria normalmente tm atrasos incertos e variveis. Uma operao
load em uma mquina com vrios nveis de memria cache poderia ter um atraso real
variando de zero a centenas ou milhares de ciclos. Se o escalonador considerar o atraso
de pior caso, arrisca deixar o processador ocioso por longos perodos. Se considerar
o atraso de melhor caso, ir parar processador em uma falha de cache. Na prtica, o
compilador pode obter bons resultados calculando uma latncia individual para cada
load com base na quantidade de paralelismo em nvel de instruo disponvel para cobrir
a latncia do load. Essa tcnica, chamada escalonamento balanceado, escalona o load

12.3 Escalonamento de lista local 543

FIGURA 12.4 Clculo de atrasos para operaes load.

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.

12.3.3 Estendendo o algoritmo


O algoritmo de escalonamento de lista, conforme apresentado, faz vrias suposies que
podem no ser verdadeiras na prtica. O algoritmo supe que somente uma operao
pode ser emitida por ciclo; a maioria dos processadores pode emitir vrias operaes
por ciclo. Para lidar com esta situao, temos que expandir o lao while de modo que
procure uma operao para cada unidade funcional em cada ciclo. A extenso inicial
simples o construtor de compiladores pode acrescentar um lao que percorre as
unidades funcionais.
A complexidade surge quando algumas operaes podem ser executadas sobre mltiplas
unidades funcionais e outras no. O construtor de compiladores pode ter que escolher
uma ordem para as unidades funcionais que escalone primeiro as unidades mais restritas e depois as unidades menos restritas. Em um processador com um conjunto de
registradores particionado, o escalonador pode ter que colocar uma operao na partio
onde seus operandos residem ou escalon-la para um ciclo em que o aparato de transferncia entre parties est ocioso.
Em fronteiras de bloco, o escalonador precisa considerar o fato de que alguns operandos
calculados nos blocos predecessores podem no estar disponveis no primeiro ciclo.

544 CAPTULO 12 Escalonamento de instrues

Se o compilador invocar o escalonador de lista sobre os blocos em ps-ordem reversa


do CFG, ele pode garantir que o escalonador sabe quantos ciclos no bloco deve esperar pelos operandos que entram no bloco ao longo das arestas diretas no CFG. (Esta
soluo no ajuda com um desvio de fechamento de lao; veja a Seo12.5 para obter
uma discusso do escalonamento de lao.)

12.3.4 Desempate no algoritmo de escalonamento de lista


A complexidade do escalonamento de instrues faz com que os construtores de
compilador usem tcnicas heursticas relativamente pouco dispendiosas variantes
do algoritmo de escalonamento de lista , ao invs de resolver o problema at a
otimalidade. Na prtica, o escalonamento de lista produz bons resultados; ele normalmente constri escalonamentos timos ou quase timos. Porm, assim como em
muitos algoritmos gulosos, seu comportamento no robusto pequenas mudanas
na entrada podem fazer grandes diferenas na soluo.
A metodologia usada para desempatar tem forte impacto sobre a qualidade dos
escalonamentos produzidos pelo escalonamento de lista. Quando dois ou mais
itens tm a mesma avaliao, o escalonador deve desempatar com base em outra
avaliao de prioridade. Um bom escalonador poderia ter duas ou trs avaliaes
de prioridade de desempate para cada operao, aplicando-as em alguma ordem
consistente. Alm do tamanho do caminho ponderado pela latncia, j descrito, o
escalonador pode usar:
j

A avaliao de um n o nmero de sucessores imediatos que ele tem em D.


Esta mtrica encoraja o escalonador a buscar muitos caminhos distintos por
meio dografo mais prximo de uma tcnica de busca em largura. Isto tende
amanter mais operaes na fila Ready.
j A avaliao de um n o nmero total de descendentes que ele tem em D.
Estamtrica amplifica o efeito da avaliao anterior. Os ns que calculam
valores crticos para muitos outros ns so escalonados mais cedo.
j A avaliao de um n igual ao seu atraso. Esta mtrica escalona operaes
de longa latncia o mais cedo possvel, considerando-as no bloco quando ainda
restam muitas operaes que podem ser usadas para cobrir sua latncia.
j A avaliao de um n igual ao nmero de operandos para os quais essa
operao o ltimo uso. Como desempate, esta mtrica move os ltimos usos
para mais perto de suas definies, o que pode diminuir a demanda por registradores.
Infelizmente, nenhum desses esquemas de prioridade domina os outros em termos de
qualidade de escalonamento global. Cada um deles se sobressai em alguns exemplos
e funciona de modo fraco em outros. Assim, existe pouco acordo sobre quais classificaes usar ou em que ordem aplic-las.

12.3.5 Escalonamento de lista para a frente versus para trs


O algoritmo de escalonamento de lista, conforme apresentado na Figura12.3, funciona
pelo grafo de dependncia a partir de suas folhas at suas razes e cria o escalonamento
do primeiro ciclo no bloco at o ltimo. Uma formulao alternativa do algoritmo
funciona sobre o grafo de dependncia na direo oposta, escalonando das razes
para as folhas. A primeira operao escalonada a ltima a ser emitida, e a ltima
operao escalonada a primeira a ser emitida. Esta verso do algoritmo chamada
escalonamento de lista para trs (backward list scheduling), e a verso original, escalonamento de lista para a frente (forward list scheduling).

12.3 Escalonamento de lista local 545

O escalonamento de lista no uma parte dispendiosa da compilao. Assim, alguns


compiladores executam o escalonador vrias vezes com diferentes combinaes
de heursticas e mantm o melhor escalonamento. (O escalonador pode reutilizar
a maior parte do trabalho preparatrio renomeao, construo do grafo de
dependncia e clculo de algumas das prioridades.) Neste esquema, o compilador
deve considerar o uso tanto do escalonamento para a frente como do escalonamento
para trs.
Na prtica, nem o escalonamento para a frente nem o para trs sempre vencem. A
diferena entre eles est na ordem em que o escalonador considera as operaes. Se
o escalonamento depende criticamente da ordenao cuidadosa de algum pequeno
conjunto de operaes, as duas direes podem produzir resultados notavelmente
diferentes. Se as operaes crticas ocorrem perto das folhas, o escalonamento para
a frente parece ser mais provvel de consider-las juntas, enquanto o para trs deve
seguir pelo restante do bloco para alcan-las. Simetricamente, se as operaes crticas
ocorrem perto das razes, o escalonamento para trs pode examin-las juntas, enquanto
o para a frente as v em uma ordem ditada pelas decises feitas a partir da outra ponta
do bloco.
Para tornar este ltimo ponto mais concreto, considere o exemplo mostrado na
Figura12.5. Ele mostra o grafo de dependncia para um bloco bsico encontrado no
programa de benchmark SPEC 95, go. O compilador acrescentou dependncias das
operaes store para o desvio de fim de bloco, a fim de garantir que as operaes
da memria sejam completadas antes que o prximo bloco inicie a execuo.
(Violar esta suposio pode produzir um valor incorreto a partir da operao load
subsequente.) Os sobrescritos nos ns no grafo de dependncia do a latncia a partir
do n at o final do bloco; os subscritos diferenciam entre operaes semelhantes.
O exemplo assume latncias de operao que aparecem na tabela abaixo do grafo
de dependncia.

FIGURA 12.5 Grafo de dependncia para um bloco de go.

Este exemplo demonstra a diferena entre o escalonamento de lista para a frente e


o escalonamento de lista para trs. Ele chamou nossa ateno em um estudo sobre
escalonamento de lista; o compilador estava visando uma mquina ILOC com duas
unidades funcionais de inteiros e uma para realizar operaes de memria. As cinco
operaes store usam a maior parte do tempo do bloco. O escalonamento que minimiza o tempo de execuo deve comear a executar stores o mais cedo possvel.

546 CAPTULO 12 Escalonamento de instrues

O escalonamento de lista para a frente, usando a latncia at o final do bloco


como prioridade, executa as operaes por ordem de prioridade, exceto para a
comparao; e escalona as cinco operaes com avaliao oito, depois as quatro com avaliao sete, e a com avaliao seis. Ele comea nas operaes com
avaliao cinco, e desloca o cmp ao longo dos stores, pois o cmp uma folha.
Se os empates forem resolvidos arbitrariamente tomando a ordem da esquerda
para a direita, chega-se ao escalonamento mostrado na Figura12.6a. Observe que
as operaes de memria comeam no ciclo 5, produzindo um escalonamento
queemite o desvio no ciclo 13.
Usando as mesmas prioridades com o escalonamento de lista para trs, o compilador
primeiro coloca o desvio no ltimo slot do bloco. O cmp o precede em um ciclo,
determinado por delay(cmp). A prxima operao escalonada store1 (pela regra de
desempate da esquerda para a direita). Ele atribudo ao slot de emisso na uni
dade de memria que est quatro ciclos mais cedo, determinado por delay(store). O
escalonador preenche os slots anteriores sucessivamente na unidade de memria com as
outras operaes store, em ordem. Comea preenchendo as operaes com inteiros
medida que se tornarem prontas. A primeira add1, dois ciclos antes de store1.
Quando o algoritmo termina, ter produzido o escalonamento mostrado na Figura12.6b.
O escalonamento para trs usa um ciclo a menos do que o escalonamento para a frente.
Ele coloca o addI antes no bloco, permitindo que store 5 seja emitido no ciclo
4 um ciclo antes da primeira operao de memria no escalonamento para a frente.
Considerando o problema em uma ordem diferente, usando as mesmas prioridades
bsicas e desempates, o algoritmo para trs encontra um resultado diferente.

FIGURA 12.6 Escalonamentos para o bloco dego.

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.

12.3 Escalonamento de lista local 547

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.

12.3.6 Melhorando a eficincia do escalonamento de lista


Para escolher uma operao da lista Ready, conforme descrevemos at aqui, preciso
uma varredura linear sobre Ready. Isso torna o custo de criao e manuteno da
abordagem Ready O(n2). A substituio da lista por uma fila de prioridade pode
reduzir o custo dessas manipulaes para O(n log2 n), custa de um pequeno aumento
na dificuldade de implementao.
Uma tcnica semelhante pode reduzir o custo de manipulao da lista Active. Quando
o escalonador acrescenta uma operao a Active, pode atribuir-lhe uma prioridade
igual ao ciclo em que a operao completada. Uma fila de prioridade que busca
pela prioridade mais baixa empurrar todas as operaes completadas no ciclo atual
paraa frente custa de um pequeno aumento no custo em relao implementao
de lista simples.
Uma melhoria adicional possvel na implementao de Active. O escalonador pode
manter um conjunto de listas separadas, uma para cada ciclo em que uma operao
pode terminar. O nmero de listas exigidas para cobrir todas as latncias de opera
o MaxLatency=maxnD delay(n). Quando o compilador escalona a operao
n em Cycle, acrescenta n a WorkList[(Cycle+delay(n)) mod MaxLatency].
Quando vai atualizar a fila Ready, todas as operaes com sucessores a considerar

548 CAPTULO 12 Escalonamento de instrues

so encontradas em WorkList[Cycle mod MaxLatency]. Este esquema usa uma


pequena quantidade de espao extra; a soma das operaes nas WorkLists a mesma
que na lista Active. As WorkLists individuais tero uma pequena quantidade de
overhead de espao. preciso um pouco mais de tempo em cada insero em uma
WorkList para calcular qual WorkList deve ser usada. Em retorno, isto evita o
custo quadrtico de pesquisar Active, substituindo-o por um percurso linear por
uma WorkList menor.

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?

12.4 ESCALONAMENTO REGIONAL


Assim como a numerao de valor, passar de blocos bsicos isolados para escopos maiores pode melhorar a qualidade do cdigo que o compilador gera. Para
o escalonamento de instrues, muitas tcnicas diferentes foram propostas para
regies maiores do que um bloco, porm menores do que um procedimento inteiro. Quase todas essas tcnicas usam o algoritmo bsico de escalonamento de
lista como mecanismo para reordenar instrues. Elas cercam esse algoritmo com
uma infraestrutura que lhe permite considerar sequncias de cdigo maiores (por
exemplo, mltiplos blocos). Nesta seo, examinaremos trs ideias para melhorar
a qualidade do escalonamento alterando o contexto em que o compilador aplica o
escalonamento de lista.

12.4.1 Escalonamento de blocos bsicos estendidos


Lembre-se, da Seo 8.3, que um bloco bsico estendido (EBB) consiste em um
conjunto de blocos B1, B2, ..., Bn, em que B1 tem vrios predecessores e cada outro bloco
Bi tem exatamente um predecessor, algum Bj no EBB. O compilador pode identificar
EBBs em uma passagem simples sobre o CFG. Considere o fragmento de cdigo simples mostrado na margem. Ele tem um EBB grande, {B1, B2, B3, B4}, e dois triviais,
{B5} e {B6}. O EBB grande tem dois caminhos, B1, B2, B4 e B1, B3. Os caminhos
compartilham B1 como um prefixo comum.

12.4 Escalonamento regional 549

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

O compilador pode mover uma operao para trs ou seja, anteriormente no


caminho. Por exemplo, ele poderia mover f de B2 para B1. Embora essa deciso
possa acelerar a execuo ao longo do caminho B1, B2, B4, ela insere uma computao de f no caminho B1, B3, ao que tem duas consequncias. Primeiro,
estende a execuo de B1, B3. Segundo, pode produzir cdigo incorreto ao
longo de B1, B3.

Se f tem um efeito colateral que muda os valores produzidos ao longo do caminho


comeando apartir de B3, ento o escalonador deve reescrever o cdigo para desfazer
esse efeito em B3. Em alguns casos, arenomeao pode resolver o problema; em outros,
ele deve inserir uma ou mais operaes decompensao em B3. Essas operaes atrasam
ainda mais aexecuo ao longo do caminho B1, B3.
O problema do cdigo de compensao tambm deixa clara a ordem em que o
escalonador dever considerar caminhos em um EBB. Como o primeiro caminho
escalonado recebe pouco ou nenhum cdigo de compensao, o escalonador deve
escolher caminhos na ordem de sua frequncia de execuo provvel. Ele pode usar
dados de perfil ou estimativas, da mesma forma como faz o algoritmo de posicionamento de cdigo global na Seo 8.6.2.
O escalonador pode tomar medidas para aliviar o impacto do cdigo de compensao.
Ele pode usar informao viva para evitar algum cdigo de compensao sugerido
pela movimentao para a frente. Se o resultado da operao movida no estiver
vivo na entrada do bloco fora do caminho, nenhum cdigo de compensao necessrio nesse bloco. Pode-se evitar todo o cdigo de compensao necessrio pelo

Se f matar algum valor usado em B3, a renomeao


doresultado de f pode evitar o problema. Se o valor estiver vivo aps B4, o escalonador pode ter que copi-lo
de volta ao seu nome original aps B4.

550 CAPTULO 12 Escalonamento de instrues

movimento para trs simplesmente proibindo esta movimentao entre fronteiras


de bloco. Embora essa restrio limite a capacidade do escalonador de melhorar o
cdigo, evita estender outros caminhos, e, ainda, permite ao escalonador alguma
oportunidade de melhoria.
O mecanismo do escalonamento de EBB simples. Para escalonar um caminho do
EBB, o escalonador realiza a renomeao, se necessrio, sobre a regio. Em seguida,
constri um nico grafo de dependncia para o caminho inteiro, ignorando quaisquer
sadas prematuras; calcula as mtricas de prioridade necessrias para selecionar entre
operaes prontas e desempates; e, finalmente, aplica o escalonamento de lista, como
para um nico bloco. Toda vez que atribui uma operao a uma instruo especfica
em um ciclo especfico do escalonamento, ele insere algum cdigo de compensao
necessrio para essa escolha.
Nesse esquema, o compilador escalona cada bloco uma vez. Em nosso exemplo, ele
poderia primeiro processar o caminho B1, B2, B4. O caminho seguinte B1, B3. Como
o escalonamento de B1 j est fixo, ele usar este conhecimento como uma condio
inicial quando processar B3, mas no mudar o escalonamento para B1. Finalmente,
ele escalona os EBBs triviais, B5 e B6.
Trao
Caminho acclico por meio do CFG, selecionado usando
informaes de perfil.

12.4.2 Escalonamento de trao (Trace scheduling)


O escalonamento de trao estende o conceito bsico de escalonamento de caminhos
para alm dos caminhos por um EBB. Ao invs de se concentrar em EBBs, o escalonamento de trao constri caminhos acclicos de comprimento mximo atravs
do CFG e aplica o algoritmo de escalonamento de lista a esses caminhos, conhecidos
como traos (ou traces). Como o escalonamento de trao tem as mesmas questes de
cdigo de compensao do que o escalonamento de EBB, o compilador deve escolher
traos de modo que garanta que os caminhos quentes os caminhos executados com
mais frequncia sejam escalonados antes dos caminhos mais frios.
Para criar traos para escalonamento, o compilador precisa acessar informaes de
perfil para as arestas no CFG. O diagrama na margem mostra nosso exemplo com
contagens de execuo em cada aresta. Para construir um trao, o escalonador pode
usar uma tcnica gulosa simples: inicia um trao selecionando a aresta executada com
mais frequncia no CFG. Em nosso exemplo, ele selecionaria a aresta B1, B2 para
criar um trao inicial B1, B2. Depois, examina as arestas que entram no primeiro n
do trao ou que saem do ltimo e escolhe a aresta com a contagem de execuo mais
alta. No exemplo, ele escolhe B2, B4, ao invs de B2, B5, para criar o trao B1, B2,
B4. Como B4 tem apenas um sucessor, B6, ele escolhe B4, B6 como sua prxima aresta
e produz o trao B1, B2, B4, B6
A construo do trao termina quando o algoritmo esgota as arestas possveis, como
em nosso exemplo, ou encontra um desvio de fechamento de lao. A segunda condio
impede o escalonador de construir um trao que mova operaes para fora de um lao.
A suposio de que a otimizao anterior ter realizado a movimentao de cdigo
invariante de lao (por exemplo, a movimentao de cdigo pouco ativo na Seo
10.3.1), e que o escalonador no dever se colocar na posio de inserir cdigo de
compensao no desvio de fechamento de lao.
Dado um trao, o escalonador aplica o algoritmo de escalonamento de lista ao trao
inteiro, da mesma forma como o escalonamento de EBB o aplica a um caminho por
meio de um EBB. Com um trao arbitrrio, surge uma oportunidade adicional para
o cdigo de compensao; o trao pode ter pontos de entrada interinos blocos no
meio do trao que possuem vrios predecessores.

12.4 Escalonamento regional 551

A movimentao de cdigo para a frente de uma operao i por meio de um


ponto de entrada interino pode acrescentar i ao caminho fora do trao. Se i
redefine um valor que tambm est vivo pela entrada interina, alguma combinao derenomeao ou recomputao pode ser necessria. A alternativa proibir
a movimentao para a frente atravs da entrada interina ou usar clonagem para
evitar essa situao (ver Seo12.4.3).
j A movimentao de cdigo para trs de uma operao i atravs de um ponto
de entrada interino pode precisar da incluso de i no caminho fora do trao.
Esta situao simples, pois i j ocorreu no caminho fora do trao (embora
mais adiante na execuo). Como o escalonador precisa corrigir quaisquer
problemas de nomeao introduzidos pela movimentao para trs no trao,
o cdigo de compensao fora do trao pode simplesmente definir o mesmo
nome.
Para escalonar o procedimento inteiro, o escalonador de trao constri um trao e
o escalona. Depois, desconsidera os blocos no trao e seleciona o prximo trao
executado com mais frequncia. Esse trao escalonado com o requisito de que respeite quaisquer restries impostas pelo cdigo previamente escalonado. O processo
continua, escolhendo um trao, escalonando-o e o desconsiderando, at que todos os
blocos tenham sido escalonados.
O escalonamento de EBB pode ser considerado um caso degenerativo de escalonamento
de trao, no qual as entradas interinas ao trao so proibidas.

12.4.3 Clonagem por contexto


Em nosso exemplo em andamento, os pontos de juno no CFG limitam as
oportunidades para escalonamento de EBB ou escalonamento de trao. Para
melhorar os resultados, o compilador pode clonar blocos para criar caminhos
livres de juno mais longos. A clonagem de superbloco tem exatamente este
efeito (ver Seo 10.6.1). Para o escalonamento de EBB, isto aumenta o tamanho
do EBB e o comprimento de alguns dos caminhos por meio do EBB. Para o escalonamento de trao, isto evita as complicaes causadas por pontos de entrada
interinos no trao. De qualquer forma, a clonagem tambm elimina alguns dos
desvios e saltos no EBB.
A figura na margem mostra o CFG que poderia resultar da clonagem em nosso exemplo em andamento. O bloco B5 foi clonado para criar ocorrncias separadas para os
caminhos a partir de B2 e de B3. De modo semelhante, B6 foi clonado duas vezes para
criar uma ocorrncia exclusiva para cada caminho que entra nele. Juntas, essas aes
eliminam todos os pontos de juno no CFG.
Aps a clonagem, o grafo inteiro forma um nico EBB. Se o compilador decidir que
B1, B2, B4, B6 o caminho quente, o escalonar primeiro. Nesse ponto, ele tem dois
outros caminhos para escalonar. Pode escalonar B5, B6 usando o B1, B2 escalonado
como um prefixo. Ou pode escalonar B3, B5, B6 usando o B1 escalonado como um
prefixo. No CFG clonado, nenhuma dessas ltimas escolhas interfere com a outra.
Compare este resultado com o escalonador EBB simples. Ele escalonou B3 com
relao a B1 e, depois, escalonou tanto B5 quanto B6 sem contexto anterior. Como
B5 e B6 tm vrios predecessores e contexto inconsistente, o escalonador EBB no
pode fazer melhor do que o escalonamento local. A clonagem desses blocos para
dar um contexto extra ao escalonador custa uma cpia das instrues j e k e duas
cpias da instruo l.

552 CAPTULO 12 Escalonamento de instrues

Na prtica, o compilador pode simplificar o CFG combinando pares de blocos como


B4 e B6 que esto vinculados por uma aresta onde a origem no tem outros sucessores
e o destino tem outros predecessores. A combinao desses blocos elimina o salto de
fim de bloco no primeiro bloco do par.
Uma segunda situao onde a clonagem merece considerao surge nos programas
recursivos na cauda. Lembre-se, pelas Sees 7.8.2 e 10.4.1, que um programa
recursivo na cauda se sua ltima ao for uma autochamada recursiva. Quando o
compilador detecta uma chamada na cauda, pode convert-la em um salto de volta
para a entrada do procedimento. Sob o ponto de vista do escalonador, a clonagem
pode melhorar a situao.
O primeiro diagrama mostrado na margem mostra o grafo CFG abstrado para uma
rotina recursiva na cauda, aps a chamada de cauda ter sido otimizada. O bloco B1
entrado ao longo de dois caminhos, o caminho a partir da entrada do procedimento e
o outro a partir de B2. Isto fora o escalonador a usar suposies de pior caso sobre o
que precede B1. Clonando B1 conforme mostramos no diagrama de baixo, o compilador
pode fazer com que o controle entre em B1 ao longo de apenas uma aresta, o que
pode melhorar os resultados do escalonamento regional. Para simplificar ainda mais a
situao, o compilador poderia agrupar B1 ao final de B2, criando um corpo de lao
de nico bloco. O lao resultante pode ser escalonado com um escalonador local ou
com um escalonador de lao, como for mais apropriado.

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?

12.5 TPICOS AVANADOS


A otimizao em compiladores, desde o primeiro compilador FORTRAN, tem focado a
melhoria de cdigo em laos. O motivo simples: o cdigo dentro de laos executado
com mais frequncia do que o cdigo fora de laos. Esta observao tem levado ao
desenvolvimento de tcnicas de escalonamento especializadas, que tentam diminuir

12.5 Tpicos avanados 553

o tempo de execuo total de um lao. A tcnica mais utilizada chamada pipelining


de software, pois cria um escalonamento que imita o comportamento de um pipeline
de hardware.

12.5.1 A estratgia de pipelining de software


Tcnicas especializadas de escalonamento de lao podem criar escalonamentos que melhoram os resultados do escalonamento local, escalonamento de EBB e escalonamentos
de trao por um simples motivo: elas podem levar em considerao o fluxo de valores em
torno do lao inteiro, incluindo o desvio de fechamento de lao. Tcnicas especializadas de
escalonamento de lao s fazem sentido quando o escalonador default incapaz de produzir
cdigo compacto e eficiente para o lao. Se o corpo do lao, aps o escalonamento, nocontm adiamentos, interbloqueios ou nops, ento o escalonador de lao provavelmente no
melhorar seu desempenho. De modo semelhante, se o corpo do lao for grande o suficiente
para que os efeitos de fim do lao sejam uma pequena frao do seu tempo de execuo,
um escalonador de lao especializado provavelmente no mostrar melhorias significativas.
Ainda assim, muitos laos pequenos e computacionalmente intensos se beneficiam
com o escalonamento de lao. Normalmente, esses laos tm poucas operaes em
relao ao comprimento de seus caminhos crticos para manter o hardware subjacente
ocupado. Um lao com pipeline de software sobrepe a execuo de iteraes sucessivas do lao; em um determinado ciclo, o lao pode emitir operaes de duas ou trs
iteraes diferentes. Esses laos em pipeline consistem em um ncleo (kernel) de
tamanho fixo, junto com um prlogo e um eplogo para lidar com a inicializao e a
finalizao do lao. O efeito combinado semelhante ao de um pipeline de hardware,
que possui operaes distintas em processamento concorrente.
Para um lao em pipeline ser executado corretamente, o cdigo deve primeiro executar
uma seo de prlogo que preenche o pipeline. Se o ncleo executa operaes de trs
iteraes do lao original, ento cada iterao do ncleo processa aproximadamente
um tero de cada iterao ativa do lao original. Para iniciar a execuo, o prlogo
deve realizar trabalho suficiente para se preparar para o ltimo tero da iterao 1,
o segundo tero da iterao 2 e o primeiro tero da iterao 3. Aps o trmino do
kernel do lao, um eplogo correspondente necessrio para completar as iteraes
finais esvaziando o pipeline. No exemplo, seria preciso executar os dois teros finais
da penltima iterao e o tero final da ltima iterao. As sees de prlogo e eplogo
aumentam o tamanho do cdigo. Embora o aumento especfico seja uma funo do lao
e do nmero de iteraes que o kernel executa simultaneamente, no incomum que o
prlogo e o eplogo dobrem a quantidade de cdigo exigida para o lao.
Para tornar essas ideias concretas, considere o lao a seguir, escrito em C:

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.

554 CAPTULO 12 Escalonamento de instrues

FIGURA 12.7 Lao de exemplo escalonado para uma unidade funcional.

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

FIGURA 12.8 Acompanhamento de execuo em um processador superescalar de duas unidades.

12.5 Tpicos avanados 555

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.

FIGURA 12.9 Lao de exemplo aps pipelining de software.

12.5.2 Um algoritmo para pipelining de software


Para criar um lao com pipeline de software, o escalonador segue um plano simples.
Primeiro, estima o nmero de ciclos no ncleo (kernel), chamado intervalo de iniciao.
Segundo, tenta escalonar o kernel; se este processo falhar, ele aumenta o tamanho do
kernel em 1 e tenta novamente. (Este processo deve terminar porque o escalonamento
ter sucesso antes que o tamanho do kernel exceda o tamanho do lao sem pipeline.)
Como etapa final, o escalonador gera cdigo de prlogo e eplogo para corresponder
com o kernel escalonado.

Estimativa do tamanho do kernel


Como uma estimativa inicial para o tamanho do kernel, o escalonador de lao pode
calcular limites inferiores sobre o nmero de ciclos que devem estar no kernel de lao.
j

O compilador pode estimar o nmero mnimo de ciclos no kernel a partir de uma


observao simples: cada operao no corpo do lao deve ser emitida. Ele pode
calcular o nmero de ciclos exigidos para emitir todas as operaes da seguinte forma:

Esta figura mostra um acompanhamento de execuo,


no o cdigo escalonado.

556 CAPTULO 12 Escalonamento de instrues

Recorrncia
Computao baseada em lao que cria um ciclo
nografo de dependncia.
Uma recorrncia precisa se espalhar por vrias iteraes.

onde u varia sobre todos os tipos de unidade funcional u, Iu o nmero de


operaes do tipo u no lao e Nu o nmero de unidades funcionais de tipo u.
Chamamos de RC a restrio de recurso (Resource Constraint).
j O compilador pode estimar o nmero mnimo de ciclos no kernel a partir de outra observao simples: o intervalo de iniciao deve ser longo o suficiente para
permitir que cada recorrncia seja completada. Ele pode calcular o limite inferior
a partir dos tamanhos de recorrncia da seguinte forma:

onde r varia sobre todas as recorrncias no corpo do lao, dr o atraso


acumulativo em torno da recorrncia r, e kr o nmero de iteraes pelas
quais r se espalha. Chamamos de DC a restrio de dependncia (Dependence Constraint).
O escalonador pode usar ii=max (RC, DC) como seu primeiro intervalo de iniciao.
Em nosso lao de exemplo, todas as computaes so do mesmo tipo. Como o corpo
do lao contm nove operaes para duas unidades funcionais, isto sugere uma restrio de recurso de 9/2=5. Porm, as operaes loadAO e storeAO s podem
ser executadas na unidade 0, de modo que tambm devemos calcular 3/1=3 como
restrio para a unidade 0. Como 5>3, RC 5. Pelo grafo de dependncia na
Figura12.10b, as recorrncias so sobre r@x, r@y e r@z. Todos os trs tm atraso de
um e se espalham por uma nica iterao, de modo que DC um. Tomando o maior
dentre RC e DC, o algoritmo encontra um valor inicial de 5 para ii.

FIGURA 12.10 Grafo de dependncia para o lao de exemplo na Figura 12.7.

12.5 Tpicos avanados 557

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.

Dependncia transportada por lao


Dependncia que representa um valor transportado
pela aresta do CFG para o desvio de fechamento de 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.

FIGURA 12.11 Escalonamento de ncleo (kernel) para o lao com pipeline.

Avanando o contador de ciclo para 2, a lista Ready contm f e j. O escalonador


seleciona f, desempatando em favor da operao com a maior latncia, e escalona
f para a unidade 0. Esta ao satisfaz a antidependncia de f para h; o escalonador
imediatamente coloca h na unidade 1, no ciclo 2.
No ciclo 3, a lista Ready contm apenas j. O escalonador o coloca na unidade 0. No
ciclo 4, a dependncia de j para m satisfeita; porm, a restrio adicional que mantm
um desvio de fim de bloco ao final do bloco o atrasa por um ciclo.
No ciclo 4, a lista Ready est vazia. Quando o contador de ciclo avana para o ciclo
5, tanto m quanto i esto prontos. O escalonador os coloca nas unidades 0 e 1.
Quando o contador avana para alm do ciclo 5, ele retorna ao ciclo 1. A lista Ready
est vazia, mas a lista Active no est, de modo que o escalonador incrementa o
contador de ciclos. No ciclo 2, a operao i ter terminado e a k estar pronta. Esta um
store, que deve ser executado na unidade 0. A unidade 0 est ocupada nos ciclos 2 e 3,

O escalonamento de kernel falha quando no encontra


um slot de emisso para alguma operao. Se isso
acontecer, o algoritmo incrementa ii e tenta novamente.

558 CAPTULO 12 Escalonamento de instrues

de modo que o escalonador continua incrementando o contador de ciclos, procurando


por um slot onde possa colocar a operao k. Finalmente, no ciclo 4, ele encontra um
slot de emisso para a operao k.
O escalonamento da operao k no ciclo 4 satisfaz a antidependncia de k para l. O
escalonador escalona imediatamente l para a unidade 1 no ciclo 4,e, ento, incrementa
o contador at que essas duas operaes saiam da lista Active. Como nenhuma
tem quaisquer descendentes no grafo de dependncia, tanto Ready quanto Active
tornam-se vazias e o algoritmo termina.

Gerao de cdigo de prlogo e eplogo


Em princpio, a gerao de cdigo de prlogo e eplogo simples. A ideia bsica,
nos dois casos, que o compilador pode usar o grafo de dependncia como seu guia.
Para gerar o cdigo de prlogo, o compilador comea a partir de cada uso exposto
para cima no lao e segue o grafo de dependncia em uma fase de escalonamento para
trs. Para cada uso exposto para cima, ele deve gerar a cadeia de operaes que geram
o valor necessrio, devidamente escalonada para cobrir suas latncias. Para gerar o
eplogo, o compilador comea a partir de cada uso exposto para baixo no lao e segue
o grafo de dependncia em uma fase de escalonamento para a frente.
O lao de exemplo tem cdigo de prlogo e eplogo particularmente simples, pois o
intervalo de iniciao grande em relao aos atrasos no lao. O Exerccio 9, ao final
do captulo, mostra uma verso do mesmo cdigo com um corpo de lao mais apertado
e, da, um prlogo e eplogo mais complexo.

12.6 RESUMO E PERSPECTIVA


Para obter um desempenho razovel em um processador moderno, o compilador deve
escalonar operaes cuidadosamente. Quase todos os compiladores modernos utilizam
alguma forma de escalonamento de lista. O algoritmo facilmente adaptado e parametrizado pela alterao de esquemas de prioridade, regras de desempate e at mesmo
de direo do escalonamento. O escalonamento de lista robusto, no sentido de que
produz bons resultados para uma grande gama de cdigos. Na prtica, ele normalmente
encontra um escalonamento de tempo timo.
As variaes sobre o escalonamento de lista que operam por regies maiores resolvem
problemas que surgem, pelo menos em parte, da complexidade aumentada dos processadores modernos. As tcnicas que escalonam EBBs e laos so, basicamente, respostas
ao aumento no nmero de pipelines que o compilador precisa considerar e suas latncias
individuais. medida que as mquinas se tornam mais complexas, os escalonadores
tm necessitado de mais contexto de escalonamento para descobrir paralelismo em nvel
de instruo suficiente para manter as mquinas ocupadas. O pipelining de software
fornece um modo de aumentar o nmero de operaes emitidas por ciclo e diminuir
o tempo total para executar um lao. O escalonamento de trao foi desenvolvido para
arquiteturas VLIW, para as quais o compilador precisava manter muitas unidades
funcionais ocupadas.

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,

12.6 Resumo e perspectiva 559

incluindo muitas variantes especializadas do problema. O escalonamento de instrues


tem sido estudado como um problema distinto, desde a dcada de 1960.
Algoritmos que garantem escalonamentos timos existem para situaes simples.
Por exemplo, em uma mquina com uma unidade funcional e latncias de operao
uniformes, o algoritmo de rotulagem de Sethi-Ullman cria um escalonamento timo
para uma rvore de expresso [311]. Ele pode ser adaptado para produzir um bom
cdigo para DAGs de expresso. Fischer e Proebsting basearam-se no algoritmo de
rotulagem para obter um algoritmo que produz resultados timos, ou quase timos,
para pequenas latncias de memria [289]. Infelizmente, ele tem problema quando as
latncias aumentam ou o nmero de unidades funcionais cresce.
Grande parte da literatura sobre escalonamento de instrues trabalha com variantes
do algoritmo de escalonamento de lista descrito neste captulo. Landskov e outros
normalmente so citados como o trabalho definitivo sobre escalonamento de lista
[239], mas o algoritmo, pelo menos, vem desde Heller em 1961 [187]. Outros artigos
que se baseiam no escalonamento de lista incluem Bernstein e Rodeh [39], Gibbons
e Muchnick [159], e Hennessy e Gross [188]. Krishnamurthy e outros fornecem um
estudo de alto nvel da literatura para processadores em pipeline [234,320]. Kerns,
Lo e Eggers desenvolveram o escalonamento balanceado como um modo de adaptar
o escalonamento de lista a latncias de memria incertas [221,249]. O algoritmo RBF de
Schielke explorou o uso da aleatoriedade e repetio como alternativa aos esquemas
deprioridade em mltiplas camadas [308].
Muitos autores tm descrito algoritmos de escalonamento regionais. A primeira tcnica
regional automatizada foi o algoritmo de escalonamento de trao de Fisher [148,149].
Ela tem sido usada em diversos sistemas comerciais [137,251] e de pesquisa [318].
Hwu e outros propuseram o escalonamento de superbloco como alternativa [201]; dentro de um lao, ele clona blocos para evitar pontos de juno, de maneira semelhante
que mostramos na Seo 12.4.3. Click props um algoritmo de ecalonamento global
baseado no uso de um grafo de valor global [85]. Vrios autores propuseram tcnicas
para utilizar recursos especficos de hardware [303,318]. Outras tcnicas que utilizam
replicao para melhorar o escalonamento incluem Ebcioglu e Nakatani [136] e Gupta e
Soffa [174]. Sweany e Beaty propuseram escolher caminhos com base em informao de
dominncia [327]; outros examinaram diversos aspectos desta tcnica [105,199,326].
Pipelining de software tem sido explorado extensivamente. Rau e Glaeser introduziram
a ideia em 1981 [294]. Lam desenvolveu o esquema para pipelining de software
apresentado aqui [236]; o artigo inclui um esquema hierrquico para tratar o fluxo de
controle dentro de um lao. Aiken e Nicolau desenvolveram uma tcnica semelhante,
chamada pipelining perfeito [10] ao mesmo tempo do trabalho de Lam.
O exemplo para escalonamento para trs versus para a frente na Figura12.5 foi trazido
ao nosso conhecimento por Philip Schielke [308], apanhado do programa de benchmark
do SPEC 95, go. Ele captura, de forma concisa, um efeito que fez com que muitos
construtores de compilador inclussem escalonadores para a frente e para trs nos back
ends de seus compiladores.

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.

560 CAPTULO 12 Escalonamento de instrues

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

encaminha o valor do store (em r21 no incio da sequncia) diretamente para o


resultado do load (r12). Como o grafo de dependncia reflete esse recurso de
bypass do hardware?
Seo 12.3
3. Estenda o algoritmo de escalonamento de lista local da Figura12.3 para lidar com
vrias unidades funcionais. Suponha que todas elas tm capacidades idnticas.
4. Um aspecto crtico de qualquer algoritmo de escalonamento o mecanismo para
definir prioridades iniciais e desempatar quando vrias operaes com a mesma
prioridade esto prontas no mesmo ciclo. Alguns desempatadores alternativos
poderiam ser:
a. Tomar as operaes com operandos baseados em registrador em detrimento
das operaes com operandos imediatos.
b. Tomar a operao cujos operandos foram definidos mais recentemente.
c. Tomar uma operao escolhida aleatoriamente a partir da lista Ready.
d. Tomar um load antes de qualquer computao.
Para cada desempatador, sugira uma racionalizao sua opinio sobre
por que algum sugeriu isso. Qual desempatador voc usaria primeiro? Qual
usaria em segundo lugar? Justifique (ou apresente razes para) suas respostas.
5. Algumas operaes, como a cpia de registrador-para-registrador, podem ser
executadas em quase toda unidade funcional, embora com um opcode diferente.
O escalonador pode aproveitar essas alternativas? Sugira modificaes ao
framework bsico de escalonamento de lista que lhe permita usar sinnimos
para uma operao bsica, como uma cpia.
6. A maioria dos microprocessadores modernos possui slots de atraso em algumas ou
todas as operaes de desvio. Com um nico slot de atraso, a operao imediatamente
aps o desvio executada enquanto o desvio processado; assim, o slot ideal para escalonar um desvio est no penltimo ciclo de um bloco bsico. (A maioria dos processadores tem uma verso do desvio que no executa o slot de atraso, de modo que o
compilador pode evitar gerar uma instruo nop em um slot de atraso no preenchido.)
a. Como voc adaptaria o algoritmo de escalonamento de lista para melhorar
sua capacidade de preencher slots de atraso?
b. Esboce um passo de ps-escalonamento que preencheria os slots de atraso.
c. Proponha um uso criativo para os slots de atraso de desvio que no possam
ser preenchidos com operaes teis.

12.6 Resumo e perspectiva 561

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.

Esta figura mostra o cdigo escalonado.

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

564 CAPTULO 13 Alocao de registradores

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.

Em geral, o alocador de registradores tenta minimizar o impacto dos loads e stores


que acrescenta ao cdigo, chamado cdigo de derramamento. Este impacto inclui o
tempo necessrio para executar o cdigo de derramamento, o espao de cdigo que
ele ocupa e o espao de dados ocupado pelos valores derramados. Um bom alocador
de registrador tenta minimizar todos os trs.
A prxima seo revisa algumas das questes fundamentais que criam o ambiente
em que os alocadores de registradores operam. As sees subsequentes exploram
algoritmos para alocao e atribuio de registradores nos escopos local e global.

13.2 QUESTES FUNDAMENTAIS


O alocador de registradores usa como entrada o cdigo que est quase completamente compilado o cdigo foi analisado lxica e sintaticamente, verificado, analisado,
otimizado, reescrito como cdigo da mquina-alvo e, talvez, escalonado. O alocador
deve ajust-lo ao conjunto de registradores da mquina-alvo renomeando valores
e inserindo operaes que movem valores entre registradores e memria. Muitas
decises tomadas nas fases iniciais do compilador afetam a tarefa do alocador, assim como as propriedades do conjunto de instrues da mquina alvo. Esta seo
explora diversos fatores que influenciam na modelagem do papel do alocador de
registradores.

13.2 Questes fundamentais 565

13.2.1Memria versus registradores


A escolha do construtor de compiladores por um modelo de memria define muitos
detalhes do problema de alocao que o alocador precisa resolver (ver Seo5.4.3).
Com um modelo do tipo registrador-para-registrador, as fases anteriores do compilador
codificam diretamente seu conhecimento sobre referncias de memria ambguas para
a forma da IR; e colocam valores no ambguos em registradores virtuais. Portanto, os
valores armazenados na memria so considerados como ambguos (ver Seo7.2),
de modo que o alocador os deixa em memria.
Em um modelo memria-para-memria, o alocador no tem essa dica da forma de
cdigo, pois o programa em IR mantm todos os valores na memria. Neste, o alocador
deve determinar quais valores podem ser mantidos em segurana nos registradores ou
seja, quais valores so no ambguos. Em seguida, precisa determinar se mant-los em
registradores lucrativo. Neste modelo, o cdigo que o alocador recebe como entrada
normalmente usa menos registradores e executa mais operaes de memria do que
o cdigo equivalente de registrador-para-registrador. Para obter bom desempenho, o
alocador precisa promover o mximo que puder de valores baseados na memria para
registradores.
Assim, a escolha de modelo de memria determina fundamentalmente a tarefa do
alocador. Nos dois cenrios, o objetivo do alocador reduzir o nmero de loads e
stores que o cdigo final executa para mover valores de um lado para outro entre
registradores e memria. Em um modelo registrador-para-registrador, a alocao
uma parte necessria do processo que produz cdigo legal; ela garante que o cdigo
final se encaixa no conjunto de registradores da mquina alvo. O alocador insere
operaes load e store para mover alguns valores baseados em registrador para a
memria presumivelmente em regies onde a demanda por registradores excede a
oferta. O alocador tenta minimizar o impacto das operaes load e store que insere.
Ao contrrio, em um compilador com um modelo memria-para-memria, o compilador realiza a alocao de registradores como uma otimizao. O cdigo vlido
antes da alocao; a alocao simplesmente melhora o desempenho mantendo alguns
valores baseados na memria em registradores e eliminando os loads e stores usados
para acess-los. O alocador tenta remover o mximo de loads e stores possvel, pois
isso pode melhorar significativamente o desempenho do cdigo final.
Assim, a falta de conhecimento limitaes na anlise do compilador pode evitar
que o compilador aloque uma varivel a um registrador. Isto tambm pode ocorrer quando uma nica sequncia de cdigo herda diferentes ambientes ao longo de diferentes
caminhos. Essas limitaes sobre o que o compilador pode saber tendem a favorecer
o modelo registrador-para-registrador, que fornece um mecanismo para outras partes
do compilador codificarem conhecimento sobre ambiguidade e exclusividade. Esse
conhecimento poderia vir da anlise, do entendimento da traduo de uma construo
complexa ou at mesmo ser obtido do texto fonte pelo parser.

13.2.2Alocao versus atribuio


Em um compilador moderno, o alocador de registradores soluciona dois problemas
distintos alocao e atribuio de registradores , que algumas vezes foram tratados
separadamente no passado. Eles so relacionados, porm distintos.
1. Alocao. A alocao de registradores mapeia um espao de nomes ilimitado
no conjunto de registradores da mquina-alvo. Em um modelo registrador-para-registrador, esta alocao mapeia registradores virtuais a um novo conjunto de
nomes que modela o conjunto de registradores fsicos e derrama valores que no

566 CAPTULO 13 Alocao de registradores

cabem no conjunto de registradores. Em um modelo memria-para-memria,


ela mapeia algum subconjunto de locais de memria a um conjunto de nomes
que modela o conjunto de registradores fsicos. A alocao garante que o cdigo
caber neste conjunto de registradores da mquina-alvo a cada instruo.
2. Atribuio. A atribuio de registradores mapeia um conjunto de nomes alocados
aos registradores fsicos da mquina-alvo. Ela assume que a alocao foi realizada, de modo que o cdigo caber no conjunto de registradores fsicos fornecidos
pela mquina-alvo. Assim, a cada instruo no cdigo gerado, no mais do que
k valores so designados como residindo em registradores, onde k o nmero
de registradores fsicos. A atribuio produz os nomes reais dos registradores
exigidos pelo cdigo executvel.
A alocao de registradores um problema difcil. Formulaes gerais do problema
so NP-completas. Para um nico bloco bsico, com um nico tamanho de valores de
dados, a alocao tima pode ser feita em tempo polinomial se cada valor tiver que ser
armazenado na memria ao final do seu tempo de vida e o custo de armazenar esses
valores for uniforme. Quase qualquer complexidade adicional ao problema o torna NP-completo. Por exemplo, a incluso de um segundo tamanho de item de dados, como um
par de registradores que armazena um nmero de ponto flutuante com preciso dupla,
torna o problema NP-completo. Reciprocamente, a incluso de um modelo de memria
com custos de acesso no uniformes, ou a distino de que alguns valores, como as constantes, no precisam ser armazenados no final de seu tempo de vida, tornam o problema
NP-completo. A extenso do escopo de alocao para incluir fluxo de controle e mltiplos
blocos tambm torna o problema NP-completo. Na prtica, uma ou mais dessas questes
surgem na compilao de qualquer sistema real. Em muitos casos, todos eles surgem.
A atribuio de registradores, em muitos casos, pode ser resolvida em tempo polinomial. Considere uma mquina com um tipo de registrador. Dada uma alocao vivel
para um bloco bsico ou seja, uma em que a demanda por registradores fsicos a
cada instruo no exceda o nmero de registradores fsicos , uma atribuio pode
ser produzida em tempo linear usando uma analogia de colorao de grafo de intervalo.
O problema relacionado para um procedimento inteiro pode ser resolvido em tempo
polinomial ou seja, se, a cada instruo, a demanda por registradores fsicos no
exceder o nmero de registradores fsicos, o compilador pode construir uma atribuio
em tempo polinomial.
Grafo de intervalo
Um grafo de intervalo representa a sobreposio entre
mltiplos intervalos na linha real. Ele tem um n para
cada intervalo e uma aresta (i,j) se, e somente se, i e j
tiverem uma interseo no vazia.

A distino entre alocao e atribuio sutil e importante. Buscando melhorar o


desempenho do alocador de registradores, o construtor de compiladores deve entender
se o ponto fraco est na alocao ou na atribuio e dirigir o esforo para a parte
apropriada do algoritmo.

13.2.3 Classes de registradores


Os registradores fsicos fornecidos pela maioria dos processadores no formam um
pool homogneo de recursos intercambiveis. A maioria dos processadores tem classes
distintas de registradores para tipos diferentes de valores.
Por exemplo, a maioria dos computadores modernos tem registradores de uso geral e
registradores de ponto flutuante. O primeiro tipo armazena valores inteiros e endereos
de memria, enquanto o outro, valores de ponto flutuante. Essa dicotomia no nova; as
antigas mquinas IBM 360 tinham 16 registradores de uso geral e quatro registradores
de ponto flutuante. Os processadores modernos podem acrescentar mais classes.
Por exemplo, o PowerPC tem uma classe separada de registradores para cdigos de
condio, e o Intel IA-64 tem classes adicionais para registradores de predicados e

13.3 Alocao e atribuio locais de registradores 567

registradores de destino de desvio. O compilador precisa colocar cada valor em um


registrador da classe apropriada.
Se as interaes entre duas classes de registrador forem limitadas, o compilador pode
alocar registradores para elas de forma independente. Na maioria dos processadores,
os registradores de uso geral e os de ponto flutuante no so usados para manter os
mesmos tipos de valores. Assim, o compilador pode alocar os registradores de ponto
flutuante independentemente dos de uso geral. O fato de que o compilador usa registradores de uso geral para derramar registradores de ponto flutuante significa que ele
deve alocar os registradores de ponto flutuante primeiro. Dividir assim a alocao em
problemas menores reduz o tamanho das estruturas de dados, e pode produzir tempos
de compilao menores.

Os valores em registradores de ponto flutuante tm um


tipo diferente na linguagem-fonte, de modo que so
disjuntos dos valores armazenados em registradores de
uso geral.

Se, por outro lado, diferentes classes de registradores se sobrepuserem, o compilador


deve aloc-las juntas. A prtica comum de usar os mesmos registradores para nmeros
de ponto flutuante de preciso simples e dupla fora o alocador a tratar deles como um
nico problema de alocao no importa se um valor de preciso dupla usa dois
registradores de preciso simples ou um valor de preciso simples usa metade de um
registrador de preciso dupla. Problema semelhante surge em arquiteturas que permitem
que valores de tamanhos diferentes sejam armazenados em registradores de uso geral.
Por exemplo, as ISAs derivadas do Intel x86 permitem que alguns registradores de 32
bits mantenham um valor de 32 bits, dois de 16 bits ou quatro de 8 bits. O alocador
precisa modelar tanto os usos potenciais quanto os conflitos entre esses usos.

13.3 ALOCAO E ATRIBUIO LOCAIS DE REGISTRADORES


Como introduo alocao de registradores, considere os problemas que aparecem na
produo de uma boa alocao para um nico bloco bsico alocao local, para usar
a terminologia da otimizao (ver Seo8.3). Um alocador local opera sobre um bloco.
Para simplificar a discusso, consideramos que o bloco o programa inteiro. Ele carrega
os valores de que precisa a partir da memria e armazena os valores que produz na
memria. O bloco de entrada usa uma nica classe de registradores de uso geral; as
tcnicas podem ser estendidas facilmente para lidar com vrias classes disjuntas de
registradores. A mquina-alvo fornece um nico conjunto de k registradores fsicos.
A forma de cdigo codifica informaes sobre quais valores podem legalmente residir
em um registrador por quantidades de tempo no triviais. O cdigo mantm em um
registrador qualquer valor que possa legalmente residir em um registrador. Ele usa
tantos registradores virtuais quantos sejam necessrios para codificar essa informao;
assim, o bloco de entrada pode nomear mais de k registradores virtuais.
O bloco de entrada contm uma srie de operaes de trs endereos o1, o2, o3, ..., oN.
Cada operao, oi, tem a forma opi vri1, vri2 vri3. Para uma viso de alto nvel,
o objetivo da alocao local de registradores criar um bloco equivalente em que cada
referncia a um registrador virtual substituda por uma referncia a um registrador
fsico especfico. Se o nmero de registradores virtuais for maior do que k, o alocador
pode ter que inserir loads e stores para colocar o cdigo nos k registradores fsicos.
Uma declarao alternativa desta propriedade que o cdigo de sada no pode ter
mais de k valores em registradores em qualquer ponto no bloco.
Esta seo explora duas tcnicas para a alocao local. A primeira delas conta o
nmero de referncias a um valor no bloco e usa esses contadores de frequncia
para determinar quais valores residem em registradores. Por se basear em informaes
obtidas externamente os contadores de frequncia para priorizar a alocao de
registradores virtuais para registradores fsicos, consideramos esta uma tcnica de cima

Usamos vri para indicar um registrador virtual e ri


para indicar um registrador fsico.

568 CAPTULO 13 Alocao de registradores

para baixo (top-down). A segunda baseia-se no conhecimento detalhado do cdigo, de


baixo nvel, para tomar suas decises; percorre o bloco e determina, a cada operao,
se um derramamento necessrio ou no. Por sintetizar e combinar muitos fatos de
baixo nvel para controlar seu processo de tomada de deciso, consideramos esta uma
tcnica de baixo para cima (bottom-up).

13.3.1 Alocao de registradores local top-down


O alocador local de cima para baixo funciona a partir de um princpio simples: os
valores mais usados devem residir em registradores. Para implementar essa heurstica,
ele encontra o nmero de vezes que cada registrador virtual aparece no bloco. Depois,
aloca registradores virtuais a registradores fsicos em ordem decrescente da contagem
de frequncia.
F
Em uma ISA qualquer, F o nmero de registradores
necessrios para gerar cdigo para os valores que
residem na memria. Pronunciamos F como feasible
(vivel).

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.

13.3.2 Alocao de registradores local bottom-up


A ideia principal por trs do alocador local de baixo para cima focar os detalhes
de como os valores so definidos e usados com base em cada operao. O alocador
local de baixo para cima comea com todos os registradores desocupados. Para cada

13.3 Alocao e atribuio locais de registradores 569

operao, o alocador precisa garantir que seus operandos estejam em registradores


antes que ela seja executada. E tambm precisa alocar um registrador para o resultado
da operao. A Figura13.1 mostra seu algoritmo bsico, junto com trs rotinas de
suporte que ele utiliza.
O alocador de baixo para cima percorre as operaes no bloco, tomando decises de
alocao por demanda. Porm, existem alguns detalhes sutis. Considerando vri1 e vri2

FIGURA 13.1 O alocador de registradores local bottom-up.

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.

570 CAPTULO 13 Alocao de registradores

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

13.3 Alocao e atribuio locais de registradores 571

operaes de memria esquerda. Ao contrrio, derramar consistentemente o valor


sujo produz a sequncia direita, que exige menos operaes de memria.

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.

Na alocao local, timo significa aquela


com o mnimo de derramamentos.

13.3.3 Indo alm de blocos isolados


Vimos como construir bons alocadores para blocos isolados. Trabalhando de cima
para baixo, chegamos ao alocador baseado na contagem de frequncia. Trabalhando
de baixo para cima, chegamos a um alocador baseado na distncia at o prximo uso.
Porm, a alocao local no captura o reso de valores por mltiplos blocos. Como
este reso ocorre de forma rotineira, precisamos de alocadores que estendam seu escopo por mltiplos blocos.
Infelizmente, passar de um nico bloco para mltiplos blocos acrescenta muitas complicaes. Por exemplo, nossos alocadores locais consideravam implicitamente que
os valores no fluem entre os blocos. O motivo principal para passar para um escopo
maior para a alocao considerar o fluxo de valores entre blocos e gerar alocaes que
lidam com os fluxos de modo eficiente. O alocador precisa tratar corretamente valores
calculados nos blocos anteriores, e preservar valores para uso nos blocos seguintes.
Para conseguir isso, ele precisa de um modo mais sofisticado para tratar valores do
que os alocadores locais utilizam.

Vivncia e faixas vivas


Alocadores regionais e globais tentam atribuir valores a registradores de um modo
que coordena seu uso por vrios blocos. Vimos, no alocador de cima para baixo e na
discusso anterior sobre a forma SSA (ver Seo9.3), que o compilador s vezes pode
calcular um novo espao de nomes que atenda melhor s finalidades de determinado
algoritmo. Alocadores regionais e globais contam com esta observao; eles calculam
um espao de nomes que reflete os padres reais de definies e usos para cada valor.
Ao invs de alocar variveis ou valores aos registradores, eles calculam um espao de
nomes que definido em termos de faixas vivas.
Uma nica faixa viva consiste em um conjunto de definies e usos que esto relacionados uns aos outros porque seus valores fluem juntos. Ou seja, uma faixa viva contm
um conjunto de definies e um conjunto de usos. Estes conjuntos so autocontidos no
sentido de que, para cada uso, cada definio que pode alcan-lo est na mesma faixa
viva do uso. De modo semelhante, para cada definio, cada uso que pode se referir ao
resultado da definio est na mesma faixa viva da definio.
O termo faixa viva conta, implicitamente, com a noo de vivncia (liveness), conforme
descrita na Seo8.6.1. Lembre-se de que uma varivel v est viva no ponto p se ela
tiver sido definida ao longo de um caminho desde a entrada do procedimento at p, e
se houver um caminho de p at um uso de v ao longo do qual v no redefinida. Em
qualquer lugar onde v estiver viva, seu valor dever ser preservado, pois a execuo

Faixa viva
Um conjunto fechado de definies e usos relacionados,
que serve como espao de nomes bsico para alocao
de registradores.

572 CAPTULO 13 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

FIGURA 13.2 Faixas vivas em um bloco bsico.

13.3 Alocao e atribuio locais de registradores 573

ponto do cdigo, apenas valores vivos precisam de registradores. Assim, os conjuntos


LIVEOUT desempenham papel chave na alocao de registradores.

Complicaes nas fronteiras de bloco


Um compilador que usa alocao de registradores local poderia calcular os conjuntos
LIVEOUT para cada bloco como um preldio necessrio para fornecer ao alocador
local informaes sobre o status de valores na entrada e na sada do bloco. Os conjuntos
LIVEOUT permitem que o alocador trate as condies de fim de bloco corretamente.
Qualquer valor em LIVEOUT(b) deve ser armazenado em seu local atribudo na
memria depois de sua ltima definio em b para garantir que o valor correto esteja disponvel em um bloco subsequente. Ao contrrio, um valor que no est em
LIVEOUT(b) pode ser descartado sem um store aps seu ltimo uso em b.
Embora a informao de LIVEOUT permita que o alocador local produza cdigo
correto, esse cdigo ter stores e loads cuja nica finalidade conectar valores por
meio de fronteiras de bloco. Considere o exemplo mostrado na margem. O alocador
local atribuiu a varivel x a diferentes registradores em cada bloco: r1 em B1, r2 em
B2, r3 em B3 e r4 em B4. O nico mecanismo local para resolver essas atribuies em
conflito armazenar x ao final de B1 e B3 e carreg-lo no incio de B2 e B4, conforme
mostrado. Esta soluo passa o valor de x pela memria para mov-lo para seu registrador atribudo em B2 e B4.
Ao longo das arestas de fluxo de controle (B1, B2) e (B3, B4), o compilador poderia substituir o par store-load por uma operao de cpia registrador-para-registrador no local
apropriado: o incio de B2 para (B1, B2) e o final de B3 para (B3, B4). Porm, a aresta
(B1, B4) no tem um local onde o compilador pode colocar a cpia, pois uma aresta
crtica, conforme discutimos na Seo9.3.5. Colocar a cpia no final de B1 produz uma
atribuio incorreta para B2, enquanto coloc-la no incio de B4 produz um resultado
incorreto na aresta (B3, B4).
Em geral, o alocador local no pode usar operaes de cpia para conectar o fluxo de
valores entre os blocos. Ele no pode saber, ao processar B1, as decises de alocao e
atribuio feitas em blocos subsequentes. Assim, precisa lanar mo de passar valores
pela memria. Mesmo que o alocador soubesse as atribuies em B2 e B4 quando
processasse B1, ainda no poderia resolver o problema com (B1, B4), a menos que
mude o grafo de fluxo de controle. Como alternativa, o alocador poderia evitar esses
problemas coordenando o processo de atribuio por todos os blocos. Neste ponto,
porm, o alocador no seria mais um alocador local.
Efeitos semelhantes surgem com a alocao. E se x no fosse referenciado em B2?
Mesmo que pudssemos coordenar a atribuio globalmente, para garantir que x
sempre estivesse em algum registrador, digamos r2, quando fosse usado, o alocador
precisaria inserir um load de x ao final de B2 para permitir que B4 evite o load inicial
de x. Naturalmente, se B2 tivesse outros sucessores, eles poderiam no referenciar x
e precisar de outro valor em r2.
Uma segunda questo, tanto mais sutil quanto mais problemtica, surge quando
tentamos esticar os paradigmas de alocao local para alm de blocos isolados.
Considere a situao que surgiria ao realizar a alocao local de baixo para cima
sobre o bloco B1 do exemplo mostrado na margem. Se, depois do uso de x em B1,
o alocador precisar de um registrador adicional, dever calcular a distncia at o
prximo uso de x. Em um bloco isolado, essa prxima referncia exclusiva, assim
como sua distncia. Com mltiplos blocos sucessores, a distncia depende do caminho tomado em tempo de execuo, (B1, B2) ou (B1, B3, B4). Assim, isto no est

574 CAPTULO 13 Alocao de registradores

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.

13.4 ALOCAO E ATRIBUIO GLOBAIS DE REGISTRADORES


Os alocadores de registrador tentam minimizar o impacto do cdigo de derramamento
que devem inserir. Este impacto pode tomar pelo menos trs formas: tempo de execuo
para o cdigo de derramamento, espao de cdigo para as operaes de derramamento, e
espao de dados para os valores derramados. A maioria dos alocadores foca o primeiro
desses efeitos minimizar o tempo de execuo do cdigo de derramamento.
Os alocadores de registrador globais no podem garantir uma soluo tima para
este problema. A diferena entre duas alocaes diferentes para o mesmo cdigo est
tanto no nmero de loads, stores e operaes de cpia que o alocador insere quanto

13.4 Alocao e atribuio globais de registradores 575

no seu posicionamento no cdigo. O nmero de operaes importa tanto no espao de


cdigo quanto no tempo de execuo. O posicionamento das operaes importa porque
diferentes blocos so executados por diferentes nmeros de vezes, e essas frequncias
de execuo variam de uma execuo para outra.
A alocao global difere da local de duas maneiras fundamentais.
1. A estrutura de uma faixa viva global pode ser mais complexa do que a de uma
faixa viva local. Esta ltima um intervalo no cdigo em linha reta. J uma faixa
viva global uma teia de definies e usos encontrados realizando o fechamento
de duas relaes. Para um uso u na faixa viva LRi, LRi precisa incluir cada definio d que alcana u. De modo semelhante, para cada definio d em LRi, LRi
deve incluir cada uso u que d alcana. Alocadores globais criam um novo espao
de nome em que cada faixa viva tem um nome distinto. A alocao, ento, mapeia
nomes de faixa viva para um registrador fsico ou para um local de memria.
2. Dentro de uma faixa viva global LRi, as referncias distintas podem ser executadas por diferentes nmeros de vezes. Em uma faixa viva local, todas as referncias so executadas uma vez por execuo do bloco (a menos que ocorra uma
exceo). Assim, o custo do derramamento local uniforme. Em um alocador
global, este custo depende de onde o cdigo de derramamento ocorre. O problema de escolher um valor para derramar , portanto, muito mais complexo no
caso global do que no local.
Os alocadores globais anotam cada referncia com uma frequncia de execuo estimada, obtida da anlise esttica ou de dados de perfil. A alocao, ento, usa essas
anotaes para orientar as decises sobre alocao e derramamento.
Qualquer alocador global precisa resolver essas duas questes. Cada uma delas torna
a alocao global substancialmente mais complexa que a local.
Os alocadores globais tomam decises sobre alocao e atribuio; decidem, para cada
faixa viva, se ela residir ou no em um registrador; decidem, para cada faixa viva que
est em registrador, se ela pode ou no compartilhar um registrador com outras faixas
vivas; e escolhem, para cada faixa viva que deve residir em registrador, um registrador
fsico especfico para ela.

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:

576 CAPTULO 13 Alocao de registradores

O grafo da esquerda 2-colorido. Por exemplo, podemos atribuir azul aos ns 1 e 5, e


vermelho aos ns 2, 3 e 4. A incluso da aresta (2,3), como vemos direita, torna o grafo
3-colorido, mas no 2-colorido. (Atribua azul aos ns 1 e 5, vermelho aos ns 2 e 4, e
amarelo ao n 3.)
Para um dado grafo, o problema de encontrar seu nmero cromtico NP-completo.
De modo semelhante, o problema de determinar se o grafo k-colorido, para algum
k fixo, NP-completo. Os algoritmos que usam a colorao de grafo como paradigma
para alocar recursos utilizam mtodos aproximados para encontrar coloraes que se
adaptem ao conjunto de recursos disponveis.

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.

Para tomar essas decises, muitos compiladores realizam alocao de registradores


usando uma analogia para a colorao de grafo. Alocadores de colorao de grafo criam
um grafo, chamado grafo de interferncia, para modelar os conflitos entre faixas vivas.
Eles tentam construir uma k-colorao para esse grafo, onde k o nmero de registradores fsicos disponveis ao alocador. (Alguns registradores fsicos, como o ARP,
podem ser dedicados a outras finalidades.) A k-colorao para os grafos de interferncia
traduz-se diretamente em uma atribuio das faixas vivas aos registradores fsicos. Se o
compilador no puder construir diretamente uma k-colorao para o grafo, ele modifica
o cdigo subjacente derramando alguns valores para a memria e tenta novamente.
Como o derramamento simplifica o grafo, garantido que este processo termina.
Diferentes alocadores de colorao tratam do derramamento (ou alocao) de diferentes
maneiras. Examinaremos alocadores de cima para baixo que usam informaes de
alto nvel para tomar decises de alocao, e alocadores de baixo para cima que usam
informaes de baixo nvel para tomar essas decises. Porm, antes de examinarmos
essas duas tcnicas, vamos explorar alguns dos subproblemas que os alocadores tm
em comum: descobrir faixas vivas, estimar custos de derramamento e construir um
grafo de interferncia.

13.4.1 Descoberta de faixas vivas globais


Para construir faixas vivas, o compilador precisa descobrir os relacionamentos que
existem entre diferentes definies e usos. O alocador deve derivar um espao de
nomes que agrupe em um nico nome todas as definies que alcanam um nico uso
e todos os usos que uma nica definio pode alcanar. Isto sugere uma tcnica em que
o compilador atribui a cada definio um nome distinto e mescla nomes de definio
que alcanam um uso comum. A converso do cdigo para a forma SSA simplifica
a construo de faixas vivas; assim, vamos considerar que o alocador opera sobre a
forma SSA.
A forma SSA do cdigo fornece um ponto de partida natural para esta construo.
Lembre-se de que, nesta forma, cada nome definido uma vez, e cada uso refere-se a
uma definio. As funes- inseridas para reconciliar essas duas regras registram o
fato de que definies distintas em diferentes caminhos no grafo de fluxo de controle
alcanam uma nica referncia. Uma operao que referencia o nome definido por uma
funo- usa o valor de um de seus argumentos; qual argumento especfico depende de
como o fluxo de controle alcanou a funo-. Todas essas definies devem residir
no mesmo registrador e, assim, pertencer mesma faixa viva. As funes- permitem
que o compilador crie faixas vivas de modo eficiente.
Para criar faixas vivas a partir da forma SSA, o alocador usa o algoritmo de determinao e unio de conjuntos disjuntos, e faz uma nica passagem pelo cdigo. Ele

13.4 Alocao e atribuio globais de registradores 577

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.

FIGURA 13.3 Descobrindo faixas vivas.

Na Seo9.3.5, vimos que as transformaes aplicadas forma SSA podem introduzir


complicaes nesse processo de reescrita. Se o alocador constri a forma SSA, usa-a
para encontrar faixas vivas e reescreve o cdigo sem realizar outras transformaes,
ento pode simplesmente substituir nomes SSA por nomes de faixas vivas. Por outro
lado, se ele usa a forma SSA que j foi transformada, o processo de reescrita precisa
lidar com as complicaes descritas na Seo9.3.5. Como a maioria dos compiladores
realizar a alocao aps a seleo de instrues e, possivelmente, o escalonamento
de instrues, o cdigo que o alocador consome no estar na forma SSA. Isto fora
o alocador a criar a forma SSA para o cdigo e garante que o processo de reescrita
seja simples.

13.4.2 Estimativa de custos de derramamento globais


Para tomar decises bem informadas de derramamento, o alocador global precisa de
uma estimativa do custo de derramamento de cada valor. O custo de um derramamento
tem trs componentes: clculo de endereo, operao de memria e frequncia estimada de execuo.
O construtor de compiladores pode escolher onde na memria manter os valores
derramados. Normalmente, eles residem em uma designada rea de salvamento de

O compilador pode representar faixas vivas globais


como um conjunto de um ou mais nomes SSA.

578 CAPTULO 13 Alocao de registradores

registrador no registro de ativao (AR) atual para minimizar o custo do clculo de


endereo (ver Figura6.4). O armazenamento de valores derramados no AR permite que
o alocador gere operaes como um loadAI ou um storeAI relativo a rarp para o
derramamento. Essas operaes normalmente evitam a necessidade de registradores
adicionais para calcular o endereo de memria de um valor derramado.
Memria de rascunho
Memria local dedicada, no em cache, s vezes
chamada memria de rascunho.
Memria de rascunho um recurso de alguns processadores embutidos.

O custo da operao de memria, em geral, inevitvel. Para cada valor derramado,


o compilador deve gerar um store aps cada definio e um load antes de cada uso.
medida que as latncias de memria aumentam, os custos dessas operaes de derramamento aumentam. Se o processador-alvo tiver uma memria de rascunho rpida, o
compilador pode reduzir o custo dessas operaes derramando para a memria de rascunho. Para tornar as coisas piores, o alocador insere operaes de derramamento em
regies onde a demanda por registradores alta. Nestas regies, a falta de registradores
livres pode restringir a capacidade do escalonador de ocultar a latncia de memria.
Assim, o construtor de compiladores deve esperar que os locais de derramamento permaneam na cache. (Paradoxalmente, esses locais permanecem na cache somente se
forem acessados com frequncia suficiente para evitar a substituio sugerindo que
o cdigo est executando muitas operaes de derramamento.)

Contabilizando frequncias de execuo


Para contabilizar as diferentes frequncias de execuo dos blocos bsicos no grafo de
fluxo de controle, o compilador deve anotar cada bloco com uma contagem de execuo
estimada. Ele pode obter essas estimativas a partir de dados de perfil ou de heursticas.
Muitos compiladores simplesmente consideram que cada lao executado 10 vezes.
Esta suposio atribui um peso de 10 a um load dentro de um lao, 100 para um load
dentro de dois laos aninhados, e assim por diante. Um if-then-else no previsto
diminuiria a frequncia estimada pela metade. Na prtica, essas estimativas garantem
uma tendncia para o derramamento nos laos externos, ao invs dos internos.
Para estimar o custo de derramamento de uma nica referncia, o alocador acrescenta
o custo do clculo de endereo ao custo da operao de memria e multiplica essa
soma pela frequncia de execuo estimada da referncia. Para cada faixa viva, soma
os custos das referncias individuais, o que exige uma passagem por todos os blocos
no cdigo. O alocador pode pr-calcular esses custos para todas as faixas vivas, ou,
ento, esperar para calcul-los at descobrir que deve derramar pelo menos um valor.

Custos de derramamento negativos


Uma faixa viva que contm um load, um store e nenhum outro uso deve receber um
custo de derramamento negativo se o load e o store se referirem ao mesmo endereo.
(Essa faixa viva pode resultar de transformaes intencionadas para melhorar o cdigo; por exemplo, se o uso fosse otimizado e o store resultasse de uma chamada de
procedimento, ao invs da definio de um novo valor.) s vezes, o derramamento de
uma faixa viva pode eliminar operaes de cpia com um custo mais alto do que as
operaes de derramamento; essa faixa viva tambm tem um custo negativo. Qualquer
faixa viva com um custo de derramamento negativo deve ser derramada, pois isso
diminui a demanda por registradores e remove instrues do cdigo.

Custos de derramamento infinitos

Faixa viva com custo de derramamento infinito.

Algumas faixas vivas so to curtas que derram-las no ajuda. Se o alocador tentar


derramar vri, inserir um store aps a definio e um load antes do uso, criando duas
novas faixas vivas. Nenhuma dessas novas faixas vivas utiliza menos registradores do
que a original, de modo que o derramamento no produz qualquer benefcio. O alocador

13.4 Alocao e atribuio globais de registradores 579

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.

13.4.3 Interferncias e o grafo de interferncia


O efeito fundamental que um alocador de registradores global deve modelar a competio entre valores pelo espao no conjunto de registradores do processador. Considere
duas faixas vivas distintas, LRi e LRj. Se houver uma operao no programa durante a
qual tanto LRi quanto LRj estejam vivas, elas no podero residir no mesmo registrador.
(Em geral, um registrador fsico pode manter apenas um valor por vez.) Dizemos que
LRi e LRj interferem.
Para modelar o problema de alocao, o compilador pode construir um grafo de
interferncia I=(N,E), em que os ns em N representam faixas vivas individuais, e
as arestas em E representam interferncias entre faixas vivas. Assim, uma aresta no
orientada (ni, nj) I existe se, e somente se, as faixas vivas correspondentes LRi e
LRj interferirem. A Figura13.4 mostra o cdigo da Figura13.3b junto com seu grafo
de interferncia. Como o grafo mostra, LRa interfere com cada uma das outras faixas
vivas. As demais faixas vivas, porm, no interferem umas com as outras.

FIGURA 13.4 Faixas vivas e interferncia.

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.

580 CAPTULO 13 Alocao de registradores

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 do grafo de interferncia


Uma vez que o alocador tenha construdo faixas vivas globais e anotado cada bloco
bsico no cdigo com seu conjunto LIVEOUT, pode construir o grafo de interferncia
em uma passagem linear simples por cada bloco. A Figura13.5 mostra o algoritmo
bsico. Ao percorrer o bloco, de baixo para cima, o alocador calcula LIVENOW, o
conjunto de valores que esto vivos na operao atual. (Vimos LIVENOW na Seo11.5.1.) Na ltima operao do bloco, LIVEOUT e LIVENOW precisam ser
idnticos. Enquanto o algoritmo caminha de trs para a frente no bloco, ele acrescenta
as arestas de interferncia apropriadas ao grafo e atualiza o conjunto LIVENOW para
refletir o impacto da operao.

FIGURA 13.5 Construo do grafo de interferncia.

O algoritmo implementa a definio de interferncia dada anteriormente: LRi e LRj


interferem somente se uma estiver viva em uma definio da outra. Essa definio
permite que o compilador construa o grafo de interferncia acrescentando, a cada
operao, uma interferncia entre o alvo da operao, LRc, e cada faixa viva que est
viva aps a operao.
Operaes de cpia exigem tratamento especial. Uma cpia LRi LRj no cria interferncia entre LRi e LRj porque as duas faixas vivas tm o mesmo valor e, portanto,
podem ocupar o mesmo registrador. Assim, a operao no deve induzir uma aresta
(LRi,LRj) em E. Se o contexto subsequente criar uma interferncia entre estas faixas
vivas, essa operao criar a aresta. De modo semelhante, uma funo- no cria
interferncia entre qualquer um de seus argumentos e seu resultado. Tratar cpias e
funes- desse modo cria um grafo de interferncia que captura exatamente quando
LRi e LRj podem ocupar o mesmo registrador.
Para melhorar a eficincia do alocador, o compilador deve construir uma matriz de bits
diagonal inferior e um conjunto de listas de adjacncia para representar E. A matriz de bits
permite um teste de interferncia em tempo constante, enquanto as listas de adjacncia

13.4 Alocao e atribuio globais de registradores 581

permitem iterao eficiente pelos vizinhos de um n. A estratgia de duas representaes


usa mais espao do que uma nica representao usaria, mas compensa no tempo de
alocao reduzido. Conforme sugerido na Seo13.2.3, o alocador pode construir grafos
separados para classes de registrador disjuntas, o que reduz o tamanho mximo do grafo.

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.

Diviso de faixa viva


Se o alocador no puder manter uma faixa viva em
um registrador, pode quebrar a faixa viva em partes
menores, conectadas por cpias ou por loads e stores.
As novas faixas vivas menores podem caber nos registradores.

13.4.4 Colorao de cima para baixo


Um alocador de registradores global de colorao de grafo usa informaes de baixo
nvel para atribuir cores a faixas vivas individuais e informaes de alto nvel para
selecionar a ordem em que realiza a colorao das faixas vivas. Para encontrar uma cor
para uma faixa viva especfica LRi, o alocador conta as cores j atribudas aos vizinhos
de LRi em I. Se o conjunto de cores dos vizinhos estiver incompleto ou seja, uma ou
mais cores no forem usadas , o alocador pode atribuir uma cor no usada a LRi. Se
o conjunto estiver completo, ento nenhuma cor estar disponvel para LRi e o alocador
deve usar sua estratgia para faixas vivas no coloridas.
Os alocadores de cima para baixo tentam colorir as faixas vivas em uma ordem determinada por alguma funo de classificao. Os alocadores de cima para baixo baseados
em prioridade, atribuem a cada n uma classificao que a economia de runtime
estimada resultante de manter essa faixa viva em um registrador. Essas estimativas
so semelhantes aos custos de derramamento descritos na Seo13.4.2. O alocador
global de cima para baixo usa registradores para os valores mais importantes, conforme
identificados por essas classificaes.
O alocador considera as faixas vivas em ordem de classificao e tenta atribuir uma cor
a cada uma delas. Se nenhuma cor estiver disponvel para uma faixa viva, ele chama o
mecanismo de derramamento ou de diviso para lidar com a faixa viva no colorida.
Para melhorar o processo, o alocador pode particionar as faixas vivas em dois conjuntos
restritas e irrestritas. Uma faixa viva restrita se tiver k ou mais vizinhos ou
seja, tiver grauk em I. As faixas vivas restritas so coloridas primeiro, em ordem de
classificao. Aps todas terem sido tratadas, as faixas vivas irrestritas so coloridas,
em qualquer ordem. Como uma faixa viva irrestrita tem menos do que k vizinhos, o
alocador sempre pode encontrar uma cor para ela; nenhuma atribuio de cores para
seus vizinhos pode usar todas as k cores.
Tratando primeiro as faixas vivas restritas, o alocador evita alguns potenciais derramamentos. A alternativa, trabalhar em uma ordem de prioridade direta, permitiria que

Indicamos grau de LRi como LRi. LRi restrita se, e


somente se, LRi k.

582 CAPTULO 13 Alocao de registradores

o alocador atribusse todas as cores disponveis a vizinhos irrestritos de LRi, porm de


maior prioridade. Esta abordagem poderia forar LRi a permanecer no colorida, embora devam existir coloraes de seus vizinhos irrestritos que deixam uma cor para LRi.

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.

Diviso de faixa viva


O derramamento muda o problema de colorao. Uma faixa viva no colorida quebrada em uma srie de pequenas faixas vivas, uma em cada definio ou uso. Outra
forma de mudar o problema dividir uma faixa viva no colorida em novas faixas vivas
subfaixas que contm vrias referncias. Se estas novas faixas vivas interferirem
com menos faixas vivas do que acontecia com a faixa viva original, elas podem receber
cores. Por exemplo, algumas das novas faixas vivas podem ser irrestritas. A diviso
da faixa viva pode evitar o derramamento da faixa viva original em cada referncia;
com pontos de diviso bem escolhidos, ela pode isolar as partes da faixa viva que o
alocador precisa derramar.
O primeiro alocador de colorao de cima para baixo, baseado em prioridade, criado
por Chow, quebrava a faixa viva no colorida em faixas vivas de nico bloco, contava
interferncias para cada faixa viva resultante e ento recombinava faixas vivas de
blocos adjacentes se a faixa viva combinada permanecesse irrestrita. Ele colocava um
limite superior arbitrrio sobre o nmero de blocos pelos quais uma faixa viva dividida
poderia se espalhar. Ele inseria um load no ponto inicial de cada faixa viva dividida
e um store no ponto final da faixa viva. O alocador derramava quaisquer faixas vivas
divididas que continuassem sem cor.

13.4 Alocao e atribuio globais de registradores 583

13.4.5 Colorao de baixo para cima


Os alocadores do registradores de colorao de grafo de baixo para cima utilizam
muitos dos mesmos mecanismos dos alocadores globais de cima para baixo. Eles descobrem faixas vivas, constroem um grafo de interferncia, tentam colori-lo e geram
cdigo de derramamento quando necessrio. A principal distino entre os alocadores
de cima para baixo e de baixo para cima est no mecanismo usado para ordenar faixas
vivas para colorao. Enquanto o alocador de cima para baixo usa informaes de
alto nvel para selecionar uma ordem para colorao, um alocador de baixo para cima
calcula uma ordem a partir do conhecimento estrutural detalhado sobre o grafo de
interferncia. Tal alocador constri uma ordem linear para considerar as faixas vivas
e atribuir cores nessa ordem.
Para ordenar as faixas vivas, um alocador por colorao de grafo de baixo para
cima conta com o fato de que as faixas vivas irrestritas so triviais para se colorir.
Ele atribui cores em uma ordem onde cada n tem menos de k vizinhos coloridos.
O algoritmo calcula a ordem de colorao para um grafo I=(N, E) da seguinte
forma:

O alocador repetidamente remove um n do grafo e coloca o n em uma pilha. Ele usa


dois mecanismos distintos para selecionar o n a ser removido em seguida. O primeiro
(clusula then) apanha um n que irrestrito no grafo do qual foi removido. Como
esses ns so irrestritos, a ordem em que so removidos no importa. A remoo de um
n irrestrito diminui o grau de cada um de seus vizinhos e pode torn-los irrestritos. O
segundo (clusula else), invocado somente quando cada n restante restrito, apanha
um n usando alguns critrios externos. Qualquer n removido por esse mecanismo tem
mais de k vizinhos e, portanto, no pode receber uma cor durante a fase de atribuio.
O lao termina quando o grafo est vazio. Neste ponto, a pilha contm todos os ns
em ordem de remoo.
Para colorir o grafo, o alocador reconstri o grafo de interferncia na ordem representada pela pilha o reverso da ordem em que o alocador os removeu do grafo. Ele
repetidamente retira um n n da pilha, insere n e suas arestas de volta a I e apanha uma
cor para n. O algoritmo :

584 CAPTULO 13 Alocao de registradores

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.

Por que isso funciona?


O alocador de baixo para cima insere cada n de volta ao grafo do qual foi removido.
Se o algoritmo de reduo remove o n representando LRi de I por meio de sua primeira
clusula (pois ele estava irrestrito no momento da remoo), ento reinsere LRi em um
grafo em que ele tambm irrestrito. Assim, quando o alocador insere LRi, uma cor
deve estar disponvel para LRi. A nica maneira de um n n poder deixar de receber
uma cor se n foi removido de I usando a mtrica de derramamento. Este n inserido
em um grafo no qual ele tem k ou mais vizinhos. Porm, uma cor ainda poder estar
disponvel para n. Suponha que n>k quando o alocador o insere em I. Seus vizinhos
no podem ter, todos eles, cores distintas, pois podem ter no mximo k cores. Se tiverem
exatamente k cores, o alocador no encontra uma cor para n. Se, ao invs disso, usarem
menos de k cores, o alocador encontra uma cor disponvel para n.
O algoritmo de reduo determina a ordem em que os ns so coloridos. Essa ordem
crucial, pois determina se as cores esto disponveis ou no. Para ns removidos do
grafo porque so irrestritos, a ordem no importante com relao aos ns restantes.
A ordem pode ser importante com relao aos ns que j esto na pilha; afinal, o n
atual pode ter sido restrito at alguns dos ns anteriores serem removidos. Para os ns
removidos do grafo usando a clusula else, a ordem fundamental. Essa clusula
executada somente quando cada n restante for restrito. Assim, os restantes formam
um ou mais subgrafos de I altamente conectados.
A heurstica usada pela clusula else para escolher um n normalmente chamada mtrica de derramamento. O alocador de colorao de grafo de baixo para cima original,
criado por Chaitin e outros, usava uma mtrica de derramamento simples. Ele apanhava
um n que minimizava a razo de custo onde custo o custo estimado
grau

de derramamento, e grau o grau do n no grafo atual. Essa mtrica um equilbrio


entre custo de derramamento e o nmero de ns cujo grau diminuir.
Outras mtricas de derramamento foram tentadas. Estas incluem

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

e impacto; os ltimos dois, custo e operaes de derramamento, visam otimizar critrios


especficos. Na prtica, nenhuma heurstica isolada domina as outras. Como o processo

13.4 Alocao e atribuio globais de registradores 585

de colorao real rpido em relao construo de I, o alocador pode tentar vrias


coloraes, cada uma usando uma mtrica de derramamento diferente, e reter o melhor
resultado.

13.4.6 Agrupamento de cpias para reduzir o grau


O construtor de compiladores pode usar o grafo de interferncia para determinar quando
duas faixas vivas conectadas por uma cpia podem ser agrupadas, ou combinadas.
Considere a operao i2i LRi LRj. Se LRi e LRj no interferirem de outra forma,
a operao pode ser eliminada e todas as referncias a LRj reescritas para usar LRi. A
combinao dessas faixas vivas tem vrios efeitos benficos: elimina a operao de
cpia, tornando o cdigo menor e, potencialmente, mais rpido; reduz o grau de qualquer LRi que interferisse com LRi e LRj; encurta o conjunto de faixas vivas, tornando I
e muitas das estruturas de dados relacionadas a I menores. (Em sua tese, Briggs mostra
exemplos nos quais o agrupamento elimina at um tero das faixas vivas.) Como esses
efeitos ajudam na alocao, os compiladores normalmente realizam o agrupamento
antes do estgio de colorao em um alocador global.
A Figura13.6 mostra um exemplo. O cdigo original aparece no painel a, com as linhas direita do cdigo, que indicam as regies onde cada um dos valores relevantes,
LRa, LRb e LRc, esto vivos. Embora LRa sobreponha LRb e LRc, no interfere com
qualquer um deles, pois a origem e o destino de uma cpia no interferem. Como LRb
est viva na definio de LRc, elas no interferem. As duas operaes de cpia so
candidatas ao agrupamento.

FIGURA 13.6 Agrupamento de faixas vivas.

A Figura13.6b mostra o resultado do agrupamento de LRa e LRb para produzir LRab.


Como LRc definida por uma cpia de LRab, elas no interferem. A combinao de
LRa e LRb para formar LRab reduziu o grau de LRc. Em geral, o agrupamento de duas
faixas vivas no pode aumentar os graus de qualquer um de seus vizinhos; pode, sim,
diminuir seus graus ou deix-los inalterados, mas nunca aument-los.
Para realizar o agrupamento, o alocador percorre cada bloco e examina cada operao
de cpia no bloco. Considere uma cpia i2i LRi LRj. Se LRi e LRj no interferem,
(LRi, LRj) E, o alocador as combina, elimina a cpia e atualiza I para refletir a
combinao. O alocador pode atualizar I de modo conservador movendo todas as
arestas do n para LRj at o n para LRi com efeito, usando LRi como LRij. Essa
atualizao no exata, mas permite que o alocador continue agrupando. Na prtica,
os alocadores agrupam cada faixa viva permitida por I, depois reescrevem o cdigo,
recriam I e tentam novamente. O processo normalmente termina depois de algumas
rodadas de agrupamento.

586 CAPTULO 13 Alocao de registradores

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.

13.4.7 Comparao de alocadores globais de cima para baixo


e de baixo para cima
Os alocadores por colorao de cima para baixo e de baixo para cima tm a mesma
estrutura bsica, mostrada na Figura13.7. Eles encontram faixas vivas, criam o grafo
de interferncia, agrupam faixas vivas, calculam custos de derramamento sobre a verso
agrupada do cdigo e tentam uma colorao. O processo de criar-agrupar repetido
at que no encontre mais oportunidades. Depois da colorao, ocorre uma dentre duas
situaes. Se o alocador atribui uma cor a cada faixa viva, ento ele reescreve o cdigo
usando nomes de registradores fsicos e a alocao termina. Se algumas faixas vivas
permanecem sem cor, ento ele insere cdigo de derramamento.
Se o alocador tiver reservado registradores para derramamento, ento os usa no cdigo
de derramamento, reescreve os registradores coloridos com seus nomes de registradores
fsicos e o processo termina. Caso contrrio, ele inventa novos nomes de registrador
virtual para usar no derramamento e insere os loads e stores necessrios para realizar

FIGURA 13.7 Estrutura dos alocadores de colorao.

13.4 Alocao e atribuio globais de registradores 587

os derramamentos. Isto muda o problema de colorao ligeiramente, de modo que o


processo de alocao inteiro repetido no cdigo transformado. Quando cada faixa
viva tem uma cor, o alocador mapeia cores a registradores reais e reescreve o cdigo
em sua forma final.
Naturalmente, um alocador de cima para baixo poderia adotar a filosofia de derramar-e
-iterar usada no alocador de baixo para cima, o que eliminaria a necessidade de reservar
registradores para derramamento. De modo semelhante, um alocador de baixo para
cima poderia reservar vrios registradores para derramamento e eliminar a necessidade
de iterao do processo de alocao inteiro. Derramar-e-iterar consome mais tempo
de compilao em troca de uma alocao que, potencialmente, usa menos cdigo de
derramamento. Reservar registradores produz uma alocao que, potencialmente,
contm mais derramamentos, mas exige menos tempo de compilao para produzir.
O alocador de cima para baixo usa sua classificao de prioridade para ordenar todos
os ns restritos. Ele faz a colorao dos ns irrestritos em uma ordem arbitrria, pois a
ordem no pode mudar o fato de que recebem uma cor. O alocador de baixo para cima
constri uma ordem em que a maioria dos ns colorida em um grafo onde so irrestritos. Cada n que o alocador de cima para baixo classifica como irrestrito colorido
pelo alocador de baixo para cima, pois irrestrito no grafo original e em cada grafo
derivado pela remoo de ns e arestas de I. O alocador de baixo para cima tambm
classifica alguns ns como irrestritos, que o alocador de cima para baixo trata como
restritos. Esses ns tambm podem ser coloridos no alocador de cima para baixo; no
existe um modo claro de comparar seus desempenhos nesses ns sem implementar os
dois algoritmos e execut-los.
Os ns verdadeiramente difceis de colorir so aqueles que o alocador de baixo para
cima remove do grafo com sua mtrica de derramamento. Esta mtrica invocada
somente quando cada n restante restrito. Esses ns formam um subgrafo fortemente
conectado de I. No alocador de cima para baixo, esses ns sero coloridos em uma
ordem determinada por sua classificao ou prioridade. No alocador de baixo para cima,
a mtrica de derramamento usa esta mesma classificao, moderada por uma medio
de quantos outros ns tm seu grau reduzido por cada escolha. Assim, o alocador de
cima para baixo escolhe derramar ns restritos, de baixa prioridade, enquanto o alocador
de baixo para cima derrama ns que ainda so restritos aps todos os ns irrestritos
terem sido removidos. A partir deste ltimo conjunto, ele apanha os ns que minimizam
a mtrica de derramamento.

ALOCAO DE VARREDURA LINEAR


Os alocadores de varredura linear comeam da suposio de que podem representar
faixas vivas globais com um intervalo simples [i,j], como fizemos na alocao local.
Esta representao superestima a extenso da faixa viva para garantir que ela inclui a
primeira e a ltima operao onde a faixa viva est viva. A superestimativa garante que
o grafo de interferncia resultante um grafo de intervalo.
Grafos de intervalo so muito mais simples do que os grafos gerais que surgem na
alocao de registradores global; por exemplo, o grafo de interferncia de um nico
bloco sempre um grafo de intervalo. Sob o ponto de vista da complexidade, grafos
de intervalo oferecem vantagens ao alocador. Embora o problema de determinar se
um grafo arbitrrio k-colorvel NP-completo, o mesmo problema solucionvel em
tempo linear em um grafo de intervalo.
A representao de intervalo menos dispendiosa de se construir do que o grafo
de interferncia preciso. Os grafos de intervalo prestam-se para algoritmos de

588 CAPTULO 13 Alocao de registradores

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.

13.4.8 Codificao de restries de mquina


no grafo de interferncia
A alocao de registradores deve lidar com propriedades idiossincrticas da mquina
alvo e sua conveno de chamada. Algumas das restries que surgem na prtica podem
ser codificadas no processo de colorao.

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

13.4 Alocao e atribuio globais de registradores 589

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.

Posicionamento de registrador especfico


O alocador de registradores tambm precisa lidar com os requisitos para o posicionamento especfico de faixas vivas. Essas restries surgem de vrias fontes. A conveno
de ligao dita o posicionamento de valores que so passados em registradores; o que
pode incluir o ARP, alguns ou todos os parmetros reais e o valor de retorno. Algumas
operaes podem exigir seus operandos em registradores particulares; por exemplo, a
multiplicao curta sem sinal (short unsigned) nas mquinas Intel x86 sempre escreve
seu resultado no registrador ax.
Como um exemplo das complicaes que surgem dos registradores atribudos na ligao
de procedimento, considere a conveno tpica em um processador PowerPC. Por
conveno, o valor de retorno de uma funo deixado em r3. Suponha que o cdigo
que est sendo compilado tem uma chamada de funo e que o cdigo representa o valor
de retorno como vri. O alocador pode forar vri para r3 acrescentando arestas de
vri para cada registrador fsico, exceto r3; essa modificao do grafo de interferncia
garante que a cor correspondente a r3 a nica disponvel para vri. Essa soluo,
porm, pode restringir demais o grafo de interferncia.
Para ver o problema, suponha que o cdigo que est sendo compilado tenha duas
chamadas de funo e que o cdigo represente os valores de retorno como vr i e
vrj. Se vri estiver vivo at a outra chamada, o cdigo final no poder manter vri
e vrj em r3. Restringir os dois registradores virtuais para mapearem em r3 forar o
derramamento de um ou de ambos.
A soluo para este problema contar com a forma do cdigo. O compilador pode
criar uma faixa viva curta para o valor de retorno em cada chamada; digamos que use
vr 1 na primeira chamada e vr 2 na segunda. Ele pode restringir tanto vr 1 quanto
vr2 de modo que eles mapeiem exclusivamente para r3; e acrescentar operaes de
cpia, vr1 vri e vr2 vrj. Esta tcnica cria cdigo correto que desacopla vri e
vrj de r3. Naturalmente, o alocador precisa restringir o mecanismo de agrupamento
para evitar combinar faixas vivas com restries de registrador fsico conflitantes; na
prtica, o compilador poderia evitar agrupar qualquer faixa viva que tenha interferncias
explcitas com registradores fsicos.
Como um exemplo das restries de registrador fsico que uma ISA pode impor,
considere a operao de multiplicao de inteiros de um endereo em um processador
Intel x86. Ela usa o registrador ax como seu segundo argumento implcito e como
seu registrador de resultado. Considere o mapeamento da sequncia IR mostrada na
margem para o cdigo x86. O compilador poderia restringir vr2, vr1 e vr5 de modo
que sejam mapeados para o registrador ax. Neste caso, o processo poderia produzir
uma sequncia de cdigo semelhante ao cdigo pseudoassembly esquerda, com os
nomes de registrador virtual, vri, substitudos por seus locais de runtime reais. Desde
que as faixas vivas mapeadas para ax sejam curtas, esta estratgia pode produzir cdigo
de alta qualidade. Novamente, o agrupamento precisa ser restrito em quaisquer faixas
vivas que sobreponham outras operaes que exigem ax.

590 CAPTULO 13 Alocao de registradores

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?

13.5 TPICOS AVANADOS


Como o custo de um passo em falso durante a alocao de registradores pode ser alto, os
algoritmos para alocao de registradores tm recebido muita ateno. Muitas variaes
sobre as tcnicas bsicas de alocao de colorao de grafo tm sido publicadas. A
Seo13.5.1 descreve diversas dessas tcnicas. A Seo13.5.2 esboa outra tcnica
promissora: uso de nomes SSA como faixas vivas em um alocador global.

13.5.1 Variaes sobre a alocao de colorao de grafo


Muitas variaes sobre esses dois estilos bsicos de alocao de registradores por
colorao de grafo apareceram na literatura. Esta seo descreve vrias dessas melhorias. Algumas enfatizam o custo de alocao. Outras tratam da qualidade da alocao.

Grafos de interferncia imprecisos


O alocador de cima para baixo, baseado em prioridade, de Chow usava uma noo
imprecisa da interferncia: faixas vivas LRi e LRj interferem se ambas estiverem vivas
no mesmo bloco bsico. Isto torna mais rpida a criao do grafo de interferncia.
Porm, a natureza imprecisa do grafo superestima o grau de alguns ns e impede que
o alocador use o grafo de interferncia como base para o agrupamento. (Em um grafo
impreciso, duas faixas vivas conectadas por uma cpia til interferem, pois esto vivas
no mesmo bloco.) O alocador tambm incluiu um pr-processamento para realizar
alocao local de valores que esto vivos em apenas um bloco.

13.5 Tpicos avanados 591

Quebrando o grafo em partes menores


Se o grafo de interferncia puder ser separado em componentes que no esto
conectados, esses componentes disjuntos podem ser coloridos independentemente.
Como o tamanho da matriz de bits O(N2), quebr-la em componentes independentes economiza espao e tempo. Um modo de dividir o grafo considerar classes
de registrador no sobrepostas separadamente, como com registradores de ponto
flutuante e registradores de inteiros. Uma alternativa mais complexa para grandes
procedimentos descobrir cliques separadores, subgrafos conectados cuja remoo
divide o grafo de interferncia em vrias partes disjuntas. Para grafos grandes o
suficiente, o uso de uma tabela hash ao invs da matriz de bits pode melhorar a
velocidade e o espao.

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.

Derramamento de faixas vivas parciais


Conforme descrevemos, as duas tcnicas para alocao global derramam faixas vivas
inteiras. Esta tcnica pode levar ao superderramamento se a demanda por registradores
for baixa pela maior parte da faixa viva e alta em uma regio pequena. Tcnicas de
derramamento mais sofisticadas encontram as regies onde o derramamento de uma
faixa viva produtivo ou seja, o derramamento libera um registrador em uma regio
onde ele seja verdadeiramente necessrio. O esquema de derramamento descrito para
o alocador de cima para baixo alcanou este resultado considerando cada bloco na
faixa viva derramada separadamente. Um alocador de baixo para cima pode alcanar

Agrupamento conservador
Forma de agrupamento que s combina LRi e LRj se LRij
receber uma cor.

592 CAPTULO 13 Alocao de registradores

resultados semelhantes derramando apenas na regio onde ocorre a interferncia. Uma


tcnica, chamada derramamento por regio de interferncia, identifica um conjunto
de faixas vivas que interferem na regio de alta demanda e limita o derramamento a
essa regio. O alocador pode estimar os custos de vrias estratgias de derramamento
para a regio de interferncia e compar-los com a tcnica padro de derramamento
em todo lugar. Permitindo que as alternativas concorram com base no custo estimado,
o alocador pode melhorar a alocao global.

Diviso de faixa viva


Dividir uma faixa viva em partes pode melhorar os resultados da alocao de registradores baseada em colorao. Em princpio, a diviso aproveita dois efeitos distintos.
Se as faixas vivas divididas tiverem graus menores do que a original, elas podem ser
mais fceis de colorir possivelmente at mesmo irrestritas. Se alguma das faixas
vivas divididas tiver grau alto e, portanto, derrama, ento a diviso pode impedir o
derramamento de outras partes da mesma faixa viva que possuem grau menor. Como
efeito final, pragmtico, a diviso introduz derramamentos nos pontos onde a faixa viva
dividida. A seleo cuidadosa dos pontos de diviso pode controlar o posicionamento
de algum cdigo de derramamento por exemplo, fora dos laos, ao invs de dentro
deles.
Muitas tcnicas para a diviso foram experimentadas. A Seo13.4.4 descreve uma que
divide uma faixa viva em blocos e os agrupa novamente se isto no mudar a capacidade
do alocador de atribuir uma cor. Vrias tcnicas que usam propriedades do grafo de
fluxo de controle para escolher pontos de diviso foram experimentadas. Briggs mostrou
que muitas tm sido inconsistentes [45]; porm, duas em particular parecem ser promissoras. Um mtodo chamado diviso de custo zero aproveita nops no escalonamento de
instrues para dividir faixas vivas e melhorar a alocao e o escalonamento. A tcnica
chamada diviso passiva usa um grafo orientado de interferncia para determinar onde
as divises devem ocorrer e seleciona entre diviso e derramamento com base em seus
custos estimados.

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.

13.5 Tpicos avanados 593

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.

13.5.2 Alocao de registradores global sobre a forma SSA


A complexidade da alocao de registradores global aparece de vrias maneiras. Na
formulao de colorao de grafo, esta complexidade apresenta-se no fato de que o
problema de determinar se existe uma k-colorao de um grafo geral NP-completo.
Para classes de grafos restritas, o problema de colorao tem solues de tempo
polinomial. Por exemplo, os grafos de intervalo gerados por um bloco bsico podem
ser coloridos em tempo linear no tamanho do grafo. Para aproveitar este fato, os
alocadores de varredura linear aproximam as faixas vivas globais por intervalos simples que produzem um grafo de intervalo.
Se o compilador criar um grafo de interferncia a partir de nomes SSA ao invs de faixas
vivas, o resultado um grafo cordal. O problema da k-colorao de um grafo cordal
pode ser resolvido em tempo O(|V|+|E|). Esta observao tem despertado o interesse
na alocao de registradores global sobre a forma SSA do cdigo.
Trabalhar a partir da forma SSA simplifica algumas partes do alocador de registradores.
O alocador pode calcular uma colorao tima para seu grafo de interferncia, ao invs
de contar com tcnicas heursticas para tanto. A colorao tima pode usar menos
registradores do que a heurstica de colorao usaria.
Se o grafo precisar de mais de k cores, o alocador ainda deve derramar um ou mais
valores. Embora a forma SSA no reduza a complexidade da escolha de derramamento,
pode oferecer alguns benefcios. As faixas vivas globais tendem a ter tempos de vida
maiores do que os nomes SSA, que so divididos por funes- nos locais apropriados
do cdigo, como cabealhos de lao e blocos que seguem laos. Essas divises do
ao alocador a chance de derramar valores por regies menores do que poderia ocorrer
com faixas vivas globais.
Infelizmente, a alocao baseada em SSA deixa o cdigo na forma SSA. O alocador, ou
um ps-processamento, precisa traduzir a partir desta forma, com todas as complicaes

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.

594 CAPTULO 13 Alocao de registradores

discutidas na Seo9.3.5. Essa traduo pode aumentar a demanda por registradores.


(Se a traduo tiver de quebrar um ciclo de cpias concorrentes, precisa de um registrador adicional para fazer isso.) Um alocador baseado em SSA precisa estar preparado
para lidar com esta situao.
Igualmente importante, esta traduo insere operaes de cpia no cdigo; algumas
dessas cpias podem ser irrelevantes. O alocador no pode agrupar cpias que implementam o fluxo de valores correspondentes a uma funo-; porque destruiria a
propriedade cordal do grafo. Assim, um alocador baseado em SSA provavelmente
usaria um algoritmo de agrupamento que no baseado no grafo de interferncia.
Existem vrios algoritmos fortes.
difcil avaliar os mritos de um alocador baseado em SSA contra um alocador
baseado em faixas vivas globais tradicionais. O alocador baseado em SSA tem potencial para obter uma colorao melhor do que o alocador tradicional, mas faz isso em
um grafo diferente. Os dois alocadores precisam resolver os problemas da escolha e
posicionamento de derramamento, o que pode contribuir mais para o desempenho do
que a colorao real. Os dois alocadores usam tcnicas diferentes para agrupamento
de cpia. Como qualquer alocador de registradores, os detalhes de baixo nvel reais da
implementao tero importncia.

13.6 RESUMO E PERSPECTIVA


Como a alocao de registradores uma parte importante de um compilador moderno,
tem recebido muita ateno na literatura. Existem tcnicas fortes para a alocao local
e global. Como muitos dos problemas subjacentes so NP-difceis, as solues tendem
a ser sensveis a pequenas decises, por exemplo, como desempatar entre escolhas
igualmente classificadas.
O progresso na alocao de registradores tem vindo do uso de paradigmas que fornecem
uma elevao intelectual do problema. Assim, os alocadores de colorao de grafo tm
sido populares, no porque a alocao de registradores idntica colorao de grafo,
mas porque a colorao captura alguns dos aspectos crticos do problema de alocao
global. Na verdade, muitas das melhorias nos alocadores de colorao vieram do ataque
aos pontos onde o paradigma de colorao no reflete com preciso o problema bsico,
como melhores modelos de custo e mtodos melhorados para diviso de faixa viva.
Com efeito, essas melhorias tm feito o paradigma se ajustar melhor ao problema real.

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

13.6 Resumo e perspectiva 595

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.

596 CAPTULO 13 Alocao de registradores

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.

13.6 Resumo e perspectiva 597

c. Sugira um esquema que permita que alguns dos valores em LIVEOUT(A)


permaneam em registradores, evitando seus loads iniciais nos blocos sucessores.
Seo 13.4
4. Considere o seguinte grafo de interferncia:

Suponha que a mquina-alvo tenha apenas trs registradores.


a. Aplique o algoritmo de colorao global de baixo para cima ao grafo. Quais
registradores virtuais so derramados? Quais so coloridos?
b. A escolha do n de derramamento faz diferena?
c. Os alocadores de colorao mais antigos derramavam qualquer faixa viva
que estivesse restrita quando selecionada. Ao invs de aplicar o algoritmo
mostrado na Figura 13.8, eles usavam o mtodo a seguir:

Se isso marcar qualquer n para derramamento, o alocador insere o cdigo de


derramamento e repete o processo de alocao para o programa modificado. Se
nenhum n for marcado para derramamento, ele prossegue para atribuir cores da
forma descrita no alocador global de baixo para cima.
O que acontece quando voc aplica esse algoritmo ao grafo de interferncia do
exemplo? O mecanismo usado para escolher um n para derramamento muda o
resultado?
5. Aps a alocao de registradores, uma anlise cuidadosa do cdigo pode descobrir que, em alguns trechos do cdigo, existem registradores no usados. Em
um alocador global de baixo para cima, de colorao de grafo, isto ocorre por
causa de deficincias detalhadas no modo como as faixas vivas so derramadas.
a. Explique como esta situao pode surgir.
b. Como o compilador poderia descobrir se esta situao ocorre e onde ela
ocorre?
c. O que poderia ser feito para usar esses registradores no usados, tanto dentro
do framework global quanto fora dele?
6. Quando um alocador de colorao de grafo alcana o ponto onde nenhuma cor
est disponvel para uma determinada faixa viva, LRi, ele derrama ou divide essa
faixa viva. Como uma alternativa, ele pode tentar recolorir um ou mais dos vizinhos da LRi. Considere o caso onde (LRi,LRj) I e (LRi, LRk) I, mas (LRj,
LRk) I. Se LRj e LRk j tiverem sido coloridos, e recebido cores diferentes, o

598 CAPTULO 13 Alocao de registradores

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

Em um procedimento com um nico bloco longo, ou um nico bloco longo


dentro de um aninhamento de lao, o custo de derramamento para uma faixa viva
se aproxima de sua contagem de frequncia. Assim, uma faixa viva que muito
usada no incio e fim do bloco longo, mas no referenciada no meio, amarra um
registrador pelo bloco inteiro.
Como voc poderia modificar o alocador de baixo para cima de modo que
seu comportamento de derramamento em blocos longos seja mais parecido ao
comportamento do algoritmo local de baixo para cima do que do algoritmo local
de cima para baixo?

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

600 APNDICE A ILOC

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

calcula um endereo somando 4 ao contedo de rj e armazena o valor encontrado em


ri no local de memria especificado pelo endereo. Em outras palavras,

A.3 Operaes individuais 601

As operaes de fluxo de controle tm uma sintaxe ligeiramente diferente. Como elas


no definem seus destinos, as escrevemos com uma seta simples, , ao invs de .

A primeira operao, cbr, implementa um desvio condicional. As outras duas operaes so desvios incondicionais, chamados saltos.

A.2 CONVENES DE NOMEAO


O cdigo ILOC nos exemplos de texto utiliza um conjunto simples de convenes de
nomeao.
1. Deslocamentos (offsets) de memria para variveis so representados simbolicamente prefixando o nome da varivel com o caractere @.
2. O usurio pode considerar um estoque ilimitado de registradores. Estes so
nomeados com inteiros simples, como em r1776, ou com nomes simblicos,
como em ri.
3. O registrador rarp reservado como um ponteiro para o registro de ativao
atual. Assim, a operao

carrega o contedo da varivel x, armazenada no deslocamento @x a partir de ARP, em r1.


Comentrios ILOC comeam com a sequncia // e continuam at o final de uma linha.
Consideramos que estes so retirados pelo scanner; assim, podem ocorrem em qualquer
lugar em uma instruo e no so mencionados na gramtica.

A.3 OPERAES INDIVIDUAIS


Os exemplos no livro utilizam um conjunto limitado de operaes ILOC. As tabelas
ao final deste apndice mostram o conjunto de todas as operaes ILOC usadas no
livro, exceto pela sintaxe alternativa de desvio, utilizada no Captulo7 para discutir o
impacto de diferentes formas de construes de desvio.

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

602 APNDICE A ILOC

Todas essas operaes leem seus operandos de origem a partir de registradores ou


constantes e escrevem seu resultado de volta para um registrador. Qualquer registrador
pode servir como operando de origem ou de destino.
As quatro primeiras so operaes padro de registrador-para-registrador. As seis
seguintes especificam um operando imediato. As operaes no comutativas, sub
e div, tm duas formas imediatas para permitir o operando imediato em qualquer
lado do operador. As formas imediatas so teis para expressar os resultados de certas
otimizaes, escrever exemplos de forma mais concisa e registrar maneiras bvias de
reduzir a demanda por registradores.
Observe que um processador real baseado em ILOC precisaria de mais de um tipo de
dado, o que levaria a cdigos de operao tipados ou a cdigos de operao polimrficos. Preferimos uma famlia de opcodes tipados um add de inteiro, um add de ponto
flutuante e assim por diante. O compilador de pesquisa do qual o ILOC foi originado
tem operaes aritmticas distintas para inteiro, ponto flutuante de preciso simples,
ponto flutuante de preciso dupla, complexo e dados de ponteiro, mas no para dados
de caractere.

A.3.2 Deslocamentos (Shifts)


ILOC admite um conjunto de operaes de deslocamento aritmtico esquerda e
direita, nas formas de registrador e imediata.
Opcode

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

A.3.3 Operaes de memria


Para mover valores entre memria e registradores, ILOC admite um conjunto completo de operaes load e store. As operaes load e cload movem itens de dados
da memria para registradores.
Opcode

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

As operaes diferem nos modos de endereamento que admitem. As formas load e


cload consideram que o endereo completo est no nico operando de registrador.
As formas loadAI e cloadAI acrescentam um valor imediato ao contedo do
registrador para formar um endereo imediato antes de realizar o load. Chamamos
essas formas de operaes de endereo-imediato. As formas loadAO e cloadAO

A.3 Operaes individuais 603

acrescentam o contedo de dois registradores para calcular um endereo efetivo antes


de realizar o load. Chamamos essas formas de operaes de endereo-deslocamento
(offset).
Como uma forma final de load, o ILOC admite uma operao de load imediato simples.
Ela usa um inteiro do fluxo de instrues e o coloca em um registrador.
Opcode
loadI

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

No existe uma operao de store imediato.

A.3.4 Operaes de cpia registrador-para-registrador


Para mover valores entre registradores sem passar pela memria, o ILOC inclui um
conjunto de operaes de cpia de registrador-para-registrador.
Opcode

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

As duas primeiras operaes, i2i e c2c, copiam um valor de um registrador para


outro, sem converso; o primeiro para uso com valores inteiros; o segundo, para
caracteres. As duas ltimas operaes realizam converses entre caracteres e inteiros,
substituindo um caractere por sua posio ordinal no conjunto de caracteres ASCII e
um inteiro pelo caractere ASCII correspondente.

604 APNDICE A ILOC

A.4 OPERAES DE FLUXO DE CONTROLE


Em geral, os operadores de comparao ILOC usam dois valores e retornam um
valor booleano. Se o relacionamento especificado for vlido entre seus operandos, a
comparao estabelece o registrador de destino com o valor verdadeiro; caso contrrio,
o registrador de destino recebe o valor falso.
Opcode

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

A operao de desvio condicional, cbr, usa um booleano como argumento e transfere


o controle para um de dois rtulos de destino. O primeiro selecionado se o booleano
for verdadeiro; o segundo, se o booleano for falso. Como os dois destinos de desvio
no so definidos pela instruo, mudamos a sintaxe ligeiramente. Ao invs de usar
a seta , escrevemos desvios com a seta simples .
Todos os desvios no ILOC tm dois rtulos. Esta tcnica elimina um desvio seguido
por um salto e torna o cdigo mais conciso. Tambm elimina quaisquer caminhos
fall-through; tornando-os explcitos, ela remove qualquer dependncia posicional e
simplifica a construo do grafo de fluxo de controle.

A.4.1 Sintaxe alternativa de comparao e desvio


Para discutir a forma de cdigo em processadores que usam um cdigo de condio,
devemos introduzir uma sintaxe alternativa de comparao e desvio. O esquema de
cdigo de condio simplifica a comparao e empurra a complexidade para a operao
de desvio condicional.

A.4 Operaes de fluxo de controle 605

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

A forma salto-para-registrador uma transferncia ambgua de fluxo de controle.


Quando tiver sido gerada, o compilador pode ser incapaz de deduzir o conjunto correto
de rtulos de destino para o salto. Por este motivo, o compilador deve evitar, se possvel, usar o salto para registrador.
s vezes, as voltas necessrias para evitar um salto para registrador so to complexas que o salto para registrador se torna atraente, apesar de seus problemas. Por
exemplo, FORTRAN inclui uma construo que salta para uma varivel de rtulo; sua
implementao com desvios imediatos exigiria uma lgica semelhante a uma instruo
case uma srie de desvios imediatos, junto com o cdigo para fazer a correspondncia
entre o valor de runtime da varivel de rtulo e o conjunto de rtulos possveis. Em tais
circunstncias, o compilador provavelmente usaria um salto para registrador.

606 APNDICE A ILOC

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:

A.5 REPRESENTAO DA FORMA SSA


Quando um compilador constri a forma SSA de um programa a partir de sua verso
IR, ele precisa de um modo para representar funes-. Em ILOC, o modo natural
de escrever uma funo- como uma operao ILOC. Assim, s vezes escrevemos

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

Usado para preencher espao


(placeholder)
r1+r2 r3
r1-r2 r3
r1r2 r3
r1 r2 r3
r1+c2 r3
r1-c2 r3
c2-r1 r3
r1c2 r3
r1 c2 r3
c2 r1 r3
r1 << r2 r3
r1 << c2 r3
r1 >> r2 r3
r1 >> c2 r3
r1 r2 r3
r1 c2 r3
r1 r2 r3
r1 c2 r3
r1 xor r2 r3

r2
r2
r2
r2
c2
c2
c2
c2
c2
c2
r2
c2
r2
c2
r2
c2
r2
c2
r2

A.5 Representao da forma SSA 607

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

Operaes de fluxo de controle ILOC


Opcode

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

B.2 REPRESENTAO DE CONJUNTOS


Muitos e diferentes problemas na compilao so formulados em termos que envolvem
conjuntos. Eles surgem em muitos pontos no texto, incluindo a construo de subconjunto (Captulo2), a construo da coleo cannica de itens LR(1) (Captulo3),
anlise de fluxo de dados (Captulos8 e9) e listas de trabalho, como a fila Ready no
escalonamento de lista (Captulo12). Em cada contexto, o construtor de compiladores precisa selecionar uma representao de conjunto apropriada. Em muitos casos,
a eficincia do algoritmo depende da seleo cuidadosa de uma representao de
conjunto. (Por exemplo, a estrutura de dados IDoms na computao de dominncia
representa todos os conjuntos de dominadores, bem como os dominadores imediatos,
em um array compacto.)
Uma diferena fundamental entre a construo de um compilador e a de outros tipos de
software de sistemas como um sistema operacional que muitos problemas na
compilao podem ser resolvidos off-line. Por exemplo, o algoritmo local ascendente
para alocao de registradores, descrito na Seo 13.3.2, foi proposto em meados da
dcada de 1950 para o compilador FORTRAN original. Ele mais conhecido como
algoritmo MIN de Belady para substituio de pgina off-line, que h muito tempotem
sido usado como um padro contra o qual a eficcia dos algoritmos de substituio de
pgina on-line avaliada. Em um sistema operacional, o algoritmo de interesse apenas
acadmico, pois off-line. Como o sistema operacional no pode saber quais pginas
sero necessrias no futuro, no pode usar um algoritmo off-line. Por outro lado, este
algoritmo prtico para um compilador, porque este pode examinar um bloco inteiro
antes de tomar decises.
A natureza off-line da compilao permite que o construtor de compiladores use uma
grande variedade de representaes de conjunto. Muitas representaes para conjuntos
tm sido exploradas. Em particular, a computao off-line normalmente nos permite
restringir os membros de um conjunto S a um universo de tamanho fixo U (S U).
Isto, por sua vez, nos permite usar representaes de conjunto mais eficientes do
que esto disponveis em uma situao on-line, onde o tamanho de U descoberto
dinamicamente.
As operaes de conjunto comuns incluem member, insert, delete, clear,
select, cardinality, forall, copy, compare, union, intersect, difference e complement. Uma aplicao especfica, normalmente,
usa apenas um pequeno subconjunto dessas operaes. O custo dessas operaes
individuais depende da representao em particular escolhida. Na seleo de uma
representao eficiente para determinada aplicao, importante considerar com que
frequncia cada tipo de operao ser usado. Outros fatores a considerar incluem os
requisitos de memria da representao de conjunto e a esparsidade esperada de S em
relao a U.
O restante desta seo se concentra em trs representaes de conjunto eficientes que
tm sido empregadas nos compiladores: listas encadeadas ordenadas, vetores de bits
e conjuntos esparsos.

B.2.1 Representao de conjuntos como listas ordenadas


Em casos em que o tamanho de cada conjunto pequeno, s vezes faz sentido usar
uma representao de lista encadeada simples. Para um conjunto S, essa representao
consiste em uma lista interligada e um ponteiro para o primeiro elemento da lista. Cada
n da lista contm uma representao para um nico elemento de S e um ponteiro
para o prximo elemento da lista. O n final na lista tem seu ponteiro definido como

B.2 Representao de conjuntos 611

um valor padro indicando o final da lista. Com a representao de lista interligada, a


implementao pode impor uma ordem aos elementos para criar uma lista ordenada.
Por exemplo, uma lista encadeada ordenada para o conjunto S={i, j, k}, i<j<k
poderia se parecer com isto:

Os elementos so mantidos em ordem crescente. O tamanho da representao de S


proporcional ao nmero de elementos em S, e no ao tamanho de U. Se |S| for muito
menor que |U|, as economias de representar apenas os elementos presentes em S podem
mais do que compensar o custo extra incorrido para um ponteiro em cada elemento.
A representao de lista particularmente flexvel. Como nada na lista se baseia no
tamanho de U ou de S, ela pode ser usada em situaes nas quais o compilador est
descobrindo U ou S, ou ambos, como na parte de localizao de faixa viva de um
alocador de registradores de colorao de grafo.
A tabela na Figura B.1 mostra as complexidades assintticas das operaes de conjunto
comuns usando essa representao. As operaes de conjunto mais comuns sobre listas
encadeadas ordenadas so O(|S|) porque preciso percorrer as listas interligadas para
realizar as operaes. Se a desalocao no exigir percorrer a lista para liberar os ns

FIGURA B.1 Complexidades de tempo assintticas das operaes de conjunto.

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

Manter um espao extra na frente da lista, aoinvs


deno final, pode simplificar insert e
delete, considerando uma lista simplesmente
encadeada.

usaria n alocaes e n+1 ponteiros. Este esquema retm a facilidade de expansoda


representao de lista, mas reduz o overhead de espao. A insero e a excluso
movem mais dados do que com um nico elemento por n; porm, sua complexidade
assinttica ainda O(|S|).
O array IDoms usado na computao de dominncia rpida (ver Seo 9.5.2) uma
aplicao inteligente da representao de lista para os conjuntos a um caso muito especial. Em particular, o compilador conhece o tamanho do universo e o nmero de
conjuntos. O compilador tambm sabe que, usando conjuntos ordenados, eles tero
a propriedade peculiar de que, se e S1 e e S2, ento cada elemento aps e em S1
tambm est em S2. Assim, os elementos comeando com e podem ser compartilhados.
Usando uma representao de array, os nomes de elemento podem ser usados como
ponteiros tambm. Isto permite um nico array de n elementos para representar n
conjuntos esparsos como listas ordenadas, e tambm produz um operador de interseo
rpido para esses conjuntos.

B.2.2 Representao de conjuntos como vetores de bits


Os construtores de compilador normalmente utilizam vetores de bits para representar
conjuntos, particularmente aqueles usados na anlise de fluxo de dados (ver Sees
8.6.1 e 9.2). Para um universo limitado U, um conjunto S U pode ser representado
com um vetor de bits de tamanho |U|, chamado vetor caracterstico de S. Para cada
i U, 0i<|U|; se i S, o i-simo elemento do vetor caracterstico igual a um.
Caso contrrio, zero. Por exemplo, o vetor caracterstico para o conjunto S U, onde
S={i, j,k}, i<j<k, o seguinte:

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,

B.2 Representao de conjuntos 613

e intersection uma operao lgica AND. Mesmo que os conjuntos usem


vrias palavras para representar, o nmero de instrues de mquina exigidas para
realizar muitas das operaes de conjunto reduzido por um fator do tamanho de
palavra da mquina.

B.2.3 Representao de conjuntos esparsos


Para um universo fixo U e um conjunto S U, S um conjunto esparso se |S| for
muito menor que |U|. Alguns dos conjuntos encontrados na compilao so esparsos.
Por exemplo, normalmente, os conjuntos LIVEOUT usados na alocao de registradores so esparsos. Os construtores de compilador frequentemente utilizam vetores
de bits para representar esses conjuntos, devido sua eficincia em tempo e espao.
Entretanto, com esparsidade suficiente, representaes mais eficientes em relao ao
tempo so possveis, especialmente em situaes em que uma grande porcentagem das
operaes pode ser admitida em tempo O(1) ou O(|S|). Ao contrrio, os conjuntos de
vetor de bits tomam tempo O(1) ou O(|S|) nessas operaes. Se |S| for menor que |U|
por um fator maior que o tamanho da palavra, ento os vetores de bits podem ser uma
escolha menos eficiente.
Uma representao de conjunto esparso que tenha essas propriedades usa dois vetores
de tamanho |U| e um escalar para representar o conjunto. O primeiro vetor, sparse,
mantm uma representao esparsa do conjunto; o outro, dense, mantm uma representao densa do conjunto. O escalar, next, mantm o ndice do local em dense
onde o prximo novo elemento do conjunto pode ser inserido. Naturalmente, next
tambm mantm a cardinalidade do conjunto.
Nenhum vetor precisa ser inicializado quando o conjunto esparso criado; os testes de
pertinncia (membership) no conjunto garantem a validade de cada entrada medida
que acessada. A operao clear simplesmente define next de volta a zero, seu
valor inicial. Para acrescentar um novo elemento i U a S, o cdigo (1) armazena i no
local next em dense, (2) armazena o valor de next no i-simo local em sparse,
e (3) incrementa next de modo que seja o ndice do prximo local onde um elemento
pode ser inserido em dense.
Se comearmos com um conjunto esparso vazio S e acrescentarmos os elementos j, i
e k, nesta ordem, onde i<j<k, o conjunto ficaria assim:

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

Como as entradas vlidas para um elemento i em sparse e dense devem apontar


uma para a outra, a condio de pertinncia pode ser determinada com os seguintes
testes:

A tabela na Figura B.1 lista as complexidades assintticas das operaes comuns de


conjunto. Como este esquema inclui tanto uma representao esparsa quanto densa
do conjunto, tem algumas das vantagens de cada uma. Os elementos individuaisdo
conjunto podem ser acessados em tempo O(1) atravs de sparse, enquanto as
operaes de conjunto que devem atravessar o conjunto podem usar dense para obter
a complexidade O(|S|).
As complexidades de espao e de tempo devem ser consideradas quando se escolhe
entre as representaes de vetor de bits e de conjunto esparso. A representao de
conjunto esparso exige dois vetores de tamanho |U| e um escalar. Ao contrrio, uma
representao de vetor de bits exige um nico vetor de bits de tamanho |U|. Como
vemos na Figura B.1, a representao de conjunto esparso domina a representao
de vetor de bits em termos de complexidade assinttica de tempo. Porm, devido s
implementaes eficientes possveis para operaes de conjunto de vetor de bits, esses
vetores de bits so preferidos em situaes onde S no esparso. Ao escolher entre
asduas representaes, importante considerar a esparsidade do conjunto representado
e a frequncia relativa das operaes de conjunto empregadas.

B.3 IMPLEMENTAO DE REPRESENTAES


INTERMEDIRIAS
Depois de escolher um estilo especfico de IR, o construtor de compiladores precisa
decidir como implement-lo. primeira vista, as escolhas parecem ser bvias. DAGs
so facilmente representados como ns e arestas, usando ponteiros e estruturas de dados
alocadas em heap. Qudruplas encaixam-se naturalmente a um array 4k. Assim como
com os conjuntos, porm, a escolha da melhor implementao exige um conhecimento
mais profundo de como o compilador usar as estruturas de dados.

B.3.1 Representaes intermedirias grficas


Compiladores usam uma variedade de IRs grficas, conforme discutimos no Captulo5.
O ajuste da implementao de um grafo s necessidades do compilador pode melhorar
sua eficincia tanto de tempo quanto de espao. Esta seo descreve algumas das questes que surgem com rvores e grafos.

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.

B.3 Implementao de representaes intermedirias 615

Cada um desses esquemas tem pontos fortes e fracos.


j

O esquema de ponteiro lida com ASTs arbitrariamente grandes. O esquema de array


exige um cdigo para expandir o array quando a AST cresce alm do seu tamanho
alocado inicialmente.
O esquema de ponteiro exige uma alocao para cada n, enquanto o de array simplesmente incrementa um contador (a menos que deva expandir o array). Algumas
tcnicas, como alocao baseada em arena (veja a nota Alocao baseada em
arena, no Captulo6), podem reduzir o custo de alocao e recuperao.
O esquema de ponteiro tem localidade de referncia que depende totalmente do
comportamento do alocador em tempo de execuo. A tcnica de array usa locais de
memria consecutivos. Um ou outro podem ser desejveis em determinado sistema.
O esquema de ponteiro mais difcil de otimizar, devido qualidade comparativamente pior da anlise esttica sobre cdigo com uso intenso de ponteiros. Ao
contrrio, muitas das otimizaes desenvolvidas para cdigos de lgebra linear
densa aplicam-se a um esquema de array. Quando o compilador compilado, essas
otimizaes podem produzir cdigo mais rpido para o esquema de array do que
para o esquema de ponteiro.
O esquema de ponteiro pode ser mais difcil de depurar do que a implementao
de array. Os programadores parecem achar que ndices de array so mais intuitivos
do que endereos de memria.
O sistema de ponteiros exige um modo de codificar os ponteiros se a AST tiver
que ser escrita em meios externos. Presume-se que isto inclua a travessia dos ns,
seguindo os ponteiros. O sistema de array utiliza deslocamentos relativos ao incio
do array, de modo que nenhuma traduo exigida. Em muitos sistemas, isto pode
ser realizado com uma operao de E/S em bloco grande.

Existem muitas outras escolhas. Cada uma deve ser avaliada no contexto.

Mapeamento de rvores arbitrrias para rvores binrias


Uma implementao simples de rvores sintticas abstratas poderia admitir ns com
muitas quantidades diferentes de filhos. Por exemplo, um cabealho de lao for tpico

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

FIGURA B.2 Mapeamento de rvores arbitrrias para rvores binrias.

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.

Representao de grafos arbitrrios


Vrias estruturas que um compilador deve representar so grafos arbitrrios, ao invs
de rvores. Alguns exemplos incluem os grafos de fluxo de controle e os grafos de
precedncia de dados. Uma implementao simples poderia usar ns alocados em heap,
com ponteiros para representar as arestas. O lado esquerdo da Figura B.3 mostra um
CFG simples.
Claramente, ele precisa de trs ns. A dificuldade surge com as arestas: quantas arestas
de entrada e de sada cada n precisa? Cada n poderia manter uma lista de arestas de
sada; isto leva a uma implementao que poderia se parecer com aquela mostrada no
lado direito da figura.

B.3 Implementao de representaes intermedirias 617

FIGURA B.3 Um exemplo de grafo de fluxo de controle.

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

FIGURA B.4 Representao tabular de um CFG.

Como os grafos de interferncia so grandes e esparsos, o espao para os vetores


de adjacncia pode se tornar um problema. Algumas implementaes utilizam duas
passagens para construir o grafo a primeira calcula o tamanho de cada vetor de
adjacncia, e a segunda constri os vetores, cada um com o tamanho mnimo exigido.
Outras implementaes utilizam uma variante da representao de lista para conjuntos
da Seo B.2.1 o grafo construdo em uma nica passagem, usando uma lista no
ordenada para o vetor de adjacncia, com mltiplas arestas por n de lista.

B.3.2 Formas intermedirias lineares


Parte do apelo conceitual das formas intermedirias lineares, como ILOC, que tm
implementao simples, bvia, como um array de estruturas. Por exemplo, um programa ILOC tem um mapeamento imediato para um array em estilo FORTRAN n
operaes ILOC mapeadas em um array de (n4) elementos inteiros.
O cdigo de operao (opcode) determina como interpretar cada um dos operandos.
Naturalmente, qualquer deciso de projeto tem suas vantagens e desvantagens, e o
construtor de compiladores que deseja usar uma IR linear deve considerar outras
representaes que no sejam um array simples.

Array em estilo Fortran


O uso de um nico array de inteiros para manter a IR garante acesso rpido a opcodes
e operandos individuais e overhead baixo para alocao e acesso. Os passos que
manipulam a IR devem ser executados rapidamente, pois todos os acessos ao array
podem ser melhorados usando as anlises e transformaes padro desenvolvidas para
melhorar programas de lgebra linear densa. Uma passagem linear pelo cdigo tem

B.3 Implementao de representaes intermedirias 619

localidade de memria previsvel; como operaes consecutivas ocupam locais de


memria consecutivos, elas no podem entrar em conflito na cache. Se o compilador
tiver que escrever a IR para meios externos (entre passos, por exemplo), pode usar
operaes eficientes de E/S em bloco.
Porm, existem desvantagens na implementao de array. Se o compilador precisar
inserir uma operao no cdigo, ele deve criar espao para a nova operao. De modo
semelhante, as excluses devem contrair o cdigo. Qualquer tipo de movimentao
de cdigo esbarra em alguma verso desse problema. Uma implementao ingnua
criaria o espao embaralhando operaes; um compilador que usa esta abordagem
frequentemente deixar slots vazios no array aps desvios e saltos para reduzir
a quantidade de embaralhamento necessrio.
Uma estratgia alternativa usar um operador detour que direciona qualquer travessia da IR para um segmento de cdigo fora de linha. Esta tcnica permite o controle da
thread do compilador atravs de segmentos fora de linha, de modo que uma insero
possa ser feita sobrescrevendo uma operao existente com um detour, colocando o
cdigo inserido e a operao sobrescrita ao final do array, seguindo-a com um detour
de volta operao aps o primeiro detour. A parte final da estratgia linearizar
os detours ocasionalmente por exemplo, ao final de cada passo, ou a qualquer
momento em que a frao de detours exceder algum patamar.
Outra complicao com a implementao de array surge da necessidade de uma
operao ocasional, como uma funo- que usa um nmero varivel de operandos.
No compilador do qual nossa ILOC derivada, as chamadas de procedimento so
representadas por uma nica operao complicada. A operao de chamada tem um
operando para cada parmetro formal, um operando para o valor de retorno (se houver)
e dois operandos que so listas de valores potencialmente modificados pela chamadae
potencialmente usados pela chamada. Esta operao no se encaixa no molde de
um array de n4 elementos, a menos que os operandos sejam interpretados como
ponteiros para listas de parmetros, variveis modificadas e variveis usadas.

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

Essas desvantagens podem ser melhoradas, at certo ponto, implementando a lista de


estruturas dentro de uma arena ou de um array. Com um alocador baseado em arena,
o custo das alocaes cai para um teste e uma adio no caso tpico. A arena tambm
produz aproximadamente a mesma localidade de uma implementao de array simples.
Em qualquer passo que no seja o primeiro, o compilador dever ter uma noo bastante
precisa do tamanho que a IR tem. Assim, ele pode alocar uma arena que mantm a IR e
algum espao para crescimento, evitando o caso mais dispendioso de expandir a arena.
A implementao da lista em um array alcana os mesmos objetivos, com a vantagem
adicional de que todos os ponteiros tornam-se ndices inteiros. A experincia sugere
que isso simplifica a depurao; e tambm torna possvel usar uma operao de E/S
em bloco para escrever e ler a IR.

B.4 IMPLEMENTAO DE TABELAS HASH


Os dois problemas centrais na implementao de tabela hash so garantir que a funo
hash produza uma distribuio uniforme de inteiros (em todos os tamanhos de tabela
que sero usados) e tratar colises de modo eficiente. Encontrar boas funes hash
difcil. Felizmente, o hashing j usado h muito tempo, de modo que muitas boas
funes tm sido descritas na literatura.
O restante desta seo descreve as questes de projeto que surgem na implementao de
tabelas hash. A Seo B.4.1 descreve duas funes hash que, na prtica, produzem bons
resultados. As duas sees seguintes apresentam as duas estratgias mais usadas para
resolver colises. A Seo B.4.2 descreve o hashing aberto (s vezes chamado hashing
de balde bucket hashing), enquanto a Seo B.4.3 apresenta um esquema alternativo
chamado endereamento aberto, ou rehashing. A Seo B.4.4 discute as questes de
gerenciamento de armazenamento para tabelas hash, enquanto a Seo B.4.5 mostra
como incorporar os mecanismos de escopo lxico nesses esquemas. A ltima seo trata
de uma questo prtica que surge em um ambiente de desenvolvimento de compilador:
as mudanas frequentes na definio da tabela hash.

B.4.1 Escolha de uma funo hash


A importncia de uma boa funo hash precisa ser bastante enfatizada. Uma funo
hash que produz uma distribuio ruim de valores de ndice aumenta diretamente o
custo mdio da insero de itens na tabela e a localizao desses itens mais tarde.
Felizmente, muitas funes hash boas j foram documentadas na literatura, incluindo
as funes hash multiplicativas descritas por Knuth e as funes hash universais, descritas por Cormen e outros.

Funes hash multiplicativas


Uma funo hash multiplicativa incrivelmente simples. O programador escolhe uma
nica constante C e a utiliza na frmula a seguir:

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

B.4 Implementao de tabelas hash 621

O efeito da funo calcular C. key, tomar sua parte fracionria com a funo mod e
multiplicar o resultado pelo tamanho da tabela.

ORGANIZAO DE UMA TABELA DE SMBOLOS


Ao projetar uma tabela de smbolos, a primeira deciso que o construtor
decompiladores enfrenta trata da organizao da tabela e seu algoritmo de busca.
Como em muitas outras aplicaes, o construtor de compiladores tem vrias escolhas.
Lista linear
Uma lista linear pode se expandir at um tamanho arbitrrio. O algoritmo de busca
um lao nico, pequeno e estreito. Infelizmente, o algoritmo de busca exige
O(n)sondagens por pesquisa, na mdia, onde n o nmero de smbolos na tabela.
Esta nica desvantagem quase sempre supera a simplicidade da implementao e
expanso. Para justificar o uso de uma lista linear, o construtor de compiladores precisa
de evidncias fortes de que os procedimentos que esto sendo compilados tm muito
poucos nomes, como poderia ocorrer para uma linguagem orientada a objeto.
Busca binria
Para reter a facilidade de expanso da lista linear enquanto se melhora o tempo
debusca, o construtor de compiladores poderia usar uma rvore binria balanceada.
No caso ideal, uma rvore balanceada deve permitir a pesquisa em O(log2 n)
sondagens por pesquisa; esta uma melhoria considervel em relao lista linear.
Muitos algoritmos tm sido publicados para o balanceamento de rvores de busca.
(Efeitos semelhantes podem ser alcanados usando a busca binria em uma tabela
ordenada, mas a tabela torna a insero e a expanso mais difceis.)
Tabela hash
Uma tabela hash pode minimizar os custos de acesso. A implementao calcula
um ndice de tabela diretamente a partir do nome. Desde que a computao produza
uma boa distribuio de ndices, o custo mdio de acesso dever ser O(1). Porm,
o pior caso pode recair na busca linear. O construtor de compiladores pode tomar
medidas para diminuir a probabilidade de que isso acontea, mas casos patolgicos
ainda podem ocorrer. Muitas implementaes de tabela hash possuem esquemas
pouco dispendiosos para expanso.
Discriminao de conjuntos mltiplos
Para evitar o comportamento de pior caso, o construtor de compiladores pode usar
uma tcnica off-line chamada discriminao de conjuntos mltiplos. Ela cria um ndice
distinto para cada identificador, ao custo de uma passagem extra pelo texto fonte.
Estatcnica evita a possibilidade de comportamento patolgico que sempre existe
com o hashing. (Veja a nota Uma alternativa ao hahsing, no Captulo5, para obter
mais detalhes.)
Dessas organizaes, a escolha mais comum parece ser a tabela hash. Ela fornece
melhor comportamento em tempo de compilao do que a lista linear ou a rvore
binria, e as tcnicas de implementao tm sido bastante estudadas e ensinadas.

Funes hash universais


Para implementar uma funo hash universal, o programador projeta uma famlia
de funes que possam ser parametrizadas por um pequeno conjunto de constantes. Em tempo de execuo, um conjunto de valores para as constantes escolhido
aleatoriamente seja usando nmeros aleatrios para as constantes ou selecionando
um ndice aleatrio em um conjunto de constantes previamente testadas. (As mesmas
constantes so usadas por toda uma nica execuo do programa que usa a funo hash,
mas as constantes variam de uma execuo para outra.) Variando a funo hash em
cada execuo do programa, uma funo hash universal produz diferentes distribuies em cada uma delas. Em um compilador, se o programa de entrada produziu um
comportamento patolgico em alguma compilao em particular, pouco provvel que
ele produza o mesmo comportamento em compilaes subsequentes. Para implementar

622 APNDICE 
Estruturas de dados

uma verso universal da funo hash multiplicativa, o construtor de compiladores pode


gerar aleatoriamente um valor apropriado para C no incio da compilao.

B.4.2 Hashing aberto


O hashing aberto, tambm chamado hashing de balde, considera que a funo hash h
produz colises. Ele conta com h para particionar o conjunto de chaves de entrada em
um nmero fixo de conjuntos, ou baldes (buckets). Cada balde contm uma lista linear
de registros, um registro por nome. LookUp(n) percorre a lista linear armazenada no
balde indexado por h(n) para encontrar n. Assim, LookUp exige uma avaliao de
h(n) e a travessia de uma lista linear. A avaliao de h(n) deve ser rpida; a travessia
de lista leva um tempo proporcional ao tamanho da lista. Para uma tabela de tamanho
S, com N nomes, o custo por pesquisa deve ser aproximadamente O(

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

de dois reduz o custo da inevitvel operao mod exigida para implementar h.

As principais desvantagens do hashing aberto relacionam-se diretamente com essas


vantagens. Ambas podem ser gerenciadas.
1. O hashing aberto pode fazer uso intenso de alocao. Cada insero aloca um
novo registro. Isto pode ser observado quando implementado em um sistema com
alocao de memria pesada. O uso de um mecanismo mais leve, como a alocao baseada em arena (veja o quadro no Captulo6) pode aliviar esse problema.
2. Se qualquer conjunto em particular se tornar grande, LookUp se degrada at
abusca linear. Com uma funo hash com comportamento razovel, isto ocorre
somente quando N muito maior que S. A implementao deve detectar este
problema e ampliar o array de baldes. Normalmente, isto envolve alocar um
novo array de baldes e reinserir cada entrada da tabela antiga para a nova.

FIGURA B.5 Tabela de hashing aberto.

B.4 Implementao de tabelas hash 623

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.

OS PERIGOS DE FUNES HASH FRACAS


A escolha de uma funo hash tem impacto crtico sobre o custo das inseres e
pesquisas na tabela. Este um caso em que uma pequena quantidade de ateno
pode fazer uma grande diferena.
H muitos anos, vimos um aluno implementando a seguinte funo hash para strings
de caracteres: (1) quebrar a chave em pedaos de 4 bytes, (2) realizar o OR exclusivo
(XOR) desses pedaos, e (3) tomar o nmero resultante, e, mdulo tamanho da tabela,
como o ndice. A funo relativamente rpida. Ela tem uma implementao direta
e eficiente. Para alguns tamanhos de tabela, produz distribuies adequadas.
Quando o aluno inseriu essa implementao em um sistema que realizava traduo
fonte-para-fonte em programas FORTRAN, vrios fatos independentes se combinaram
para criar um desastre algortmico. Primeiro, a linguagem de implementao
preenchia strings de caracteres com espaos direita para alcanar um limite
de 4 bytes. Segundo, o estudante tinha escolhido um tamanho inicial da tabela de
2048. Finalmente, os programadores FORTRAN usam muitos nomes de varivel
de um e dois caracteres, como i, j, k, x, y e z.
Todos os nomes de varivel curtos cabem em uma nica palavra, evitando o efeito
doOR exclusivo. Porm, usar (e mod 2048) mascara todos, menos os 11 bits finais dee.
Assim, todos os nomes curtos de varivel produzem o mesmo ndice os ltimos
11bits de um par de espaos. A busca hash instantaneamente se transforma em linear.
Embora essa funo hash em particular esteja longe de ser ideal, simplesmente mudar
o tamanho da tabela para 2047 elimina os efeitos negativos mais observveis.

B.4.3 Endereamento aberto


O endereamento aberto, tambm chamado rehashing, trata de colises calculando
um ndice alternativo para os nomes cujo slot normal, em h(n), j est ocupado. Neste
esquema, LookUp(n) calcula h(n) e examina esse slot. Se estiver vazio, LookUp
falha. Se LookUp encontra n, ele tem sucesso. Se ele encontra um nome diferente de
n, usa uma segunda funo g(n) para calcular um incremento para a busca. Isto o leva
a sondar a tabela em (h(n)+g(n)) mod S, depois em (h(n)+2g(n)) mod S, depois
em (h(n)+3g(n)) mod S, e assim por diante, at encontrar n, encontrar um slot
vazio ou retornar para h(n) pela segunda vez. (A tabela numerada de 0 a S 1, o que
garante que mod S retornar um ndice de tabela vlido.) Se LookUp encontrar um
slot vazio, ou retornar para h(n) pela segunda vez, ele falha.
A Figura B.6 mostra uma pequena tabela hash implementada com este esquema, usando
os mesmos dados da Figura B.5. Como antes, h(a)=h(d)=3, enquanto h(b)=1 e
h(c)=9. Quando d foi inserido, produziu uma coliso com a. A funo hash secundria
g(d) produziu 2, de modo que Insert colocou d no ndice 5da tabela. Com efeito,
o endereamento aberto constri cadeias de itens semelhantes aos usados no hashing

624 APNDICE 
Estruturas de dados

FIGURA B.6 Tabela de endereamento aberta.

aberto. Porm, no endereamento aberto, as cadeias so armazenadas diretamente na


tabela, e um nico local da tabela pode servir como ponto de partida para vrias cadeias,
cada uma com um incremento diferente produzido por g.
Este esquema faz uma escolha sutil entre espao e velocidade. Como cada chave
armazenada na tabela, S deve ser maior que N. Se as colises so infrequentes, pois h
e g produzem boas distribuies, as cadeias de rehash permanecem curtas e os custos
de acesso baixos. Como pode recalcular g com baixo custo, este esquema no precisa
armazenar ponteiros para formar as cadeias de rehash uma economia de N ponteiros.
Esse espao economizado torna a tabela maior, o que melhora o desempenho, reduzindo
a frequncia de coliso. A principal vantagem do endereamento aberto simples:
custos de acesso menores atravs de cadeias de rehash mais curtas.
O endereamento aberto tem duas desvantagens principais. Ambas surgem quando N
se aproxima de S e a tabela se torna cheia.
1. Como as cadeias de rehash so encadeadas pela tabela de ndice, uma coliso
entre n e m pode interferir com uma insero subsequente de algum outro nome
p. Se h(n)=h(m) e (h(m)+g(m)) mod S=h(p), a insero de n, seguida dem,
preenche o slot de p na tabela. Quando o esquema se comporta bem, esteproblema tem impacto secundrio. Quando N se aproxima de S, pode se tornar
pronunciado.
2. Como S deve ser pelo menos to grande quanto N, a tabela deve ser expandida se
N se tornar muito grande. (De modo semelhante, a implementao pode expandir
S quando alguma cadeia ficar muito grande.) A expanso necessria por questo de exatido; com o hashing aberto, uma questo de eficincia.
Algumas implementaes utilizam uma funo constante para g. Isto simplifica a
implementao e reduz o custo da computao de ndices secundrios. Porm, cria
uma nica cadeia de rehash para cada valor de h e tem o efeito de mesclar cadeias de
rehash sempre que um ndice secundrio encontra um slot de tabela j ocupado. Essas
duas desvantagens superam o custo de avaliao de uma segunda funo de hash.
Uma escolha mais razovel, se possvel, usar duas funes hash multiplicativas com
diferentes constantes, selecionadas aleatoriamente inicialmente a partir de uma tabela
de constantes.
O tamanho da tabela S desempenha um papel importante no endereamento aberto.
LookUp precisa reconhecer quando alcana um slot da tabela que j visitou; caso
contrrio, no terminar com falha. Para tornar isto eficiente, a implementao deve

B.4 Implementao de tabelas hash 625

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.

B.4.4 Armazenamento de registros de smbolos


Nem o hashing aberto nem o endereamento aberto resolvem diretamente a questo de
como alocar espao para a informao associada a cada entrada da tabela hash. Com o
hashing aberto, a tentao alocar os registros diretamente nos ns que implementam
as cadeias. Com o endereamento aberto, evitar ponteiros e fazer com que cada
entrada na tabela de ndice seja um registro de smbolo. Essas duas tcnicas possuem
desvantagens. Podemos alcanar resultados melhores usando uma pilha alocada
separadamente para manter os registros.
A Figura B.7 representa esta implementao. Em uma implementao de hashing
aberto, as prprias listas de cadeia podem ser implementadas na pilha, o que reduz o
custo de alocao de registros individuais particularmente se a alocao for uma
operao pesada. J em uma implementao com endereamento aberto, as cadeias de
rehash ainda so implcitas no conjunto de ndices, preservando a economia de espao
que motivou a ideia.
Quando os registros reais so armazenados em uma pilha, formam uma tabela densa,
que melhor para a E/S externa. Para a alocao pesada, este esquema amortiza o custo
de uma alocao grande por muitos registros. Com um coletor de lixo, diminui-se o
nmero de objetos que devem ser marcados e coletados. De qualquer forma, ter uma
tabela densa torna mais eficiente percorrer os smbolos na tabela operao que o
compilador usa para realizar tarefas como atribuir locais de armazenamento.

FIGURA B.7 Alocao de pilha para registros.

626 APNDICE 
Estruturas de dados

Como vantagem final, este esquema simplifica drasticamente a tarefa de expandir o


conjunto de ndices. Para esta expanso, o compilador descarta o conjunto de ndices
antigo, aloca um conjunto maior e depois reinsere os registros na nova tabela, trabalhando de baixo para cima na pilha. Isto elimina a necessidade de ter, temporariamente,
as tabelas antiga e nova na memria. Percorrer a tabela densa exige menos trabalho, em
geral, do que caar os ponteiros para atravessar as listas no hashing aberto. Isto evita
percorrer slots de tabela vazios, como pode acontecer quando o endereamento aberto
expande o conjunto de ndices para manter as cadeias curtas.
O compilador no precisa alocar a pilha inteira como um nico objeto. Ao invs, a
pilha pode ser implementada como uma cadeia de ns que mantm k registros cada,
para algum k de tamanho razovel. Quando um n se torna cheio, a implementao
aloca um novo, acrescenta-o ao final da cadeia e continua. Isto oferece ao construtor
de compiladores um controle minucioso sobre o compromisso entre custo de alocao
e espao desperdiado.

B.4.5 Incuso de escopos lxicos aninhados


A Seo 5.5.3 descreveu as questes que surgem na criao de uma tabela de smbolos
para lidar com escopos lxicos aninhados. Descreveu uma implementao simples
que cria um feixe de tabelas de smbolos, uma por nvel. Embora esta implementao
seja conceitualmente limpa, transfere o overhead da definio de escopo para LookUp,
ao invs de para InitializeScope, FinalizeScope e Insert. Como o
compilador chama LookUp muito mais vezes do que essas outras rotinas, outras
implementaes merecem considerao.
Considere novamente o cdigo na Figura5.10. Ele gera as seguintes aes:

onde representa uma chamada a InitializeScope, uma chamada a FinalizeScope, e nome, n uma chamada a Insert que acrescenta nome no nveln.

Incluso de escopos lxicos ao hashing aberto


Considere o que poderia acontecer em uma tabela de hashing aberto se simplesmente
acrescentssemos um campo de nvel lxico ao registro para cada nome e inserssemos
cada novo nome na frente de sua cadeia. Insert poderia ento verificar duplicatas
comparando tanto nomes quanto nveis lxicos. LookUp retornaria o primeiro registro que descobrisse para determinado nome. InitializeScope simplesmente
incrementaria um contador para o nvel lxico atual. Este esquema transfere as complicaes para FinalizeScope, que deve no apenas decrementar o nvel lxico
atual, mas tambm remover os registros para quaisquer nomes inseridos no escopo
que est sendo desalocado.
Se o hashing aberto for implementado com ns alocados individualmente para suas
cadeias, como mostra a Figura B.5, ento FinalizeScope deve encontrar todos os
registros para o escopo que est sendo descartado e remov-los de suas respectivas
cadeias. Se eles no forem usados mais adiante no compilador, FinalizeScope
deve desaloc-los; caso contrrio, encade-los juntos para que sejam preservados.
A Figura B.8 mostra a tabela que esta tcnica produziria, na instruo de atribuio
daFigura5.10.

B.4 Implementao de tabelas hash 627

FIGURA B.8 Escopo lxico em uma tabela de hashing aberto.

Com registros alocados em pilha, FinalizeScope pode repetir a partir do topo da


pilha para baixo at alcanar um registro para algum nvel abaixo do que est sendo
descartado. Para cada registro, ele atualiza a entrada do conjunto de ndices com o
ponteiro do registro para o prximo item na cadeia. Se os registros esto sendo descartados, FinalizeScope reinicia o ponteiro para o prximo slot disponvel; caso
contrrio, os registros so preservados juntos na pilha. A Figura B.9 mostra a tabela
de smbolos para nosso exemplo na instruo de atribuio.

FIGURA B.9 Escopo lxico em uma tabela de hash aberto alocada em pilha.

628 APNDICE 
Estruturas de dados

Com um pouco de cuidado, a reordenao dinmica da cadeia pode ser acrescentada


a este esquema. Como FinalizeScope usa a ordenao de pilha, ao invs da
ordenao de cadeia, ele ainda encontrar todos os nomes de alto nvel no topo da pilha.
Com cadeias reordenadas, o compilador, ou precisa percorrer a cadeia para remover
cada registro de nome excludo, ou encadear duplamente as cadeias para permitir a
excluso mais rpida.

Incluso de escopos lxicos ao endereamento aberto


Com uma tabela de endereamento aberto, a situao ligeiramente mais complexa.
Nela, os slots na tabela so um recurso crtico; quando todos so preenchidos, a tabela
precisa ser expandida antes que mais insero possa ocorrer. A excluso a partir de uma
tabela que usa rehashing difcil; a implementao no pode dizer com facilidade se o
registro excludo cai no meio de alguma cadeia de rehash. Assim, a marcao do slot
vazio quebra qualquer cadeia que passe por esse local (ao invs de terminar ali). Isto
depe contra o armazenamento de registros discretos para cada variante de um nome
na tabela. Ao invs disso, o compilador deve encadear apenas um registro por nome na
tabela; ele pode criar uma cadeia de registros substitudos por variantes mais antigas.
A Figura B.10 representa esta situao para o exemplo em andamento.
Este esquema transfere a maior parte da complexidade para Insert e FinalizeScope.
Insert cria um novo registro no topo da pilha. Se descobrir uma declarao mais
antiga com o mesmo nome no conjunto de ndices, ele substitui essa referncia por
uma referncia ao novo registro e vincula a referncia mais antiga ao novo registro.
FinalizeScope percorre os itens do topo da pilha, como no hashing aberto. Para
remover um registro que tem uma variante mais antiga, ele simplesmente revincula o
conjunto de ndices para que aponte para a variante mais antiga. Para remover a variante
final de um nome, ele precisa inserir uma referncia a um registro especialmente
designado, que indica uma referncia excluda. LookUp precisa reconhecer a referncia
excluda como ocupando um slot na cadeia atual. Insert precisa saber que pode
substituir uma referncia excluda por qualquer smbolo recm-inserido.

FIGURA B.10 Escopo lxico em uma tabela de endereamento aberto.

B.5 Um projeto de tabela de smbolos flexvel 629

Este esquema, basicamente, cria cadeias separadas para colises e redeclaraes.


Colises so encadeadas pelo conjunto de ndices; redeclaraes so encadeadas atravs
da pilha. Isto deve reduzir ligeiramente o custo de LookUp, pois evita examinar mais
de um registro para qualquer nome isolado.
Considere um balde no hashing aberto que contm sete declaraes para x e uma nica
declarao para y no nvel zero. LookUp poderia encontrar todos os sete registros para
x antes de encontrar y. Com o esquema de endereamento aberto, LookUp encontra
um registro para x e um registro para y.

B.5 UM PROJETO DE TABELA DE SMBOLOS FLEXVEL


A maioria dos compiladores usa uma tabela de smbolos como um repositrio central
para informaes sobre os diversos nomes que surgem no cdigo fonte, na IR e no
cdigo gerado. Durante o desenvolvimento do compilador, o conjunto de campos na
tabela de smbolos parece crescer monotonicamente. Os campos so acrescentados
para dar suporte a novos passos e comunicar informaes entre passos. Quando a
necessidade de um campo desaparece, ele pode ou no ser removido da definio da
tabela de smbolos. medida que cada campo acrescentado, a tabela de smbolos
aumenta em tamanho, e quaisquer partes do compilador com acesso direto tabela de
smbolos devem ser recompiladas.
Encontramos este problema na implementao dos ambientes de programao Rn e
ParaScope. A natureza experimental desses sistemas levou a uma situao em que
acrscimos e excluses de campos da tabela de smbolos eram comuns. Para resolver
o problema, implementamos uma estrutura mais complexa, porm mais flexvel, para
a tabela de smbolos uma tabela hash bidimensional. Isto eliminou quase todas as
mudanas na definio da tabela de smbolos e sua implementao.
A tabela bidimensional, mostrada na Figura B.11, usa duas tabelas de ndices hash
distintas. A primeira, mostrada junto com a borda esquerda da figura, corresponde
tabela de ndices esparsa da Figura B.7. A implementao usa esta tabela para realizar
o hash sobre os nomes de smbolos. A segunda, mostrada no topo da figura, uma
tabela hash para os nomes de campo. O programador referencia campos individuais
tanto por seu nome textual como pelo nome do smbolo; a implementao realiza o
hash do nome de smbolo para obter um ndice e o nome de campo para selecionar um
vetor de dados. O atributo desejado armazenado no vetor sob o ndice do smbolo.
Ela se comporta como se cada campo tivesse sua prpria tabela hash, implementada
como mostra a Figura B.7.
Embora isto parea complexo, no particularmente dispendioso. Cada acesso tabela
exige dois clculos de hash, ao invs de um. A implementao no precisa alocar
armazenamento para um determinado campo at que um valor seja armazenado nele;
isto evita o overhead de espao dos campos no utilizados, e permite que os desenvolvedores individuais criem e excluam campos da tabela de smbolos sem interferir
com outros programadores.
Nossa implementao forneceu pontos de entrada para definir valores iniciais para um
campo (pelo nome), excluir um campo (pelo nome) e para relatar estatsticas sobre uso
do campo. Este esquema permite que programadores individuais gerenciem seu prprio
uso da tabela de smbolos de modo responsvel e independente, sem interferncia de
outros programadores e seu cdigo.
Como uma questo final, a implementao deve ser abstrata com relao a uma tabela de smbolos especfica. Ou seja, sempre deve tomar uma tabela como parmetro.

630 APNDICE 
Estruturas de dados

FIGURA B.11 Tabela hash de smbolos bidimensional.

Isto permite que o compilador reutilize a implementao em muitos casos, como


os algoritmos de numerao de valor superlocal ou baseados em dominador, do
Captulo8.

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

rvores sintticas abstratas, conforme mencionamos no quadro em destaque no


Captulo5, podem se tornar excessivamente grandes. A tcnica de mapeamento de
uma rvore arbitrria em uma rvore binria simplifica a implementao e parece
manter o overhead baixo [231].
j A representao tabular de um grafo, com listas de sucessores e predecessores, foi
reinventada muitas vezes. Ela funciona particularmente bem para CFGs, para os
quais o compilador percorre tanto os sucessores e os predecessores. Usamos esta
estrutura de dados pela primeira vez no sistema PFC em 1980.
j Os conjuntos na anlise de fluxo de dados podem crescer e ocupar centenas de
megabytes. Como alocao e desalocao so questes de desempenho nessa escala, normalmente usamos o alocador baseado em rea de Hanson [179].

B.5 Um projeto de tabela de smbolos flexvel 631

O tamanho e a esparsidade dos grafos de interferncia os tornam outra rea que


merece considerao cuidadosa. Usamos a variante de lista ordenada com mltiplos elementos de conjunto por n para manter baixo o custo da criao do grafo
enquanto o overhead de espao gerenciado [101].

As tabelas de smbolos desempenham um papel central no modo como os compiladores


armazenam e acessam informaes. Muita ateno tem sido dada organizao dessas
tabelas. Listas de reorganizao [302,317], rvores de busca balanceadas [109,41]
e hashing [231, vol.3] desempenham papel importante para tornar eficiente o acesso
a essas tabelas. Knuth [231, vol.3] e Cormen [109] descrevem a funo hash multiplicativa com detalhes.

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

-transio, 34, 35, 37, 42

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

esttica, 4, 162, 196, 344, 393, 399, 400, 408,


451, 455, 575, 615
interprocedimental, 203, 297, 323, 353, 354,
384, 385, 387-389, 391, 392, 394,
399, 400, 410, 413, 414, 434-438,
440
lxica. Ver scanning
viva iterativa, 375, 407
ARP, 242-245, 260-264, 266, 267, 280, 288, 334,
469, 505, 506, 510, 519, 589
arquitetura de conjunto de instrues (ISA), 2,
205
array
bidimensionais, 125, 151, 314, 345, 346
de estruturas, 320, 341, 614, 616, 618
endereamento, 54, 337, 346
estilo FORTRAN, 190, 618
layout de armazenamento, 235, 308
ordem
por colunas, 308-311, 314, 346, 371
por linhas, 308-310, 312, 314, 340, 346
polinmios de endereo, 311, 314, 320
subscritos, 10, 12, 80, 125, 129, 151, 159,
185, 203, 212, 213, 218, 223, 231,
239, 290, 308-310, 314, 323, 358360, 381, 415, 416, 422, 423, 427,
434, 462, 470, 484
vetores de indireo, 308-312, 314
Ver tambm vetores
rvore
binria, 179, 285, 364, 615, 616, 621, 630
de anlise, 75, 83
de baixo nvel, 199, 200, 505, 509
de dominadores, 417, 418, 420, 422, 423,
425, 426, 443, 447, 473-475
de operao, 508, 509, 511, 514
grafo acclico direcionado, 199, 516
sinttica, 75, 76, 78-80, 82-84, 88, 98, 100,
101, 102, 104, 106, 124, 125, 128130, 138, 143, 152, 156, 158, 163,
164, 169-172, 174, 176, 178, 179,
189, 190, 193, 195-200, 204, 213,
228, 229, 272
mapeamento, 615, 616, 630
nvel de abstrao, 195, 196, 199, 506
assinaturas de tipo, 259
associatividade
direita, 128, 187
esquerda, 128, 187, 285
para reordenar expresses, 356
ativao, 13, 216, 236-238, 242, 243, 245, 246,
265, 267, 271, 273, 274, 276, 277,
281, 287, 338, 384, 470, 498, 504,
578, 601
atraso, slots, 210, 324, 326-328, 345, 350, 498,
560
atribuio
de registradores, 564-566
de string, 315, 316, 318
lvalue, 298
rvalue, 298
atributo

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

650 ndice Remissivo

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

open, 56, 208


de trs fases, 6
just-in-time (JIT), 3, 18, 246, 249
redirecionveis, 528
comutatividade, 4, 292, 296, 356, 394, 453, 483,
493
concatenao
expresses regulares, 32, 36
strings, 317
conjunto potncia, 133
constantes de regio, 486, 487
construo
de subconjunto, 35, 38-42, 45, 47, 48, 61, 62,
65, 67, 108, 374, 610
do grafo de chamada, 203, 414, 434, 435
contagem de referncia, 272
Ver tambm coleta de lixo
contexto
anlise lxica, 20, 183
clonagem, 551
conveno de ligao, 234, 235, 259, 264266, 268, 297, 336, 342, 589
definio
sequncia de eplogo, 266
sequncia de ps-retorno, 266
sequncia de pr-chamada, 266
sequncia de prlogo, 266
converso
explcita, 145, 153, 155, 197
implcita, 145
coordenada de distncia esttica, 261
cpia perdida, problema, 428-430
custo de derramamento globais, 577
Ver tambm derramamento

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

despacho esttico e dinmico, 253


Desvio
para a frente, 381
para trs, 476
tomado, 378
condicionais, 480, 482, 483, 601, 604
fall-through, 378
no tomados, 205, 210
predio, 94, 325, 326
relativos ao contador de programa (PC
Program Counter)
tomados, 210
detour, operador, 619
diagramas de transio
como abstraes de cdigo, 23
discriminao de multiconjunto, 221, 228
display
gerenciamento, 268
global, 262
distributividade, 356
divergncia, 82, 88, 218, 349, 526
diviso
de custo zero, 592
de faixa viva, 581, 582, 590, 592, 594, 595
passiva, 592
dominador imediato, 417, 423, 443, 473, 610
dominncia estrita, 417
Ver tambm dominncia

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

ndice Remissivo 651

para a frente, 546


para trs, 546
dependncias, 431, 479
limitaes, 537
de mdulo, 557
de tempo timo, 536, 558
de trao (trace schedulling), 550
do kernel, 557
pipelining de software, 553
regional, 548
escopo de otimizao, 352
mtodos de programa inteiro, 354
mtodos globais, 353
mtodos interprocedimentais, 354
mtodos intraprocedimentais, 353
mtodos locais, 352
mtodos regionais, 352
escopo dinmico, 241
escopo aninhado, 250
em Pascal
gerenciamento, 223
lxicos, 223
escopo lxico, 239, 241
exemplo, 222
para endereamento aberto, 628
para hashing aberto, 626
espao de endereos
layout, 286
vises, 287
espao de nome, 290
em linguagens orientadas a objeto, 246, 436
em linguagens tipo Algol, 334, 384
hierarquia de classes, 248, 249
especializao, 468
computao, 468
eliminao de recurso de cauda, 494
otimizao peephole, 494, 517
propagao de constante, 354, 378, 413
estimador de tempo de execuo, 165, 176
gramtica de atributo, 165
estrutura de classe fechada, 250
estrutura de classes aberta, 251
estrutura de dados, 193, 216, 242
implementao de tabela hash, 620
representao de conjuntos, 610
etiquetas de runtime, 321
exatido (correctness), 4, 349, 403
execuo condicional, 324
execuo predicada, 305, 324
expresses antecipveis, 405, 412
expresses de tipo misto, 155, 297
expresses disponveis, 411, 463
expresses regulares, 26
alternao, 27
autmatos finitos e, 64
concatenao, 32
fechamento positivo, 33
linguagens regulares, 32
livres de fechamento, 228
notao, 19, 26, 28
precedncia, 36
propriedades de fechamento, 32
expresses
booleanas, 299, 302
chamadas de funo em, 296

gerao de ILOC para, 180


gramtica, 183, 299
inferncia de tipo para, 156, 163, 167, 178
operandos, 296
percurso em rvore para, 297, 300, 503
redundantes, 354, 411, 468
relacionais, 302
reordenao, 535, 539

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

fronteira superior, 100, 102, 104


funo-, 415, 416
algoritmo de insero, 430
argumentos, 430, 432
funo memo, 227
funo monotnica, 41, 47
funo trampolim, 255
funo hash, escolha, 620
funo hash multiplicativa, 620, 622, 631
funo hash universal, 621
funo trampolim, 255
fuso de lao, 371

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

652 ndice Remissivo

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

com sinal, 26, 149, 600


ponteiro para, 153
sem sinal, 24, 25, 27, 29, 149, 150, 157, 340
interbloqueio, 532, 533
interface externa, 234
interferncia, 579, 580
interpretadores
compiladores e, 3
IR de nvel mais baixo (LLIR), 518, 519, 521,
523-526
gerao pelo front end, 345
operaes, 518
Ver tambm representaes intermedirias
(IR)
IR, 193
IR grfica, 196
rvores relacionadas sintaxe, 196
definio, 196
eficincia do armazenamento, 198
grafos, 200
implementao, 228
Ver tambm representaes intermedirias
(IR)
IR hbrida, 195, 201
IR linear, 204
cdigo de dois endereos, 205
cdigo de mquina de pilha, 205, 207
cdigo de trs endereos, 205-208, 211, 216,
285
cdigo de um endereo, 205, 211, 502
definio, 204
estruturas de dados, 206
Ver tambm representaes intermedirias
(IR)

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

ndice Remissivo 653

layout de registro de objeto, 252


leque (handle), 101
localizao, 101
lex, gerador de scanner, 47
lexema, 25, 48, 50, 57-59
linguagem de transferncia de registrador (RTL),
208, 524
linguagens fortemente tipadas, 145, 148, 156,
185
linguagens fracamente tipadas, 145, 156
linguagens livres de contexto, 69, 72, 93, 143,
183
linguagens no tipadas, 145, 148
linguagens naturais, 2, 31
linguagens orientadas a objeto, 146, 237, 246,
250
espaos de nomes de, 255
estruturas de runtime, 250
fluxo de controle em, 237
herana nica, 250
herana, 250
implementao, 251
linguagens tipo Algol e, 233, 384
terminologia, 247
linguagens regulares, 32, 63, 64, 81, 183
linguagens dinamicamente tipadas, 145, 156
linguagens tipadas estaticamente, 145, 148, 156
linguagens tipo Algol, 26, 76, 143, 233, 334, 384
espaos de nomes, 239
estruturas de runtime, 242
linguagens orientadas a objeto, 233, 334
linguagens verificadas dinamicamente, 156
linguagens
espaos de nomes, 238, 239, 246
estaticamente tipadas, 145, 148, 156
expresses regulares em, 26
fortemente tipadas, 145, 148, 156, 185
fracamente tipadas, 145, 156
livres de declarao, 185
microssintaxe, 20, 33, 46, 52
no tipadas, 145, 148
naturais, 2, 31
orientadas a objeto, 146, 237, 246, 250
palavras em, 31
regras de escopo, 226, 241
regulares, 32, 63, 64, 81, 183
tipadas dinamicamente, 145, 156
tipos de dados, 142
verificadas dinamicamente, 156
links de acesso, 261
definio, 261
display global, 282
gerenciamento, 268
links estticos. Ver links de acesso
LINPACK, 345, 346
lista livre, 269, 270, 271, 274, 570
lista linear, 621, 622
listas ordenadas, 533, 610-612, 631
LL(1), parsers, 81
construo de tabela, 108, 119, 122, 123
controlados por tabela, 124, 126
esqueleto, 102, 104, 123, 173, 174
propriedade livre de retrocesso, 92
load, operao, 166
loadAI, operao, 288

loadAO, operao, 288


loadI, operao, 13, 303, 505, 512
locais de armazenamento
atribuio, 188, 285, 625
escolha de, 292
Lucratividade, 350
lvalue, 311

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

tradues de, 240


valor temporrio, 211, 216, 227, 416
ns
AST, 503, 506, 509, 513
DAG, 199
diviso, 443
ncleo do lao, 553
numerao de valor baseada em dominador,
472-474
dominadores, 401, 417, 418, 420
Ver tambm numerao de valor
numerao de valor local (LVN), 355, 356, 427,
452, 453, 454, 455, 461, 472
com extenses, 358
numerao de valor superlocal, 367
Ver tambm numerao de valor
numerao de valor
baseada em dominador, 473
definio, 473
identidades algbricas para, 357
impacto de atribuies indiretas, 359
local, 367
operaes comutativas, 356, 357
superlocal, 367, 368, 373, 394, 396, 400, 461,
467, 472
nmero cromtico, 575, 576
nmero de ps-ordem, 403, 487, 489
nmeros de ponto flutuante, 149
nmeros
como tipos bsicos, 149
ponto flutuante, 149
reais, 26, 29, 149, 296

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

654 ndice Remissivo

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

propagao de constante, 440, 524


reduo de fora de operador, 468, 471, 480,
484, 488, 495, 553
remoo de condicionais de lao (loop
unswitching), 477, 478
renomeao, 213
sequncias, 452, 493
substituio de teste de funo linear, 553
substituio em linha, 384, 394
otimizaes escalares, 415, 451

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

ndice Remissivo 655

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

mantendo valores em, 290


memria versus, 565
predicado, 566
restaurao, 335
salvamento, 335
salvamentos do procedimento chamado, 469,
470
salvamentos do procedimento chamador, 469
valores salvos, 208, 244, 267
Registros de Ativao (ARs Activation
Records)
alocao de heap, 245
alocao de pilha, 245
alocao esttica, 246
alocao, 245
registros de objeto, 255
regra declarar antes de usar, 143, 155, 221
regras de atribuio, 159-161, 163-167, 173,
176, 190
regras de cpia
definio
tamanho de uma gramtica de atributo e, 170
regras de escopo, 239
C, 241
FORTRAN, 241
Scheme, 242
regras de reescrita, 506, 509
regras de tipos, 185
relacionais
codificao numrica, 299, 302, 306
codificao posicional, 299, 300, 302, 303, 306
rematerializao, 592
remoo de condicionais de lao
(unswitching), 477
Ver tambm habilitando transformaes
renomeao dinmica de registrador, 547
renomeao, 478
aps insero-, 423
evitar antidependncias e, 539
representao de rvores, 614
representao de conjunto esparso, 613, 614
representao de conjuntos, 610, 612
representao em lista de um conjunto
representaes intermedirias (IRs), 614
baixo nvel, 10, 196, 208, 211
baseadas em rvore, 195
expressividade, 196
forma de atribuio nica esttica (SSA), 194,
213, 215, 400, 415
grficas, 196
hbridas, 195
implementao, 194
lineares, 204
representaes
conjuntos esparsos, 613
grafos arbitrrios, 616
lineares, 198
lista interligada, 611
lista, 187, 610, 611, 612
string, 315
tabela de smbolos, 126, 176, 177, 183, 609,
621, 627, 629
resoluo de nome, 223, 247, 248, 253
restries de recursos, 556
rvalue, 311

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

656 ndice Remissivo

sistemas numricos, 296


SLR(1), construo, 136
sobrecarga de operador, 145, 191
sobrecarga, 146, 155, 157
SSA mnima, 422
SSA podada, 421, 422
SSA, grafos, 480
string terminada em nulo, 318
string
caractere entre aspas, 29
como tipo construdo, 151
concatenao, 317
conjuntos de, 27, 61, 72
forma sentencial, 73-75, 78, 84, 85, 89, 100,
101
operaes, 315, 318
representaes, 315
sobreposio
tamanho, 318
terminada em nulo, 318
subclasse, 63, 71, 226, 247, 249, 253
substituio aleatria, 289
substituio de teste de funo linear
(LFTR Linear-Function Test
Replacement), 480, 485, 491, 553
Ver tambm reduo de fora
substituio em linha, 354, 384-388, 391-393
substituio escalar, 384, 453, 454, 477, 479
superclasses, 227, 247, 248, 252, 254
switch, instruo, 330, 331

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

tipos de funo constante, 185


tipos enumerados, 151, 152
tipos
bsicos, 149, 150, 157, 297, 307
clula, 145
compostos, 150
construdos, 150
declarados, 155
enumerados, 151, 152
erros, 123, 148, 155, 186
funo constante, 185, 624
mudanas dinmicas em, 186
representao, 154
retorno, 153, 185, 186, 191
traduo ad hoc dirigida pela sintaxe, 141, 142,
172-174, 176, 178, 179, 184, 189,
292
ILOC, gerao, 180
transformao dependente de mquina, 452, 453
transformao independente de mquina, 452
transformao
fatorao esquerda, 93
independentes de mquina, 453
reduo de fora, 484
substituio em linha, 385
taxonomia para, 494
troca (swap), problema, 428

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

nomeao, 175, 211


nomeados, 234
ponteiro, 258, 259
retorno, 455, 589
sujos, 570, 571
temporrios, nomeao, 211
tempos de vida, 142, 242, 287
variveis de induo, 486, 487
variveis livres, 238, 349
variveis locais, 386
de outros procedimentos, 261, 262, 266
inicializao, 265, 469
Ver tambm variveis
variveis mortas, 405
variveis no inicializadas
com informaes vivas, 374
localizao, 374
variveis vivas, 374
em alocao de registradores global, 378
em construo de SSA, 378
equaes para, 405
Ver tambm vivncia
variveis
baseadas em ponteiro, 291
classe, 503
classes de armazenamento, 503
escolha de nome, 358
estticas, 244, 246, 260
globais, 260, 410, 413
induo, 346, 348, 486, 487, 489-491
inicializao, 244
livres, 238
locais, 244, 245, 259-262, 265, 266
mortas, 405
sombra de ndice, 328
vivas, 374, 378
varivel sombra de ndice, 328
verificao de tipo, 184
Very-Long Instruction Word (VLIW), mquinas,
534
vetores de bits, 42, 610, 612-614
vetores de indireo, 308-312, 314
Ver tambm arrays
vetores dopados, 313
vetores
adjacncia, 617, 618
bit, 610-614
dopados, 313, 314
indireo, 308-312, 314
layout de memria, 306
Ver tambm arrays
vivncia (liveness), 374, 407, 571
volatile, palavra-chave, 291

W
while, laos, 329
Ver tambm laos

Você também pode gostar