100% found this document useful (1 vote)
184 views512 pages

Compiler Construction

Uploaded by

Matias Fernandez
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
184 views512 pages

Compiler Construction

Uploaded by

Matias Fernandez
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Compiler construction

PDF generated using the open source mwlib toolkit. See http://code.pediapress.com/ for more information.
PDF generated at: Tue, 20 Sep 2011 17:42:02 UTC
Contents
Articles
Introduction 1
Compiler construction 1
Compiler 2
Interpreter 10
History of compiler writing 13

Lexical analysis 21
Lexical analysis 21
Regular expression 25
Regular expression examples 36
Finite-state machine 40
Preprocessor 50

Syntactic analysis 53
Parsing 53
Lookahead 57
Symbol table 60
Abstract syntax 62
Abstract syntax tree 63
Context-free grammar 64
Terminal and nonterminal symbols 76
Left recursion 78
Backus–Naur Form 82
Extended Backus–Naur Form 85
TBNF 90
Top-down parsing 90
Recursive descent parser 92
Tail recursive parser 97
Parsing expression grammar 99
LL parser 105
LR parser 113
Parsing table 122
Simple LR parser 124
Canonical LR parser 125
GLR parser 127
LALR parser 128
Recursive ascent parser 130
Parser combinator 138
Bottom-up parsing 140
Chomsky normal form 146
CYK algorithm 148
Simple precedence grammar 151
Simple precedence parser 152
Operator-precedence grammar 154
Operator-precedence parser 157
Shunting-yard algorithm 161
Chart parser 171
Earley parser 172
The lexer hack 176
Scannerless parsing 177

Semantic analysis 180


Attribute grammar 180
L-attributed grammar 182
LR-attributed grammar 182
S-attributed grammar 183
ECLR-attributed grammar 183
Intermediate representation 184
Intermediate language 185
Control flow graph 187
Basic block 189
Call graph 191
Data-flow analysis 194
Use-define chain 199
Live variable analysis 202
Reaching definition 204
Three address code 205
Static single assignment form 206
Dominator 212
C3 linearization 214
Intrinsic function 215
Aliasing 216
Alias analysis 218
Array access analysis 220
Pointer analysis 220
Escape analysis 221
Shape analysis 222
Loop dependence analysis 224
Program slicing 227

Code optimization 230


Compiler optimization 230
Peephole optimization 241
Copy propagation 244
Constant folding 245
Sparse conditional constant propagation 247
Common subexpression elimination 248
Partial redundancy elimination 249
Global value numbering 250
Strength reduction 251
Bounds-checking elimination 262
Inline expansion 263
Return value optimization 266
Dead code 269
Dead code elimination 270
Unreachable code 272
Redundant code 274
Jump threading 275
Superoptimization 275
Loop optimization 276
Induction variable 278
Loop fission 281
Loop fusion 282
Loop inversion 283
Loop interchange 285
Loop-invariant code motion 286
Loop nest optimization 287
Manifest expression 291
Polytope model 292
Loop unwinding 294
Loop splitting 301
Loop tiling 302
Loop unswitching 304
Interprocedural optimization 305
Whole program optimization 309
Adaptive optimization 309
Lazy evaluation 310
Partial evaluation 314
Profile-guided optimization 315
Automatic parallelization 316
Loop scheduling 318
Vectorization 318
Superword Level Parallelism 326

Code generation 327


Code generation 327
Name mangling 329
Register allocation 338
Chaitin's algorithm 340
Rematerialization 340
Sethi-Ullman algorithm 341
Data structure alignment 343
Instruction selection 351
Instruction scheduling 352
Software pipelining 354
Trace scheduling 358
Just-in-time compilation 358
Bytecode 362
Dynamic compilation 364
Dynamic recompilation 365
Object file 367
Code segment 368
Data segment 369
.bss 371
Literal pool 372
Overhead code 372
Link time 373
Relocation 373
Library 374
Static build 381
Architecture Neutral Distribution Format 382

Development techniques 384


Bootstrapping 384
Compiler correctness 385
Jensen's Device 387
Man or boy test 388
Cross compiler 390
Source-to-source compiler 396

Tools 398
Compiler-compiler 398
PQCC 400
Compiler Description Language 401
Comparison of regular expression engines 403
Comparison of parser generators 409
Lex 420
flex lexical analyser 423
Quex 430
JLex 433
Ragel 434
yacc 435
Berkeley Yacc 436
ANTLR 437
GNU bison 439
Coco/R 449
GOLD 451
JavaCC 456
JetPAG 457
Lemon Parser Generator 460
ROSE compiler framework 461
SableCC 463
Scannerless Boolean Parser 464
Spirit Parser Framework 465
S/SL programming language 467
SYNTAX 468
Syntax Definition Formalism 469
TREE-META 471
Frameworks supporting the polyhedral model 473

