0 ratings0% found this document useful (0 votes) 596 views881 pagesAdvanced+Compiler+Design+and+Implementation 1997
Advanced+Compiler+Design+and+Implementation
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content,
claim it here.
Available Formats
Download as PDF or read online on Scribd
Advanccd
COMPILER DESIGN
IMPLEMENTATION
Steven S. MuchnickScalar replacement of array references
Data-cache optimizations
— |
Procedure integration
Tail-call optimization, including
tail-recursion elimination
Scalar replacement of aggregates
Sparse conditional constant propagation
Interprocedural constant propagation
Procedure specialization and cloning
Sparse conditional constant propagation
y
Global value numbering Algebraic
Local and global copy propagation _»,J| Constant | simplifications,
Sparse conditional constant propagation folding including
Dead-code elimination reassociation
cl
Local and global common-
subexpression elimination
Loop-invariant code motion
Dead-code elimination
Code hoisting
Induction-variable strength reduction
Linear-function test replacement
Induction-variable removal
Unnecessary bounds-checking
elimination
Control-flow optimizations
c4
---------------------------------- >]
J (from box D)
Order of Optimizations
This flowchart represents a recommended order for performing optimizations in an aggres-
sive optimizing compiler. Other orders are possible, and the examples of real-world compilers
in Chapter 21 present several alternatives, though none of them includes all of the optimiza-
tions in this diagram. The letters at the left in the diagram correspond to the levels of code
appropriate for the corresponding optimizations. The correspondence between letters and
code levels is as follows:
These optimizations typically are applied either to source code or to a high-level intermediate
code that preserves loop structure and the sequence in which operations are performed and
that has array accesses in essentially their source-code form. Usually, these optimizations are
done very early in the compilation process, since compilation tends to lower the level of the
code as it proceeds from one phase to the next.(to constant folding, algebraic
simplifications, and reassociation)
A
In-line expansion
Leaf-routine optimization
Shrink wrapping
Machine idioms
Tail merging
Branch optimizations and conditional
moves
Dead-code elimination
D Software pipelining, with loop
unrolling, variable expansion, register
renaming, and hierarchical reduction
Basic-block and branch scheduling 1
Register allocation by graph coloring
Basic-block and branch scheduling 2
Intraprocedural I-cache optimization
Instruction prefetching
Data prefetching
Branch prediction
— |
Interprocedural register allocation
E Aggregation of global references
Interprocedural I-cache optimization
B,C These optimizations are typically performed on medium- or low-level intermediate code,
depending on the overall organization of the compiler. If code selection is done before all
optimizations other than those in box A (known as the “low-level” model of optimizer struc-
ture), then these optimizations are performed on low-level code. If, on the other hand, some
optimizations are performed on a medium-level, relatively machine-independent intermedi-
ate code and others are performed on low-level code after code generation (known as the
“mixed” model), then these optimizations are generally done on the medium-level interme-
diate code.
The branches from C1 to C2 and C3 represent a choice of the method used to perform
essentially the same optimization (namely, moving computations to places where they are per-
formed less frequently without changing the semantics of the program). They also represent
a choice of the data-flow analyses used to perform the optimization.
D_ These optimizations are almost always done on a low-level form of code—one that may
be quite machine-dependent (e.g., a structured assembly language) or that may be somewhat
more general, such as the low-level intermediate code used in this book—because they require
that addresses have been turned into the form required by the target processor and because
several of them require low-level control-flow code.
E_ These optimizations are performed at link time, so they operate on relocatable object code.
Three optimizations, namely, constant folding, algebraic simplification, and reassociation,
are in boxes connected to the other phases of the optimization process by dotted lines because
they are best structured as subroutines that can be invoked whenever they are needed.
A version of this diagram appears in Chapters 1 and 11 through 20 to guide the reader
in ordering optimizer components in a compiler.Advanced Compiler Design
and Implementation
Steven S. MuchnickSenior Editor Denise E. M. Penrose
Director of Production and Manufacturing Yonie Overton
Senior Production Editor Cheri Palmer
Editorial Coordinator Jane Elliott
Cover Design Ross Carron Design
Text Design, Composition, and Illustration Windfall Software
Copyeditor Jeff Van Bueren
Proofreader Jennifer McClain
Indexer Ty Koontz
Printer Courier Corporation
ACADEMIC PRESS
A Harcourt Science and Technology Company
525 B Street, Suite 1900, San Diego, CA 92101-4495, USA
http:/[Link]
Academic Press
Harcourt Place, 32 Jamestown Road, London, NW1 7BY, United Kingdom
http:/[Link]
Morgan Kaufmann Publishers
340 Pine Street, Sixth Floor, San Francisco, CA 94104-3205, USA
http:/[Link]
© 1997 by Academic Press
All rights reserved
Printed in the United States of America
04 03 6
No part of this publication may be reproduced, stored in a retrieval system, or transmitted
in any form or by any means—electronic, mechanical, photocopying, recording, or
otherwise—without the prior written permission of the publisher.
Library of Congress Cataloging-in-Publication Data
Muchnick, Steven S., date.
Advanced compiler design and implementation / Steve Muchnick.
. cm.
Includes bibliographical references and index.
ISBN 1-55860-320-4
1. Compilers (Computer programs) 2. Systems programming (Computer
science). I. Title.
QA76.76.C65M8 1997
005.4’53—dc21 97-13063
CIPTo Eric, nihil sine quéForeword
ompiler design has been an active topic of research and development since
the mid-1950s. Fortran, the first widely used higher-level language, suc-
ceeded, in large part, because of the high quality of its early compilers. John
Backus and his colleagues at IBM recognized that programmers would not give up
the detailed design control they had with assembly language unless the performance
of compiled code was sufficiently close to the performance of handwritten machine
code. Backus’s group invented several key concepts that underlie the topics in this
book. Among them are the treatment of array indexes in loop optimization and
methods for local register allocation. Since that time, both researchers and practi-
tioners have improved and supplanted them (repeatedly) with more effective ones.
In light of the long history of compiler design, and its standing as a relatively
mature computing technology, why, one might ask, should there be a new book in
the field? The answer is clear. Compilers are tools that generate efficient mappings
from programs to machines. The language designs continue to change, the target
architectures continue to change, and the programs become ever more ambitious
in their scale and complexity. Thus, while the compiler design problem remains
the same at a high level, as we zoom in, it is continually changing. Furthermore,
the computational resources we can bring to bear in the compilers themselves are
increasing. Consequently, modern compilers use more time- and space-intensive
algorithms than were possible before. And, of course, researchers continue to invent
new and better techniques for solving conventional compiler design problems. In
fact, an entire collection of topics in this book are direct consequences of changes in
computer architecture.
This book takes on the challenges of contemporary languages and architectures
and prepares the reader for the new compiling problems that will inevitably arise
in the future. For example, in Chapter 3 the book builds on the reader’s knowledge
of symbol tables and local scope structure to describe how to deal with imported
and exported scopes as found in Ada, Modula-2, and other modern languages. And,
since run-time environments model the dynamic semantics of source languages, the
discussion of advanced issues in run-time support in Chapter 5, such as compiling
shared objects, is particularly valuable. That chapter also addresses the rich type
systems found in some modern languages and the diverse strategies for parameter
passing dictated by modern architectures.Foreword
No compiler book would be complete without a chapter on code generation.
The early work in code generation provided approaches to designing handcrafted
instruction-selection routines and intermixing instruction selection with register
management. The treatment of code generation in Chapter 6 describes automated
techniques based on pattern matching, made possible not only by compiler research
but also by simpler and more orthogonal instruction sets and by the feasibility of
constructing and traversing intermediate-code trees in a compiler.
Optimization is the heart of advanced compiler design and the core of this
book. Much theoretical work has gone into program analysis, both for the sake
of optimization and for other purposes. Chapters 7 through 10 revisit what are,
by now, the classic analysis methods, along with newer and more efficient ones
previously described only in research papers. These chapters take a collection of
diverse techniques and organize them into a unified whole. This synthesis is, in itself,
a significant contribution to compiler design. Most of the chapters that follow use
the analyses to perform optimizing transformations.
The large register sets in recent systems motivate the material on register allo-
cation in Chapter 16, which synthesizes over a decade of advances in algorithms
and heuristics for this problem. Also, an important source of increased speed is
concurrency—the ability to do several things at once. In order to translate a sequen-
tial program into one that can exploit hardware concurrency, the compiler may need
to rearrange parts of the computation in a way that preserves correctness and in-
creases parallelism. Although a full treatment of concurrency is beyond the scope of
this book, it does focus on instruction-level parallelism, which motivates the discus-
sion of dependence analysis in Chapter 9 and the vital topic of code scheduling in
Chapter 17.
Chapter 20, on optimization for the memory hierarchy, is also motivated by
modern target machines, which introduce a diversity of relative speeds of data access
in order to cope with the increasing gap between processor and memory speeds.
An additional chapter available from the publisher’s World Wide Web site discusses
object-code translation, which builds on compiler technology to translate programs
for new architectures, even when the source programs are unavailable.
The importance of interprocedural analysis and optimization has increased as
new language designs have encouraged programmers to use more sophisticated
methods for structuring large programs. Its feasibility has increased as the analysis
methods have been refined and tuned and as faster computers have made the requi-
site analyses acceptably fast. Chapter 19 is devoted to the determination and use of
interprocedural information.
Compiler design is, in its essence, an engineering activity. The methods that are
used must be ones that provide good solutions to the translation situations that
arise in practice—namely, real programs written in real languages executing on real
machines. Most of the time, the compiler writer must take the languages and the
machines as they come. Rarely is it possible to influence or improve the design of
either. It is the engineering choices of what analyses and transformations to perform
and when to perform them that determine the speed and quality of an optimizing
compiler. Both in the treatment of the optimization material throughout the book
and in the case studies in Chapter 21, these design choices are paramount.Foreword ix
One of the great strengths of the author, Steve Muchnick, is the wealth and di-
versity of his experience. After an early career as a professor of computer science,
Dr. Muchnick applied his knowledge of compilers as a vital member of the teams
that developed two important computer architectures, namely, PA-RISC at Hewlett-
Packard and sparc at Sun Microsystems. After the initial work on each architecture
was completed, he served as the leader of the advanced compiler design and im-
plementation groups for these systems. Those credentials stand him in good stead
in deciding what the reader needs to know about advanced compiler design. His
research experience, coupled with his hands-on development experience, are invalu-
able in guiding the reader through the many design decisions that a compiler designer
must make.
Susan Graham
University of California, BerkeleyContents
Foreword by Susan Graham vii
Preface
xxi
Introduction to Advanced Topics 1
11
1.2
1.3
1.4
LS
1.6
1.7
1.8
1.9
1.10
1.11
1.12
Review of Compiler Structure 1
Advanced Issues in Elementary Topics 3
The Importance of Code Optimization 6
Structure of Optimizing Compilers 7
Placement of Optimizations in Aggressive
Optimizing Compilers 11
Reading Flow Among the Chapters 14
Related Topics Not Covered in This Text 16
Target Machines Used in Examples 16
Number Notations and Data Sizes 16
Wrap-Up 17
Further Reading 18
Exercises 18
Informal Compiler Algorithm Notation (ICAN) 19
2.1
2.2
2.3
2.4
2.5
2.6
Extended Backus-Naur Form Syntax Notation 19
Introduction toICAN 20
A Quick Overview of ICAN 23
Whole Programs 25
Type Definitions 25
Declarations 26xii
Contents
2.7 Data Types and Expressions 27
2.8 Statements 36
2.9 Wrap-Up 41
2.10 Further Reading 41
2.11 Exercises 41
Symbol-Table Structure 43
3.1 Storage Classes, Visibility, and Lifetimes 43
3.2 Symbol Attributes and Symbol-Table Entries 45
3.3 Local Symbol-Table Management 47
3.4 Global Symbol-Table Structure 49
3.5 Storage Binding and Symbolic Registers 54
3.6 Approaches to Generating Loads and Stores 59
3.7 Wrap-Up 64
3.8 Further Reading 64
3.9 Exercises 64
Intermediate Representations 67
41 Issues in Designing an Intermediate Language 67
4.2 High-Level Intermediate Languages 69
4.3 Medium-Level Intermediate Languages 71
4.4 Low-Level Intermediate Languages 71
4.5 Multi-Level Intermediate Languages 72
4.6 Our Intermediate Languages: MIR, HIR, and LIR 73
4.7 Representing MIR, HIR, and LIRinICAN 81
4.8 ICAN Naming of Data Structures and Routines that Manipulate
Intermediate Code 92
4.9 Other Intermediate-Language Forms 96
4.10 Wrap-Up 101
4.11 Further Reading 102
4.12 Exercises 102
Run-Time Support 105
5.1 Data Representations and Instructions 106
5.2 Register Usage 109Contents xiii
5.3 The Local Stack Frame 111
5.4 The Run-Time Stack 114
5.5 Parameter-Passing Disciplines 116
5.6 Procedure Prologues, Epilogues, Calls, and Returns 119
5.7 Code Sharing and Position-Independent Code 127
5.8 Symbolic and Polymorphic Language Support 131
5.9 Wrap-Up 133
5.10 Further Reading 134
5.11 Exercises 135
Producing Code Generators Automatically 137
6.1 Introduction to Automatic Generation of Code Generators 138
6.2 A Syntax-Directed Technique 139
6.3 Introduction to Semantics-Directed Parsing 159
6.4 Tree Pattern Matching and Dynamic Programming 160
6.5 Wrap-Up 165
6.6 Further Reading 166
6.7 Exercises 166
Control-Flow Analysis 169
71 Approaches to Control-Flow Analysis 172
7.2 Depth-First Search, Preorder Traversal, Postorder Traversal, and
Breadth-First Search 177
7.3 Dominators and Postdominators 181
7.4 Loops and Strongly Connected Components 191
7.5 Reducibility 196
7.6 Interval Analysis and Control Trees 197
7.7 Structural Analysis 202
7.8 Wrap-Up 214
7.9 Further Reading 214
7.10 Exercises 215
Data-Flow Analysis 217
8.1 An Example: Reaching Definitions 218
8.2 Basic Concepts: Lattices, Flow Functions, and Fixed Points 223xiv
10
Contents
8.3
8.4
8.5
8.6
8.7
8.8
8.9
8.10
8.11
8.12
8.13
8.14
8.15
8.16
8.17
Taxonomy of Data-Flow Problems and Solution Methods 228
Iterative Data-Flow Analysis 231
Lattices of Flow Functions 235
Control-Tree-Based Data-Flow Analysis 236
Structural Analysis 236
Interval Analysis 249
Other Approaches 250
Du-Chains, Ud-Chains, and Webs 251
Static Single-Assignment (SSA) Form 252
Dealing with Arrays, Structures, and Pointers 258
Automating Construction of Data-Flow Analyzers 259
More Ambitious Analyses 261
Wrap-Up 263
Further Reading 264
Exercises 265
Dependence Analysis and Dependence Graphs 267
91
9.2
9.3
9.4
9.5
9.6
9.7
9.8
9.9
Dependence Relations 267
Basic-Block Dependence DAGs 269
Dependences in Loops 274
Dependence Testing 279
Program-Dependence Graphs 284
Dependences Between Dynamically Allocated Objects 286
Wrap-Up 288
Further Reading 289
Exercises 290
Alias Analysis 293
10.1
10.2
10.3
10.4
10.5
10.6
Aliases in Various Real Programming Languages 297
The Alias Gatherer 302
The Alias Propagator 307
Wrap-Up 314
Further Reading 315
Exercises 31611
12
13
14
Contents
Introduction to Optimization 319
11.1. Global Optimizations Discussed in Chapters 12 Through 18 321
11.2. Flow Sensitivity and May vs. Must Information 323
11.3. Importance of Individual Optimizations 323
11.4 Order and Repetition of Optimizations 325
11.5 Further Reading 328
11.6 Exercises 328
Early Optimizations 329
12.1 Constant-Expression Evaluation (Constant Folding) 329
12.2 Scalar Replacement of Aggregates 331
12.3 Algebraic Simplifications and Reassociation 333
12.4 Value Numbering 343
12.5 Copy Propagation 356
12.6 Sparse Conditional Constant Propagation 362
12.7. Wrap-Up 371
12.8 Further Reading 373
12.9 Exercises 374
Redundancy Elimination 377
13.1. Common-Subexpression Elimination 378
13.2. Loop-Invariant Code Motion 397
13.3. Partial-Redundancy Elimination 407
13.4 Redundancy Elimination and Reassociation 415
13.5 Code Hoisting 417
13.6 Wrap-Up 420
13.7. Further Reading 422
13.8 Exercises 422
Loop Optimizations 425
14.1. Induction-Variable Optimizations 425
14.2 Unnecessary Bounds-Checking Elimination 454
14.3. Wrap-Up 457
14.4 Further Reading 459
14.5 Exercises 46015
16
17
18
Contents
Procedure Optimizations 461
15.1
15.2
15.3
15.4
15.5
15.6
15.7
Tail-Call Optimization and Tail-Recursion Elimination 461
Procedure Integration 465
In-Line Expansion 470
Leaf-Routine Optimization and Shrink Wrapping 472
Wrap-Up 476
Further Reading 478
Exercises 478
Register Allocation 481
16.1
16.2
16.3
16.4
16.5
16.6
16.7
16.8
Register Allocation and Assignment 482
Local Methods 483
Graph Coloring 485
Priority-Based Graph Coloring 524
Other Approaches to Register Allocation 525
Wrap-Up 526
Further Reading 528
Exercises 529
Code Scheduling 531
17.1
17.2
17.3
17.4
17.5
17.6
17.7
17.8
17.9
Instruction Scheduling 532
Speculative Loads and Boosting 547
Speculative Scheduling 548
Software Pipelining 548
Trace Scheduling 569
Percolation Scheduling 571
Wrap-Up 573
Further Reading 575
Exercises 576
Control-Flow and Low-Level Optimizations 579
18.1
18.2
18.3
18.4
Unreachable-Code Elimination 580
Straightening 583
If Simplifications 585
Loop Simplifications 58619
20
Contents
18.5
18.6
18.7
18.8
18.9
18.10
18.11
18.12
18.13
18.14
18.15
Loop Inversion 587
Unswitching 588
Branch Optimizations 589
Tail Merging or Cross Jumping 590
Conditional Moves 591
Dead-Code Elimination 592
Branch Prediction 597
Machine Idioms and Instruction Combining 599
Wrap-Up 602
Further Reading 604
Exercises 605
Interprocedural Analysis and Optimization 607
19.1
19.2
19.3
19.4
19.5
19.6
19.7
19.8
19.9
19.10
19.11
Interprocedural Control-Flow Analysis: The Call Graph 609
Interprocedural Data-Flow Analysis 619
Interprocedural Constant Propagation 637
Interprocedural Alias Analysis 641
Interprocedural Optimizations 656
Interprocedural Register Allocation 659
Aggregation of Global References 663
Other Issues in Interprocedural Program Management 663
Wrap-Up 664
Further Reading 666
Exercises 667
Optimization for the Memory Hierarchy 669
20.1
20.2
20.3
20.4
20.5
20.6
20.7
20.8
Impact of Data and Instruction Caches 670
Instruction-Cache Optimization 672
Scalar Replacement of Array Elements 682
Data-Cache Optimization 687
Scalar vs. Memory-Oriented Optimizations 700
Wrap-Up 700
Further Reading 703
Exercises 704xviii
21
App. A
App. B
App. C
Contents
Case Studies of Compilers and Future Trends 705
21.1 The Sun Compilers for SPARC 707
21.2 The IBM XL Compilers for the POWER and PowerPC
Architectures 716
21.3. Digital Equipment’s Compilers for Alpha 726
21.4. The Intel Reference Compilers for the Intel 386 Architecture
Family 734
21.5 Wrap-Up 744
21.6 Future Trends in Compiler Design and Implementation 745
21.7 Further Reading 746
Guide to Assembly Languages Used in This Book 747
Al Sun SPARC Versions 8 and 9 Assembly Language 747
A.2. IBM POWER and PowerPC Assembly Language 749
A.3. DEC Alpha Assembly Language 750
A.4__ Intel 386 Architecture Assembly Language 752
A.S — Hewlett-Packard’s PA-RISC Assembly Language 753
Representation of Sets, Sequences, Trees, DAGs, and
Functions 757
B.1 Representation of Sets 759
B.2 Representation of Sequences 763
B.3 Representation of Trees and DAGs 763
B.4 Representation of Functions 764
B.S Further Reading 765
Software Resources 767
C.1 ‘Finding and Accessing Software on the Internet 767
C.2. Machine Simulators 767
C.3 Compilers 768
C.4_ Code-Generator Generators: BURG and IBURG 769
C.5 Profiling Tools +770
List of Illustrations 773Contents
List of Tables 797
Bibliography 801
Technical Index of Mathematical Formulas and 1caN Procedures
and Major Data Structures 821
Subject Index 827
xixPreface
his book concerns advanced issues in the design and implementation of
compilers, for uniprocessors, with its major emphasis (over 60% of the
text) on optimization. While it does consider machines with instruction-level
parallelism, we ignore almost completely the issues of large-scale parallelization and
vectorization.
It begins with material on compiler structure, symbol-table management (includ-
ing languages that allow scopes to be imported and exported), intermediate code
structure, run-time support issues (including shared objects that can be linked to
at run time), and automatic generation of code generators from machine descrip-
tions. Next it explores methods for intraprocedural (conventionally called global)
control-flow, data-flow, dependence, and alias analyses. Then a series of groups of
global optimizations are described, including ones that apply to program compo-
nents from simple expressions to whole procedures. Next, interprocedural analyses
of control flow, data flow, and aliases are described, followed by interprocedural
optimizations and use of interprocedural information to improve global optimiza-
tions. We then discuss optimizations designed to make effective use of the memory
hierarchy. Finally, we describe four commercial compiler systems in detail, namely,
ones from Digital Equipment Corporation, IBM, Intel, and Sun Microsysytems, to
provide specific examples of approaches to compiler structure, intermediate-code de-
sign, optimization choices, and effectiveness. As we shall see, these compiler systems
represent a wide range of approaches and often achieve similar results in different
ways.
How This Book Came to Be Written
In June 1990 and 1991, while a Distinguished Engineer at Sun Microsystems,
I presented a half-day tutorial entitled “Advanced Compiling Techniques for RISC
Systems” at the annual ACM siGpLan Conference on Programming Language De-
sign and Implementation. The tutorial was based on approximately 130 transparen-
cies on RISC architectures and relevant issues in compilers, particularly optimization.
I left that experience with the idea that somewhere within the material covered there
was a seed (the mental image was, in fact, of an acorn) yearning for sun, soil, and
water to help it grow into the mature oak tree of a book you have before you. Over
xxixxii
Preface
a year later I discussed this idea with Wayne Rosing, then President of Sun Microsys-
tems Laboratories, and within a few weeks he decided to nurture this project with a
year-and-a-half’s worth of partial funding.
The first draft that resulted included quite a lot of material on RISC architectures,
as well as material on advanced compilation issues. Before long (with the help of
three reviewers) I had decided that there was little point in including the architecture
material in the book. New risc architectures are being developed quite frequently,
the kind of coverage of them that is needed is provided in architecture courses at
most universities, and the real strengths of the text were in the compiler material.
This resulted in a major change of direction. Most of the architecture material
was dropped, keeping just those parts that support decisions on how to proceed
in compilation; the focus of the compiler material was broadened to provide equal
coverage of ciscs; and it was decided to focus entirely on uniprocessors and to
leave it to other texts to discuss parallelization and vectorization. The focus of the
compilation material was deepened and, in some respects narrowed and in others
broadened (for example, material on hand-crafted code generation was dropped
almost entirely, while advanced methods of scheduling, such as trace and percolation
scheduling, were added). The result is what you see before you.
About the Cover
The design on the cover is of a Chilkat blanket from the author’s collection of
Northwest Coast native art. The blanket was woven of fine strands of red-cedar
inner bark and mountain-goat wool in the late 19th century by a Tlingit woman
from southeastern Alaska. It generally took six to nine months of work to complete
such a blanket. The blanket design is divided into three panels, and the center panel
depicts a diving whale. The head is the split image at the bottom; the body is the
panel with the face in the center (a panel that looks like a face never represents the
face in this iconography); the lateral fins are at the sides of the body; and the tail
flukes are at the top. Each part of the design is, in itself, functional but meaningless;
assembled together in the right way, the elements combine to depict a diving whale
and proclaim the rights and prerogatives of the village chief who owned the blanket.
In a similar way, each component of a compiler is functional, but it is only when
the components are put together in the proper way that they serve their overall
purpose. Designing and weaving such a blanket requires skills that are akin to
those involved in constructing industrial-strength compilers—each discipline has a
set of required tools, materials, design elements, and overall patterns that must be
combined in a way that meets the prospective users’ needs and desires.
Audience for This Book
This book is intended for computer professionals, graduate students, and advanced
undergraduates who need to understand the issues involved in designing and con-
structing advanced compilers for uniprocessors. The reader is assumed to have had
introductory courses in data structures, algorithms, compiler design and implemen-Preface xxiii
tation, computer architecture, and assembly-language programming, or equivalent
work experience.
Overview of the Book’s Contents
This volume is divided into 21 chapters and three appendices as follows:
Chapter 1. Introduction to Advanced Topics
This chapter introduces the subject of the book, namely, advanced topics in the de-
sign and construction of compilers, and discusses compiler structure, the importance
of optimization, and how the rest of the material in the book works together.
Chapter 2. Informal Compiler Algorithm Notation (ICAN)
Chapter 2 describes and gives examples of an informal programming notation called
ICAN that is used to present algorithms in the text. After describing the notation used
to express the language’s syntax, it gives a brief overview of ICAN, followed by a
detailed description of the language. The brief description should be sufficient for
reading most of the algorithms presented and the full description should need to be
referred to only rarely.
Chapter 3. Symbol-Table Structure
Chapter 3 first discusses the attributes of variables, such as storage class, visibility,
volatility, scope, size, type, alignment, structure, addressing method, and so on.
Then it describes effective methods for structuring and managing local and global
symbol tables, including importation and exportation of scopes (as found, e.g., in
Ada, Mesa, and Modula-2); storage binding; and approaches to generating load and
store instructions that take the above characteristics into account.
Chapter 4. Intermediate Representations
This chapter focuses on intermediate language design, three specific intermediate lan-
guages used in the remainder of the book, and other basic forms of intermediate code
that might be used in a compiler. We use three closely related intermediate forms,
one high-level, one medium-level, and one low-level, to allow us to demonstrate vir-
tually all the optimizations discussed. We also discuss the relative importance and
usefulness of our chosen forms and the others.
Two other more elaborate forms of intermediate code, namely, static single-
assignment (SSA) form and program dependence graphs, are discussed in Sec-
tions 8.11 and 9.5, respectively.
Chapter 5. Run-Time Support
Chapter 5 concerns the issues involved in supporting programs written in high-level
languages at run time. It discusses data representation, register usage, design of the
stack frame and overall run-time stack, parameter passing, procedure structure andxxiv
Preface
linkage, procedure-valued variables, code sharing, position-independent code, and
issues involved in supporting symbolic and polymorphic languages.
Chapter 6. Producing Code Generators Automatically
Chapter 6 discusses automatic approaches for producing code generators from ma-
chine descriptions. We present the Graham-Glanville syntax-directed technique in
detail and introduce two other approaches, namely, semantics-directed parsing and
tree pattern matching.
Chapter 7. Control-Flow Analysis
This and the following three chapters discuss four types of analyses that apply to
procedures and that are vital to doing correct and ambitious optimization.
Chapter 7 concentrates on approaches to determining the control flow within
a procedure and to constructing a control-flow graph (CFG). It begins with an
overview of the possible approaches and then discusses three of them in detail. The
first is the classic approach of using depth-first search and dominators. In this area
we also discuss flowgraph traversals, such as preorder and postorder, of the CFG
and finding the strongly connected components of a CFG. The other two approaches
depend on the concept of reducibility, which allows the control flow of a procedure
to be composed hierarchically. One of the two is called interval analysis and the other
is called structural analysis, and the two differ in what types of structural units they
distinguish. We also discuss the representation of a procedure’s hierarchical structure
by a so-called control tree.
Chapter 8. Data-Flow Analysis
Chapter 8 discusses approaches to determining the flow of data in a procedure. It
begins with an example and next discusses the basic mathematical concepts un-
derlying data-flow analysis, namely, lattices, flow functions, and fixed points. It
continues with a taxonomy of data-flow problems and solution methods and then
proceeds to discuss in detail three techniques for solving data-flow problems that
correspond to the three approaches to control-flow analysis presented in the preced-
ing chapter. The first approach is iterative data-flow analysis, which corresponds to
the use of depth-first search and dominators. The other two approaches correspond
to the control-tree-based approaches to control-flow analysis and are known by the
same names as those analyses: interval analysis and structural analysis. This is fol-
lowed by an overview of a new sparse technique known as slotwise analysis, and
descriptions of methods of representing data-flow information, namely, du-chains,
ud-chains, webs, and static single-assignment (or SSA) form. The chapter concludes
with thoughts on how to deal with arrays, structures, and pointers and with a dis-
cussion of a method for automating construction of data-flow analyzers.
Chapter 9. Dependence Analysis and Dependence Graphs
Chapter 9 concerns dependence analysis, which is a poor-man’s version of data-flow
analysis for arrays and low-level storage references, and a closely related intermedi-
ate code form known as the program dependence graph. It first discusses dependencePreface XXV
relations and then how to compute dependence relations within a basic block, which
is vital to the code-scheduling techniques discussed in Chapter 17. Next it discusses
dependence in loops and methods of doing dependence testing, which are essential to
the data-storage optimizations discussed in Chapter 20. Finally, it discusses the pro-
gram dependence graph, which is an intermediate-code form that represents control
and data dependences directly, and that can be used to perform a series of optimiza-
tions more effectively than on an intermediate code that leaves such information
implicit.
Chapter 10. Alias Analysis
Chapter 10 discusses alias analysis, which determines whether a storage location
may be accessible by more than one access path, such as by name and through a
pointer. It discusses how aliases may impact programs in specific languages, such as
Fortran 77, Pascal, C, and Fortran 90. Next it discusses a very general approach
to determining the aliases present in a procedure that consists of a language-specific
alias gatherer and a language-independent alias propagator. The alias gatherer and
propagator can be tailored to provide information that depends or not on the control
flow of the procedure and that also provides information in a variety of other ways,
making it a general approach that can be suited to the needs and time constraints of
a particular programming language and compiler.
Chapter 11. Introduction to Optimization
Chapter 11 introduces the subject of code optimization, discusses the fact that the
applicability and effectiveness of most optimizations are recursively undecidable but
still worthwhile for programs for which they are determinable, and provides a quick
survey of the intraprocedural optimizations covered in Chapters 12 through 18. It
then discusses flow sensitivity and may vs. must information and how they apply to
optimization, the relative importance of particular optimizations, and the order in
which they should be performed.
Chapter 12. Early Optimizations
Chapter 12 discusses optimizations that are usually performed early in the opti-
mization process, namely, scalar replacement of aggregates, local and global value
numbering (performed on SSA-form code), local and global copy propagation, and
sparse conditional constant propagation (also performed on SSA-form code). It also
discusses constant expression evaluation (or constant folding) and algebraic simpli-
fication and how they are best included in an optimizer as subroutines that can be
called from wherever they are needed.
Chapter 13. Redundancy Elimination
Chapter 13 concerns several types of redundancy elimination, which, in essence,
delete computations that are performed more than once on a path through a pro-
cedure. It describes local and global common-subexpression elimination, forward
substitution, loop-invariant code motion, partial-redundancy elimination, and code
hoisting.