Comp Chapter 1
Comp Chapter 1
Compiler is a program which translates a program written in one language (the source language) to an
equivalent program in other language (the target language). Usually the source language is a high level
language like Java, C, Fortran etc. whereas the target language is machine code or "code" that a computer's
processor understands. The source language is optimized for humans. It is more user-friendly, to some
extent platform-independent. They are easier to read, write, and maintain and hence it is easy to avoid errors.
Ultimately, programs written in a high-level language must be translated into machine language by a
compiler. The target machine language is efficient for hardware but lacks readability.
Compilers
. Translates from one representation of the program to another
. Typically from high level source code to low level machine code or object code
. Source code is normally optimized for human readability
- Expressive: matches our notion of languages (and application?!)
- Redundant to help avoid programming errors
. Machine code is optimized for hardware
- Redundancy is reduced
- Information about the intent is lost
How to translate?
• The high level languages and machine languages differ in level of abstraction. At machine
level we deal with memory locations, registers whereas these resources are never accessed
in high level languages. But the level of abstraction differs from language to language and
some languages are farther from machine code than others
• Goals of translation
• Good performance for the generated code
• Good performance for generated code : The metric for the quality of the generated code is
the ratio between the size of handwritten code and compiled machine code for same
program. A better compiler is one which generates smaller code. For optimizing compilers this
ratio will be lesser.
• Good compile time performance
• Good compile time performance : A handwritten machine code is more efficient than a
compiled code in terms of the performance it produces. In other words, the program
handwritten in machine code will run faster than compiled code. If a compiler produces a
code which is 20-30% slower than the handwritten code then it is considered to be
acceptable. In addition to this, the compiler itself must run fast (compilation time must be
proportional to program size).
• Maintainable code
• High level of abstraction
• Correctness is a very important issue.
• Correctness : A compiler's most important goal is correctness -
all valid programs must compile correctly. How do we check if a
compiler is correct i.e. whether a compiler for a programming
language generates correct machine code for programs in the
language. The complexity of writing a correct compiler is a
major limitation on the amount of optimization that can be done.
• Can compilers be proven to be correct? Very tedious!
• . However, the correctness has an implication on the
development cost
• Many modern compilers share a common 'two stage' design.
The "front end" translates the source language or the high level
program into an intermediate representation. The second stage
is the "back end", which works with the internal representation
to produce code in the output language which is a low level
code. The higher the abstraction a compiler can support, the
better it is.
The Big picture
• . Translate in steps. Each step handles a reasonably simple, logical, and well defined task
• . Design a series of program representations
• . Intermediate representations should be amenable to program manipulation of various
kinds (type checking, optimization, code generation etc.)
• . Representations become more machine specific and less language specific as the
translation proceeds
The first few steps
• The first few steps of compilation like lexical, syntax and semantic analysis can be
understood by drawing analogies to the human way of comprehending a natural
language. The first step in understanding a natural language will be to recognize
characters, i.e. the upper and lower case alphabets, punctuation marks, alphabets, digits,
white spaces etc. Similarly the compiler has to recognize the characters used in a
programming language. The next step will be to recognize the words which come from a
dictionary. Similarly the programming language have a dictionary as well as rules to
construct words (numbers, identifiers etc).
• . The first step is recognizing/knowing alphabets of a language. For example
• - English text consists of lower and upper case alphabets, digits, punctuations and white spaces
• - Written programs consist of characters from the ASCII characters set (normally 9-13, 32-126)
• . The next step to understand the sentence is recognizing words (lexical analysis)
• - English language words can be found in dictionaries
• - Programming languages have a dictionary (keywords etc.) and rules for constructing words
(identifiers, numbers etc.)
Lexical Analysis
• . Once the sentence structure is understood we try to understand the meaning of the
sentence (semantic analysis)
• . Example: Prateek said Nitin left his assignment at home
• . What does his refer to? Prateek or Nitin ?
• . Even worse case
• Amit said Amit left his assignment at home
• . How many Amits are there? Which one left the assignment?
• Semantic analysis is the process of examining the statements and to make
sure that they make sense. During the semantic analysis, the types, values,
and other required information about statements are recorded, checked, and
transformed appropriately to make sure the program makes sense. Ideally
there should be no ambiguity in the grammar of the language. Each sentence
should have just one meaning.
Semantic Analysis
• . Too hard for compilers. They do not have capabilities similar to human understanding
• . However, compilers do perform analysis to understand the meaning and catch
inconsistencies
• . Programming languages define strict rules to avoid such ambiguities
{ int Amit = 3;
{ int Amit = 4;
cout << Amit;
}
}
• Since it is too hard for a compiler to do semantic analysis, the programming languages
define strict rules to avoid ambiguities and make the analysis easier. In the code written
above, there is a clear demarcation between the two instances of Amit. This has been
done by putting one outside the scope of other so that the compiler knows that these
two Amit are different by the virtue of their different scopes.
More on Semantic Analysis
Till now we have conceptualized the front end of the compiler with its 3 phases, viz.
Lexical Analysis, Syntax Analysis and Semantic Analysis; and the work done in each
of the three phases. Next, we look into the backend in the forthcoming slides.
Front End Phases
Lexical analysis is based on the finite state automata and hence finds the lexicons from the
input on the basis of corresponding regular expressions. If there is some input which it
can't recognize then it generates error. In the above example, the delimiter is a blank space.
See for yourself that the lexical analyzer recognizes identifiers, numbers, brackets etc.
Syntax Analysis
Semantic analysis should ensure that the code is unambiguous. Also it should do the type
checking wherever needed. Ex. int y = "Hi"; should generate an error. Type coercion can be
explained by the following example:- int y = 5.6 + 1; The actual value of y used will be 6 since it is
an integer. The compiler knows that since y is an instance of an integer it cannot have the value
of 6.6 so it down-casts its value to the greatest integer less than 6.6. This is called type coercion.
Code Optimization
• No strong counter part with English, but is similar to editing/précis writing
• . Automatically modify programs so that they
• - Run faster
• - Use less resources (memory, registers, space, fewer fetches etc.)
• . Some common optimizations
• - Common sub-expression elimination
• - Copy propagation
• - Dead code elimination
• - Code motion
• - Strength reduction
• - Constant folding
• . Example: x = 15 * 3 is transformed to x = 45
There is no strong counterpart in English, this is similar to precise writing where one cuts
down the redundant words. It basically cuts down the redundancy. We modify the compiled
code to make it more efficient such that it can - Run faster - Use less resources, such as
memory, register, space, fewer fetches etc.
Example of Optimizations
Code Generation
• . Usually a two step process
• Generate intermediate code from the semantic representation of the program
• Generate machine code from the intermediate code
• . The advantage is that each phase is simple
• . Requires design of intermediate language
• . Most compilers perform translation between successive intermediate
representations
• . Intermediate languages are generally ordered in decreasing level of abstraction
from highest (source) to lowest (machine)
• . However, typically the one after the intermediate code generation is the most
important
The final phase of the compiler is generation of the relocatable target code. First of all,
Intermediate code is generated from the semantic representation of the source program, and
this intermediate code is used to generate machine code.
Intermediate Code Generation
• Abstraction at the source level identifiers, operators, expressions, statements,
conditionals, iteration, functions (user defined, system defined or libraries)
• . Abstraction at the target level memory locations, registers, stack, opcodes,
addressing modes, system libraries, interface to the operating systems
• . Code generation is mapping from source level abstractions to target machine
abstractions
• . Map identifiers to locations (memory/storage allocation)
• . Explicate variable accesses (change identifier reference to relocatable/absolute
address
• . Map source operators to opcodes or a sequence of opcodes
• . Convert conditionals and iterations to a test/jump or compare instructions
• . Layout parameter passing protocols: locations for parameters, return values,
layout of activations frame etc.
• . Interface calls to library, runtime system, operating systems
By the very definition of an intermediate language it must be at a level of abstraction which is in
the middle of the high level source language and the low level target (machine) language. Design
of the intermediate language is important.
Thus it must not only relate to identifiers, expressions, functions & classes but also to opcodes,
registers, etc. Then it must also map one abstraction to the other.
These are some of the things to be taken care of in the intermediate code generation.
Post translation Optimizations
Instruction selection
• - Addressing mode selection
• - Opcode selection
• - Peephole optimization
Intermediate code generation
Code Generation
CMP Cx, 0 CMOVZ Dx,Cx
There is a clear intermediate code optimization - with 2 different sets of codes having 2 different parse trees. The optimized code
does away with the redundancy in the original code and produces the same result.