Case studies 478


GNU Compiler Collection 478
Java performance 487

Literature 497
Compilers: Principles, Techniques, and Tools 497
Principles of Compiler Design 499
The Design of an Optimizing Compiler 499

References
Article Sources and Contributors 500
Image Sources, Licenses and Contributors 504

Article Licenses
License 505
1

Introduction

Compiler construction
Compiler construction is an area of computer science that deals with the theory and practice of developing
programming languages and their associated compilers.
The theoretical portion is primarily concerned with syntax, grammar and semantics of programming languages. One
could say that this gives this particular area of computer science a strong tie with linguistics. Some courses on
compiler construction will include a simplified grammar of a spoken language that can be used to form a valid
sentence for the purposes of providing students with an analogy to help them understand how grammar works for
programming languages.
The practical portion covers actual implementation of compilers for languages. Students will typically end up writing
the front end of a compiler for a simplistic teaching language, such as Micro.

Subfields
• Parsing
• Program analysis
• Program transformation
• Compiler or program optimization
• Code generation

Further reading
• Alfred V. Aho, Monica S. Lam, Ravi Sethi, Jeffrey D. Ullman. Compilers: Principles, Techniques, and Tools.
• Michael Wolfe. High-Performance Compilers for Parallel Computing. ISBN 978-0805327304

External links
• The ACE CoSy compiler development suite [1], an industry leading C/C++ compiler development suite.
• The LLVM Compiler Infrastructure project [2], The leading open source compiler tools project.
• Let's Build a Compiler, by Jack Crenshaw [3], An interesting tutorial on compiler construction.
• Compiler Construction at the University of New England [4]

References
[1] http:/ / www. ace. nl/ compiler/ cosy. html
[2] http:/ / llvm. org/
[3] http:/ / compilers. iecc. com/ crenshaw/
[4] http:/ / mcs. une. edu. au/ ~comp319/
Compiler 2

Compiler
A compiler is a computer program (or
set of programs) that transforms source
code written in a programming
language (the source language) into
another computer language (the target
language, often having a binary form
known as object code). The most
common reason for wanting to
transform source code is to create an
executable program.

The name "compiler" is primarily used


for programs that translate source code
from a high-level programming
language to a lower level language
(e.g., assembly language or machine
code). If the compiled program can run
on a computer whose CPU or
operating system is different from the
one on which the compiler runs, the
compiler is known as a cross-compiler.
A program that translates from a low
level language to a higher level one is
a decompiler. A program that A diagram of the operation of a typical multi-language, multi-target compiler.

translates between high-level


languages is usually called a language translator, source to source translator, or language converter. A language
rewriter is usually a program that translates the form of expressions without a change of language.

A compiler is likely to perform many or all of the following operations: lexical analysis, preprocessing, parsing,
semantic analysis (Syntax-directed translation), code generation, and code optimization.
Program faults caused by incorrect compiler behavior can be very difficult to track down and work around; therefore,
compiler implementors invest a lot of time ensuring the correctness of their software.
The term compiler-compiler is sometimes used to refer to a parser generator, a tool often used to help create the lexer
and parser.
Compiler 3

History
Software for early computers was primarily written in assembly language for many years. Higher level programming
languages were not invented until the benefits of being able to reuse software on different kinds of CPUs started to
become significantly greater than the cost of writing a compiler. The very limited memory capacity of early
computers also created many technical problems when implementing a compiler.
Towards the end of the 1950s, machine-independent programming languages were first proposed. Subsequently,
several experimental compilers were developed. The first compiler was written by Grace Hopper, in 1952, for the
A-0 programming language. The FORTRAN team led by John Backus at IBM is generally credited as having
introduced the first complete compiler in 1957. COBOL was an early language to be compiled on multiple
architectures, in 1960.[1]
In many application domains the idea of using a higher level language quickly caught on. Because of the expanding
functionality supported by newer programming languages and the increasing complexity of computer architectures,
compilers have become more and more complex.
Early compilers were written in assembly language. The first self-hosting compiler — capable of compiling its own
source code in a high-level language — was created for Lisp by Tim Hart and Mike Levin at MIT in 1962.[2] Since
the 1970s it has become common practice to implement a compiler in the language it compiles, although both Pascal
and C have been popular choices for implementation language. Building a self-hosting compiler is a bootstrapping
problem—the first such compiler for a language must be compiled either by a compiler written in a different
language, or (as in Hart and Levin's Lisp compiler) compiled by running the compiler in an interpreter.

Compilers in education
Compiler construction and compiler optimization are taught at universities and schools as part of the computer
science curriculum. Such courses are usually supplemented with the implementation of a compiler for an educational
programming language. A well-documented example is Niklaus Wirth's PL/0 compiler, which Wirth used to teach
compiler construction in the 1970s.[3] In spite of its simplicity, the PL/0 compiler introduced several influential
concepts to the field:
1. Program development by stepwise refinement (also the title of a 1971 paper by Wirth)[4]
2. The use of a recursive descent parser
3. The use of EBNF to specify the syntax of a language
4. A code generator producing portable P-code
5. The use of T-diagrams[5] in the formal description of the bootstrapping problem

Compilation
Compilers enabled the development of programs that are machine-independent. Before the development of
FORTRAN (FORmula TRANslator), the first higher-level language, in the 1950s, machine-dependent assembly
language was widely used. While assembly language produces more reusable and relocatable programs than machine
code on the same architecture, it has to be modified or rewritten if the program is to be executed on different
hardware architecture.
With the advance of high-level programming languages soon followed after FORTRAN, such as COBOL, C,
BASIC, programmers can write machine-independent source programs. A compiler translates the high-level source
programs into target programs in machine languages for the specific hardwares. Once the target program is
generated, the user can execute the program.
Compiler 4

The structure of a compiler


Compilers bridge source programs in high-level languages with the underlying hardware. A compiler requires 1)
determining the correctness of the syntax of programs, 2) generating correct and efficient object code, 3) run-time
organization, and 4) formatting output according to assembler and/or linker conventions. A compiler consists of
three main parts: the frontend, the middle-end, and the backend.
The front end checks whether the program is correctly written in terms of the programming language syntax and
semantics. Here legal and illegal programs are recognized. Errors are reported, if any, in a useful way. Type
checking is also performed by collecting type information. The frontend then generates an intermediate
representation or IR of the source code for processing by the middle-end.
The middle end is where optimization takes place. Typical transformations for optimization are removal of useless
or unreachable code, discovery and propagation of constant values, relocation of computation to a less frequently
executed place (e.g., out of a loop), or specialization of computation based on the context. The middle-end generates
another IR for the following backend. Most optimization efforts are focused on this part.
The back end is responsible for translating the IR from the middle-end into assembly code. The target instruction(s)
are chosen for each IR instruction. Register allocation assigns processor registers for the program variables where
possible. The backend utilizes the hardware by figuring out how to keep parallel execution units busy, filling delay
slots, and so on. Although most algorithms for optimization are in NP, heuristic techniques are well-developed.

Compiler output
One classification of compilers is by the platform on which their generated code executes. This is known as the
target platform.
A native or hosted compiler is one which output is intended to directly run on the same type of computer and
operating system that the compiler itself runs on. The output of a cross compiler is designed to run on a different
platform. Cross compilers are often used when developing software for embedded systems that are not intended to
support a software development environment.
The output of a compiler that produces code for a virtual machine (VM) may or may not be executed on the same
platform as the compiler that produced it. For this reason such compilers are not usually classified as native or cross
compilers.
The lower level language that is target of a compiler may itself be a high-level programming language. C, often
viewed as some sort of portable assembler, can also be the target language of a compiler. E.g.: Cfront, the original
compiler for C++ used C as target language.

Compiled versus interpreted languages


Higher-level programming languages are generally divided for convenience into compiled languages and interpreted
languages. However, in practice there is rarely anything about a language that requires it to be exclusively compiled
or exclusively interpreted, although it is possible to design languages that rely on re-interpretation at run time. The
categorization usually reflects the most popular or widespread implementations of a language — for instance,
BASIC is sometimes called an interpreted language, and C a compiled one, despite the existence of BASIC
compilers and C interpreters.
Modern trends toward just-in-time compilation and bytecode interpretation at times blur the traditional
categorizations of compilers and interpreters.
Some language specifications spell out that implementations must include a compilation facility; for example,
Common Lisp. However, there is nothing inherent in the definition of Common Lisp that stops it from being
interpreted. Other languages have features that are very easy to implement in an interpreter, but make writing a
compiler much harder; for example, APL, SNOBOL4, and many scripting languages allow programs to construct
Compiler 5

arbitrary source code at runtime with regular string operations, and then execute that code by passing it to a special
evaluation function. To implement these features in a compiled language, programs must usually be shipped with a
runtime library that includes a version of the compiler itself.

Hardware compilation
The output of some compilers may target hardware at a very low level, for example a Field Programmable Gate
Array (FPGA) or structured Application-specific integrated circuit (ASIC). Such compilers are said to be hardware
compilers or synthesis tools because the source code they compile effectively controls the final configuration of the
hardware and how it operates; the output of the compilation are not instructions that are executed in sequence - only
an interconnection of transistors or lookup tables. For example, XST is the Xilinx Synthesis Tool used for
configuring FPGAs. Similar tools are available from Altera, Synplicity, Synopsys and other vendors.

Compiler construction
In the early days, the approach taken to compiler design used to be directly affected by the complexity of the
processing, the experience of the person(s) designing it, and the resources available.
A compiler for a relatively simple language written by one person might be a single, monolithic piece of software.
When the source language is large and complex, and high quality output is required, the design may be split into a
number of relatively independent phases. Having separate phases means development can be parceled up into small
parts and given to different people. It also becomes much easier to replace a single phase by an improved one, or to
insert new phases later (e.g., additional optimizations).
The division of the compilation processes into phases was championed by the Production Quality
Compiler-Compiler Project (PQCC) at Carnegie Mellon University. This project introduced the terms front end,
middle end, and back end.
All but the smallest of compilers have more than two phases. However, these phases are usually regarded as being
part of the front end or the back end. The point at which these two ends meet is open to debate. The front end is
generally considered to be where syntactic and semantic processing takes place, along with translation to a lower
level of representation (than source code).
The middle end is usually designed to perform optimizations on a form other than the source code or machine code.
This source code/machine code independence is intended to enable generic optimizations to be shared between
versions of the compiler supporting different languages and target processors.
The back end takes the output from the middle. It may perform more analysis, transformations and optimizations that
are for a particular computer. Then, it generates code for a particular processor and OS.
This front-end/middle/back-end approach makes it possible to combine front ends for different languages with back
ends for different CPUs. Practical examples of this approach are the GNU Compiler Collection, LLVM, and the
Amsterdam Compiler Kit, which have multiple front-ends, shared analysis and multiple back-ends.
Compiler 6

One-pass versus multi-pass compilers


Classifying compilers by number of passes has its background in the hardware resource limitations of computers.
Compiling involves performing lots of work and early computers did not have enough memory to contain one
program that did all of this work. So compilers were split up into smaller programs which each made a pass over the
source (or some representation of it) performing some of the required analysis and translations.
The ability to compile in a single pass has classically been seen as a benefit because it simplifies the job of writing a
compiler and one-pass compilers generally perform compilations faster than multi-pass compilers. Thus, partly
driven by the resource limitations of early systems, many early languages were specifically designed so that they
could be compiled in a single pass (e.g., Pascal).
In some cases the design of a language feature may require a compiler to perform more than one pass over the
source. For instance, consider a declaration appearing on line 20 of the source which affects the translation of a
statement appearing on line 10. In this case, the first pass needs to gather information about declarations appearing
after statements that they affect, with the actual translation happening during a subsequent pass.
The disadvantage of compiling in a single pass is that it is not possible to perform many of the sophisticated
optimizations needed to generate high quality code. It can be difficult to count exactly how many passes an
optimizing compiler makes. For instance, different phases of optimization may analyse one expression many times
but only analyse another expression once.
Splitting a compiler up into small programs is a technique used by researchers interested in producing provably
correct compilers. Proving the correctness of a set of small programs often requires less effort than proving the
correctness of a larger, single, equivalent program.
While the typical multi-pass compiler outputs machine code from its final pass, there are several other types:
• A "source-to-source compiler" is a type of compiler that takes a high level language as its input and outputs a high
level language. For example, an automatic parallelizing compiler will frequently take in a high level language
program as an input and then transform the code and annotate it with parallel code annotations (e.g. OpenMP) or
language constructs (e.g. Fortran's DOALL statements).
• Stage compiler that compiles to assembly language of a theoretical machine, like some Prolog implementations
• This Prolog machine is also known as the Warren Abstract Machine (or WAM).
• Bytecode compilers for Java, Python, and many more are also a subtype of this.
• Just-in-time compiler, used by Smalltalk and Java systems, and also by Microsoft .NET's Common Intermediate
Language (CIL)
• Applications are delivered in bytecode, which is compiled to native machine code just prior to execution.

Front end
The front end analyzes the source code to build an internal representation of the program, called the intermediate
representation or IR. It also manages the symbol table, a data structure mapping each symbol in the source code to
associated information such as location, type and scope. This is done over several phases, which includes some of
the following:
1. Line reconstruction. Languages which strop their keywords or allow arbitrary spaces within identifiers require a
phase before parsing, which converts the input character sequence to a canonical form ready for the parser. The
top-down, recursive-descent, table-driven parsers used in the 1960s typically read the source one character at a
time and did not require a separate tokenizing phase. Atlas Autocode, and Imp (and some implementations of
ALGOL and Coral 66) are examples of stropped languages which compilers would have a Line Reconstruction
phase.
2. Lexical analysis breaks the source code text into small pieces called tokens. Each token is a single atomic unit of
the language, for instance a keyword, identifier or symbol name. The token syntax is typically a regular language,
Compiler 7

so a finite state automaton constructed from a regular expression can be used to recognize it. This phase is also
called lexing or scanning, and the software doing lexical analysis is called a lexical analyzer or scanner.
3. Preprocessing. Some languages, e.g., C, require a preprocessing phase which supports macro substitution and
conditional compilation. Typically the preprocessing phase occurs before syntactic or semantic analysis; e.g. in
the case of C, the preprocessor manipulates lexical tokens rather than syntactic forms. However, some languages
such as Scheme support macro substitutions based on syntactic forms.
4. Syntax analysis involves parsing the token sequence to identify the syntactic structure of the program. This phase
typically builds a parse tree, which replaces the linear sequence of tokens with a tree structure built according to
the rules of a formal grammar which define the language's syntax. The parse tree is often analyzed, augmented,
and transformed by later phases in the compiler.
5. Semantic analysis is the phase in which the compiler adds semantic information to the parse tree and builds the
symbol table. This phase performs semantic checks such as type checking (checking for type errors), or object
binding (associating variable and function references with their definitions), or definite assignment (requiring all
local variables to be initialized before use), rejecting incorrect programs or issuing warnings. Semantic analysis
usually requires a complete parse tree, meaning that this phase logically follows the parsing phase, and logically
precedes the code generation phase, though it is often possible to fold multiple phases into one pass over the code
in a compiler implementation.

Back end
The term back end is sometimes confused with code generator because of the overlapped functionality of generating
assembly code. Some literature uses middle end to distinguish the generic analysis and optimization phases in the
back end from the machine-dependent code generators.
The main phases of the back end include the following:
1. Analysis: This is the gathering of program information from the intermediate representation derived from the
input. Typical analyses are data flow analysis to build use-define chains, dependence analysis, alias analysis,
pointer analysis, escape analysis etc. Accurate analysis is the basis for any compiler optimization. The call graph
and control flow graph are usually also built during the analysis phase.
2. Optimization: the intermediate language representation is transformed into functionally equivalent but faster (or
smaller) forms. Popular optimizations are inline expansion, dead code elimination, constant propagation, loop
transformation, register allocation and even automatic parallelization.
3. Code generation: the transformed intermediate language is translated into the output language, usually the native
machine language of the system. This involves resource and storage decisions, such as deciding which variables
to fit into registers and memory and the selection and scheduling of appropriate machine instructions along with
their associated addressing modes (see also Sethi-Ullman algorithm).
Compiler analysis is the prerequisite for any compiler optimization, and they tightly work together. For example,
dependence analysis is crucial for loop transformation.
In addition, the scope of compiler analysis and optimizations vary greatly, from as small as a basic block to the
procedure/function level, or even over the whole program (interprocedural optimization). Obviously, a compiler can
potentially do a better job using a broader view. But that broad view is not free: large scope analysis and
optimizations are very costly in terms of compilation time and memory space; this is especially true for
interprocedural analysis and optimizations.
Interprocedural analysis and optimizations are common in modern commercial compilers from HP, IBM, SGI, Intel,
Microsoft, and Sun Microsystems. The open source GCC was criticized for a long time for lacking powerful
interprocedural optimizations, but it is changing in this respect. Another open source compiler with full analysis and
optimization infrastructure is Open64, which is used by many organizations for research and commercial purposes.
Compiler 8

Due to the extra time and space needed for compiler analysis and optimizations, some compilers skip them by
default. Users have to use compilation options to explicitly tell the compiler which optimizations should be enabled.

Compiler correctness
Compiler correctness is the branch of software engineering that deals with trying to show that a compiler behaves
according to its language specification. Techniques include developing the compiler using formal methods and using
rigorous testing (often called compiler validation) on an existing compiler.

Related techniques
Assembly language is a type of low-level language and a program that compiles it is more commonly known as an
assembler, with the inverse program known as a disassembler.
A program that translates from a low level language to a higher level one is a decompiler.
A program that translates between high-level languages is usually called a language translator, source to source
translator, language converter, or language rewriter. The last term is usually applied to translations that do not
involve a change of language.

International conferences and organizations


Every year, the European Joint Conferences on Theory and Practice of Software (ETAPS) sponsors the
International Conference on Compiler Construction (CC), with papers from both the academic and industrial
sectors.[6]

Notes
[1] "IP: The World's First COBOL Compilers" (http:/ / www. interesting-people. org/ archives/ interesting-people/ 199706/ msg00011. html).
interesting-people.org. 12 June 1997. .
[2] T. Hart and M. Levin. "The New Compiler, AIM-39 - CSAIL Digital Archive - Artificial Intelligence Laboratory Series" (ftp:/ / publications.
ai. mit. edu/ ai-publications/ pdf/ AIM-039. pdf). publications.ai.mit.edu. .
[3] "The PL/0 compiler/interpreter" (http:/ / www. 246. dk/ pl0. html). .
[4] "The ACM Digital Library" (http:/ / www. acm. org/ classics/ dec95/ ). .
[5] T diagrams were first introduced for describing bootstrapping and cross-compiling compilers in McKeeman et al. A Compiler Generator
(1971). Conway described the broader concept before that with his UNCOL in 1958, to which Bratman added in 1961: H. Bratman, “An
alternate form of the ´UNCOL diagram´“, Comm. ACM 4 (March 1961) 3, p. 142. Later on, others, including P.D. Terry, gave an explanation
and usage of T-diagrams in their textbooks on the topic of compiler construction. Cf. Terry, 1997, Chapter 3 (http:/ / scifac. ru. ac. za/
compilers/ cha03g. htm). T-diagrams are also now used to describe client-server interconnectivity on the World Wide Web: cf. Patrick
Closhen, et al. 1997: T-Diagrams as Visual Language to Illustrate WWW Technology (http:/ / pu. rbg. informatik. tu-darmstadt. de/ docs/
HJH-19990217-etal-T-diagrams. doc), Darmstadt University of Technology, Darmstadt, Germany
[6] ETAPS (http:/ / www. etaps. org/ ) - European Joint Conferences on Theory and Practice of Software. Cf. "CC" (Compiler Construction)
subsection.

References
• Compiler textbook references (http://www.informatik.uni-trier.de/~ley/db/books/compiler/index.html) A
collection of references to mainstream Compiler Construction Textbooks
• Aho, Alfred V.; Sethi, Ravi; and Ullman, Jeffrey D., Compilers: Principles, Techniques and Tools (ISBN
0-201-10088-6) link to publisher (http://www.aw.com/catalog/academic/product/0,4096,0201100886,00.
html). Also known as “The Dragon Book.”
• Allen, Frances E., "A History of Language Processor Technology in IBM" (http://www.research.ibm.com/
journal/rd/255/ibmrd2505Q.pdf), IBM Journal of Research and Development, v.25, no.5, September 1981.
Compiler 9

• Allen, Randy; and Kennedy, Ken, Optimizing Compilers for Modern Architectures, Morgan Kaufmann
Publishers, 2001. ISBN 1-55860-286-0
• Appel, Andrew Wilson
• Modern Compiler Implementation in Java, 2nd edition. Cambridge University Press, 2002. ISBN
0-521-82060-X
• Modern Compiler Implementation in ML (http://books.google.com/books?id=8APOYafUt-oC&
printsec=frontcover), Cambridge University Press, 1998. ISBN 0-521-58274-1
• Bornat, Richard, Understanding and Writing Compilers: A Do It Yourself Guide (http://www.cs.mdx.ac.uk/
staffpages/r_bornat/books/compiling.pdf), Macmillan Publishing, 1979. ISBN 0-333-21732-2
• Cooper, Keith D., and Torczon, Linda, Engineering a Compiler, Morgan Kaufmann, 2004, ISBN 1-55860-699-8.
• Leverett; Cattel; Hobbs; Newcomer; Reiner; Schatz; Wulf, An Overview of the Production Quality
Compiler-Compiler Project, in Computer 13(8):38-49 (August 1980)
• McKeeman, William Marshall; Horning, James J.; Wortman, David B., A Compiler Generator (http://www.cs.
toronto.edu/XPL/), Englewood Cliffs, N.J. : Prentice-Hall, 1970. ISBN 0-13-155077-2
• Muchnick, Steven, Advanced Compiler Design and Implementation (http://books.google.com/
books?id=Pq7pHwG1_OkC&printsec=frontcover&source=gbs_summary_r&cad=0), Morgan Kaufmann
Publishers, 1997. ISBN 1-55860-320-4
• Scott, Michael Lee, Programming Language Pragmatics (http://books.google.com/
books?id=4LMtA2wOsPcC&printsec=frontcover&dq=Programming+Language+Pragmatics), Morgan
Kaufmann, 2005, 2nd edition, 912 pages. ISBN 0-12-633951-1 ( The author's site on this book (http://www.cs.
rochester.edu/~scott/pragmatics/)).
• Srikant, Y. N.; Shankar, Priti, The Compiler Design Handbook: Optimizations and Machine Code Generation
(http://books.google.com/books?id=0K_jIsgyNpoC&printsec=frontcover), CRC Press, 2003. ISBN
0-8493-1240-X
• Terry, Patrick D., Compilers and Compiler Generators: An Introduction with C++ (http://scifac.ru.ac.za/
compilers/conts.htm), International Thomson Computer Press, 1997. ISBN 1-85032-298-8,
• Wirth, Niklaus, Construction (http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.70.3774&
rep=rep1&type=pdf''Compiler) (ISBN 0-201-40353-6), Addison-Wesley, 1996, 176 pages. Revised November
2005.

External links
• The comp.compilers newsgroup and RSS feed (http://compilers.iecc.com/)
• Hardware compilation mailing list (http://www.jiscmail.ac.uk/lists/hwcomp.html)
• Practical introduction to compiler construction using flex and yacc (http://www.onyxbits.de/content/blog/
patrick/introduction-compiler-construction-using-flex-and-yacc)
• Book " Basics of Compiler Design (http://www.diku.dk/hjemmesider/ansatte/torbenm/Basics/)" by Torben
Ægidius Mogensen
Interpreter 10

Interpreter
In computer science, an interpreter normally means a computer program that executes, i.e. performs, instructions
written in a programming language. An interpreter may be a program that either
1. executes the source code directly
2. translates source code into some efficient intermediate representation (code) and immediately executes this
3. explicitly executes stored precompiled code[1] made by a compiler which is part of the interpreter system
Perl, Python, MATLAB, and Ruby are examples of type 2, while UCSD Pascal is type 3: Source programs are
compiled ahead of time and stored as machine independent code, which is then linked at run-time and executed by
an interpreter and/or compiler (for JIT systems). Some systems, such as Smalltalk, BASIC, Java and others, may also
combine 2 and 3.
While interpreting and compiling are the two main means by which programming languages are implemented, these
are not fully distinct categories, one of the reasons being that most interpreting systems also perform some
translation work, just like compilers. The terms "interpreted language" or "compiled language" merely mean that the
canonical implementation of that language is an interpreter or a compiler; a high level language is basically an
abstraction which is (ideally) independent of particular implementations.

Bytecode interpreters
There is a spectrum of possibilities between interpreting and compiling, depending on the amount of analysis
performed before the program is executed. For example, Emacs Lisp is compiled to bytecode, which is a highly
compressed and optimized representation of the Lisp source, but is not machine code (and therefore not tied to any
particular hardware). This "compiled" code is then interpreted by a bytecode interpreter (itself written in C). The
compiled code in this case is machine code for a virtual machine, which is implemented not in hardware, but in the
bytecode interpreter. The same approach is used with the Forth code used in Open Firmware systems: the source
language is compiled into "F code" (a bytecode), which is then interpreted by a virtual machine.
Control tables - that do not necessarily ever need to pass through a compiling phase - dictate appropriate algorithmic
control flow via customized interpreters in similar fashion to bytecode interpreters.

Efficiency
The main disadvantage of interpreters is that when a program is interpreted, it typically runs more slowly than if it
had been compiled. The difference in speeds could be tiny or great; often an order of magnitude and sometimes
more. It generally takes longer to run a program under an interpreter than to run the compiled code but it can take
less time to interpret it than the total time required to compile and run it. This is especially important when
prototyping and testing code when an edit-interpret-debug cycle can often be much shorter than an
edit-compile-run-debug cycle.
Interpreting code is slower than running the compiled code because the interpreter must analyze each statement in
the program each time it is executed and then perform the desired action, whereas the compiled code just performs
the action within a fixed context determined by the compilation. This run-time analysis is known as "interpretive
overhead". Access to variables is also slower in an interpreter because the mapping of identifiers to storage locations
must be done repeatedly at run-time rather than at compile time.
There are various compromises between the development speed when using an interpreter and the execution speed
when using a compiler. Some systems (such as some Lisps) allow interpreted and compiled code to call each other
and to share variables. This means that once a routine has been tested and debugged under the interpreter it can be
compiled and thus benefit from faster execution while other routines are being developed. Many interpreters do not
Interpreter 11

execute the source code as it stands but convert it into some more compact internal form. Many BASIC interpreters
replace keywords with single byte tokens which can be used to find the instruction in a jump table. Others achieve
even higher levels of program compaction by using a bit-orientated rather than a byte-orientated program memory
structure, where commands tokens may occupy perhaps 5 bits of a 16 bit word, leaving 11 bits for their label or
address operands.
An interpreter might well use the same lexical analyzer and parser as the compiler and then interpret the resulting
abstract syntax tree.

Advantages and disadvantages of using interpreters


Programmers usually write programs in high level code, which the CPU cannot execute; so this source code has to
be converted into machine code. This conversion is done by a compiler or an interpreter. A compiler makes the
conversion just once, while an interpreter typically converts it every time a program is executed (or in some
languages like early versions of BASIC, every time a single instruction is executed).

Development cycle
During program development, the programmer makes frequent changes to source code. With a compiler, each time
the programmer makes a change to the source code, s/he must wait for the compiler to make a compilation of the
altered source files, and link all of the binary code files together before the program can be executed. The larger the
program, the longer the programmer must wait to see the results of the change. By contrast, a programmer using an
interpreter does a lot less waiting, as the interpreter usually just needs to translate the code being worked on to an
intermediate representation (or not translate it at all), thus requiring much less time before the changes can be tested.

Distribution
A compiler converts source code into binary instruction for a specific processor's architecture, thus making it less
portable. This conversion is made just once, on the developer's environment, and after that the same binary can be
distributed to the user's machines where it can be executed without further translation. A cross compiler can generate
binary code for the user machine even if it has a different processor than the machine where the code is compiled.
An interpreted program can be distributed as source code. It needs to be translated in each final machine, which takes
more time but makes the program distribution independent to the machine's architecture.

Execution environment
An interpreter makes source translations during runtime, meaning that every line of code must be converted to
machine --- code each time the program runs. This process slows down the program's execution, something that does
not occur for compiled programs. This slowness puts interpreters at a major disadvantage when compared with
compilers. Another key disadvantage of an interpreter is that it must be present on the user's machine, (as additional
software) to run the program.

Abstract Syntax Tree interpreters


In the spectrum between interpreting and compiling, another approach is transforming the source code into an
optimized Abstract Syntax Tree (AST) then executing the program following this tree structure, or using it to
generate native code Just-In-Time.[2] In this approach, each sentence needs to be parsed just once. As an advantage
over bytecode, the AST keeps the global program structure and relations between statements (which is lost in a
bytecode representation), and when compressed provides a more compact representation.[3] Thus, using AST has
been proposed as a better intermediate format for Just-in-time compilers than bytecode. Also, it allows to perform
better analysis during runtime.
Interpreter 12

However, for interpreters, an AST causes more overhead than a bytecode interpreter, because of nodes related to
syntax performing no useful work, of a less sequential representation (requiring traversal of more pointers) and of
overhead visiting the tree.[4]

Just-in-time compilation
Further blurring the distinction between interpreters, byte-code interpreters and compilation is just-in-time
compilation (or JIT), a technique in which the intermediate representation is compiled to native machine code at
runtime. This confers the efficiency of running native code, at the cost of startup time and increased memory use
when the bytecode or AST is first compiled. Adaptive optimization is a complementary technique in which the
interpreter profiles the running program and compiles its most frequently-executed parts into native code. Both
techniques are a few decades old, appearing in languages such as Smalltalk in the 1980s.[5]
Just-in-time compilation has gained mainstream attention amongst language implementers in recent years, with Java,
the .NET Framework and most modern JavaScript implementations now including JITs.

Punched card interpreter


The term "interpreter" often referred to a piece of unit record equipment that could read punched cards and print the
characters in human-readable form on the card. The IBM 550 Numeric Interpreter and IBM 557 Alphabetic
Interpreter are typical examples from 1930 and 1954, respectively.

Another definition of interpreter


Instead of producing a target program as a translation, an interpreter performs the operation implied by the source
program. For an assignment statement, for example, an interpreter might build a tree and then carry out