CD Notes
CD Notes
Course Objectives:
1. Introduce the major concepts of language translation and compiler design and impart the
knowledge of practical skills necessary for constructing a compiler.
2. Topics include phases of compiler, parsing, syntax directd translation, type checking use of
symbol tables, code optimization techniques, intermediate code generation, code generation
and data flow analysis.
Course Outcomes:
UNIT - I
UNIT - II
UNIT - III
UNIT - IV
Run-Time Environments: Stack Allocation of Space, Access to Nonlocal Data on the Stack,
Heap Management, Introduction to Garbage Collection, Introduction to Trace-Based
Collection.
Code Generation: Issues in the Design of a Code Generator, The Target Language, Addresses
in the Target Code, Basic Blocks and Flow Graphs, Optimization of Basic Blocks, A Simple
Code Generator, Peephole Optimization, Register Allocation and Assignment, Dynamic
Programming Code-Generation.
UNIT - V
TEXT BOOK:
1. Compilers: Principles, Techniques and Tools, Second Edition, Alfred V. Aho, Monica S.
Lam, Ravi Sethi, Jeffry D. Ullman.
REFERENCES: 1. Lex & Yacc – John R. Levine, Tony Mason, Doug Brown, O’reilly 2.
Compiler Construction, Louden, Thomson.
LESSION PLAN
Lecture-1
Overview of systems, why we study programming languages?, attributes of a
good language, classification of programming languages.
Ref: Principles of programming languages, Rabi Sethi
Lecture-2
Introduction to Compiler, Cousins of Compiler(Translator, assembler,
interpreter, loader, linker etc), Phases of Compilers.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-3
Operation in each phases of a Compiler, lexical analyzer, syntax analyzer,
semantics analyzer, symbol table manager, error handler, intermediate code generator,
code optimizer, code generator.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-4
Compiler Construction Tools, Parser generators, Scanner generators, syntax
directed translation engines, automatic code generator, data flow engine.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-5
Role of the lexical analyzer, issues in lexical analysis, tokens, patterns,
lexemes.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-6
Lexical errors and error recovery actions, Input buffering.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-7
Specification of tokens, Strings and languages, Finite automata, DFA, NFA.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Automata Theory, KLP Mishra, N. Chandrasekharan Automata
Theory, AV Aho, JD Ullman
Lecture-8
Equivalence of NFA and DFA, Conversion of NFA to DFA.
Ref: Automata Theory, KLP Mishra, N. Chandrasekharan
Automata Theory, AV Aho, JD Ullman
Lecture-9
Minimizing states of DFA, Є-NFA,
Ref: Automata Theory, KLP Mishra, N. Chandrasekharan
Automata Theory, AV Aho, JD Ullman
Lecture-10
Regular Expression, regular grammar, Conversion of regular expression into
NFA
Ref: Automata Theory, KLP Mishra, N. Chandrasekharan
Automata Theory, AV Aho, JD Ullman
Lecture-11
A language for specifying lexical analyzer, Design of lexical analyzer
generator
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-12
The role of Parser, Syntactic errors and recovery actions
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-13
Context free Grammar, Parse Tree, Parse tree Derivation, Left most
Derivation, Right most derivation, ambiguity.
Ref: Automata Theory, KLP Mishra, N. Chandrasekharan
Automata Theory, AV Aho, JD Ullman
Lecture-14
Eliminating ambiguity, predictive parsing, Recursive decent parsing,
predictive parsing using tables.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-15
Top down parsing, bottom up parsing, shift reduce parsing using the
ACTION/GOTO Tables.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-16
Table construction, SLR, LL, LALR Grammar, Practical consideration for
LALR grammar.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Automata Theory, KLP Mishra, N. Chandrasekharan
Lecture-17
Syntax directed translation, Syntax directed definition, bottom up evaluation
of S-attributed definition.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-18
L-attribute definition, top-down translation, bottom up evaluation of inherited
attributes.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-19
Recursive evaluators, space for attribute values at compile time, assigning
space at compiler construction time, analysis of syntax directed definitions.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-20
Semantic actions, semantic analysis, symbol tables, types and type checking.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-21
Run time Environment, Activation Records, run time storage organization.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-22
Symbol Tables, Language facilities for dynamic storage allocation, Dynamic
storage allocation techniques
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-23
Intermediate code Generation, intermediate languages, Declarations.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-24
Assignment statements, Boolean expressions, Case statements, Back patching,
Procedure Calls.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-25
Code Generation, Issues in the design of code generation, The target machine.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-26
Run time storage management, Basic blocks and flow graphs.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-27
A simple code generator, Register and address descriptors, A code generation
algorithm.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-28
Register allocation and assignments, global register allocation, usage counts,
register assignment for outer loops, Register allocation by graph coloring.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-29
The Dag representation of basic blocks, Dag Construction, Application of
Dag.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lectur-30
Peephole optimization, Redundant-instruction elimination, Flow of control
optimizations, algebraic simplifications, Use of machine idioms.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-31
Generating code from dags, Rearranging the order, A Heuristic ordering for
Dags.(Cont….)
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-32
Optimal ordering for Trees, The labeling algorithm, Code generation from a
Labeled tree, Multiregister Operations, Algebraic Properties.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-33
Dynamic programming code generation algorithm, A class of register
Machines, The principle of dynamic programming, contiguous evaluation.(Cont….)
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-34
The dynamic programming algorithm, Code-Generator Generators.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-35
Introduction to code optimization, An organization for an optimizing
Compiler.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-36
The principal sources of optimization, Function-Preserving Transformations,
Common sub expressions, Copy propagations. (Cont…)
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-37
Dead –Code Elimination, Loop Optimizations, Code m otion, Induction
Variables and Reduction in Strength.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-38
Optimization of basic Blocks, Loops in flow graph, Introduction to Global
data flow analysis.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-39
Code improving transformations, Dealing with Aliases, Data flow analysis of
structured flow graphs, Efficient data flow algorithm.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Lecture-40
A Tool for data flow analysis, Estimation of types, symbolic debugging of optimized
code.
Ref: Principle of Compiler Design, A.V.Aho, Rabi Sethi, J.D.Ullman
Module -I
Introduction to Compiling:
1.1 INTRODUCTION OF LANGUAGE PROCESSING SYSTEM
A preprocessor produce input to compilers. They may perform the following functions.
Macro processing: A preprocessor may allow a user to define macros that are short hands for
longer constructs.
File inclusion: A preprocessor may include header files into the program text.
Rational preprocessor: these preprocessors augment older languages with more modern flow-of-
control and data structuring facilities.
Language Extensions: These preprocessor attempts to add capabilities to the language by certain
amounts to build-in macro
COMPILER
Compiler is a translator program that translates a program written in (HLL) the source program and
translate it into an equivalent program in (MLL) the target program. As an important part of a
compiler is error showing to the programmer.
ASSEMBLER
Programmers found it difficult to write or read programs in machine language. They begin to use a
mnemonic (symbols) for each machine instruction, which they would subsequently translate into
machine language. Such a mnemonic machine language is now called an assembly language.
Programs known as assembler were written to automate the translation of assembly language in to
machine language. The input to an assembler program is called source program, the output is a
machine language translation (object program).
INTERPRETER
An interpreter is a program that appears to execute a source program as if it were machine language.
Languages such as BASIC, SNOBOL, LISP can be translated using interpreters. JAVA also uses
interpreter. The process of interpretation can be carried out in following phases.
Lexical analysis
Synatx analysis
Semantic analysis
Direct Execution
Advantages:
Modification of user program can be easily made and implemented as execution proceeds.
Type of object that denotes a various may change dynamically.
Debugging a program and finding errors is simplified task for a program used for interpretation.
The interpreter for the language makes it machine independent.
Disadvantages:
The execution of the program is slower.
Memory consumption is more.
Once the assembler procedures an object program, that program must be placed into memory and
executed. The assembler could place the object program directly in memory and transfer control to it,
thereby causing the machine language program to be execute. This would waste core by leaving the
assembler in memory while the user’s program was being executed. Also the programmer would
have to retranslate his program with each execution, thus wasting translation time. To over come this
problems of wasted translation time and memory. System programmers developed another
component called loader
“A loader is a program that places programs into memory and prepares them for execution.” It would
be more efficient if subroutines could be translated into object form the loader could”relocate”
directly behind the user’s program. The task of adjusting programs o they may be placed in arbitrary
core locations is called relocation. Relocation loaders perform four functions.
1.2 TRANSLATOR
A translator is a program that takes as input a program written in one language and produces as
output a program in another language. Beside program translation, the translator performs another
very important role, the error-detection. Any violation of d HLL specification would be detected and
reported to the programmers. Important role of translator are:
1 Translating the HLL program input into an equivalent ml program.
2 Providing diagnostic messages wherever the programmer violates specification of the HLL.
Syntax Analysis:-
The second stage of translation is called Syntax analysis or parsing. In this phase expressions,
statements, declarations etc… are identified by using the results of lexical analysis. Syntax analysis is
aided by using techniques based on formal grammar of the programming language.
Code Optimization :-
This is optional phase described to improve the intermediate code so that the output runs faster and
takes less space.
Code Generation:-
The last phase of translation is code generation. A number of optimizations to reduce the length of
machine language program are carried out during this phase. The output of the code generator is
the machine language program of the specified computer.
Table Management (or) Book-keeping:- This is the portion to keep the names used by the about
program and records essential information each. The data structure used to record this
information called a ‘Symbol Table’.
Error Handlers:-
It is invoked when a flaw error in the source program is detected. The output of LA is a stream of
tokens, which is passed to the next phase, the syntax analyzer or parser. The SA groups the tokens
together into syntactic structure called as expression. Expression may further be combined to form
statements. The syntactic structure can be regarded as a tree whose leaves are the token called as
parse trees.
The parser has two functions. It checks if the tokens from lexical analyzer, occur in pattern that are
permitted by the specification for the source language. It also imposes on tokens a tree-like structure
that is used by the sub-sequent phases of the compiler.
Example, if a program contains the expression A+/B after lexical analysis this expression might
appear to the syntax analyzer as the token sequence id+/id. On seeing the /, the syntax analyzer
should detect an error situation, because the presence of these two adjacent binary operators violates
the formulations rule of an expression. Syntax analysis is to make explicit the hierarchical structure
of the incoming token stream by identifying which parts of the token stream should be grouped.
Code Optimization
This is optional phase described to improve the intermediate code so that the output runs faster and
takes less space. Its output is another intermediate code program that does the some job as the
original, but in a way that saves time and / or spaces.
Local Optimization:-
There are local transformations that can be applied to a program to make an improvement.
For example,
If A > B goto L2
Goto L3
L2 :
T1:=B+C
A:=T1+D
E:=T1+F
Take this advantage of the common sub-expressions B + C.
b. Loop Optimization:-
Another important source of optimization concerns about increasing the speed of loops. A
typical loop improvement is to move a computation that produces the same result each
time around the loop to a point, in the program just before the loop is entered.
Code generator :-
Code Generator produces the object code by deciding on the memory locations for data, selecting
code to access each datum and selecting the registers in which each computation is to be done. Many
computers have only a few high speed registers in which computations can be performed quickly. A
good code generator would attempt to utilize registers as efficiently as possible.
Error Handing :-
One of the most important functions of a compiler is the detection and reporting of errors in the
source program. The error message should allow the programmer to determine exactly where the
errors have occurred. Errors may occur in all or the phases of a compiler.
Whenever a phase of the compiler discovers an error, it must report the error to the error handler,
which issues an appropriate diagnostic msg. Both of the table-management and error-Handling
routines interact with all phases of the compiler.
Example:
← Language Definition
← Appearance of programming language :
Vocabulary : Regular expression
Syntax : Backus-Naur Form(BNF) or Context Free Form(CFG)
• Semantics : Informal language or some examples
← Grammar G=(N,T,P,S)
• N : a set of nonterminal symbols
o T : a set of terminal symbols, tokens
o P : a set of production rules
o S : a start symbol, S∈N
o
• Grammar G for a language L={9-5+2, 3-1, ...}
o G=(N,T,P,S)
N={list,digit}
T={0,1,2,3,4,5,6,7,8,9,-,+}
P: list -> list + digit
list -> list - digit
list -> digit
digit -> 0|1|2|3|4|5|6|7|8|9
S=list
⇒
⇒ ⇒ ⇒ ⇒
⇒ ⇒ ⇒
• there is ⇒ ⇒ ⇒ ⇒
∉
note L(G)={anbn| n 0} where
• Parse Tree
A derivation can be conveniently represented by a derivation tree( parse tree).
← The root is labeled by the start symbol.
o Each leaf is labeled by a token or ε.
o Each interior none is labeled by a nonterminal symbol.
o When a production A→x1… xn is derived, nodes labeled by x1… xn are made as
children
nodes of node labeled by A.
• root : the start symbol
• internal nodes : nonterminal
• leaf nodes : terminal
← Example G:
list -> list + digit | list - digit |
digit digit -> 0|1|2|3|4|5|6|7|8|9
← left most derivation for 9-5+2,
list ⇒ list+digit ⇒list-digit+digit ⇒ digit-digit+digit ⇒ 9-
digit+digit ⇒ 9-5+digit ⇒ 9-5+2
← right most derivation for 9-5+2,
list ⇒ list+digit ⇒list+2 ⇒list-digit+2 ⇒ list-5+2
• digit-5+2 ⇒ 9-5+2
Fig 2.2. Parse tree for 9-5+2 according to the grammar in Example
Ambiguity
← A grammar is said to be ambiguous if the grammar has more than one parse tree for
a given string of tokens.
← Example 2.5. Suppose a grammar G that can not distinguish between lists and digits as in
Example 2.1.
G : string → string + string | string - string |0|1|2|3|4|5|6|7|8|9
Fig 2.3. Two Parse tree for 9-5+2
← 1-5+2 has 2 parse trees => Grammar G is ambiguous.
Associativity of operator
A operator is said to be left associative if an operand with operators on both sides of it
is taken by the operator to its left.
eg) 9+5+2≡(9+5)+2, a=b=c≡a=(b=c)
← Left Associative Grammar :
list → list + digit | list - digit
digit →0|1|…|9
← Right Associative Grammar :
right → letter = right | letter
letter → a|b|…|z
• Syntax of statements
← stmt → id = expr ;
| if ( expr ) stmt ;
| if ( expr ) stmt else stmt ;
| while ( expr ) stmt ;
expr → expr + term | expr - term | term
term → term * factor | term / factor | factor
factor → digit | ( expr )
digit → 0 | 1 | … | 9
2.3 SYNTAX-DIRECTED TRANSLATION(SDT)
A formalism for specifying translations for programming language
constructs. ( attributes of a construct: type, string, location, etc)
← Syntax directed definition(SDD) for the translation of constructs
← Syntax directed translation scheme(SDTS) for specifying translation
Postfix notation for an expression E
← If E is a variable or constant, then the postfix nation for E is E itself ( E.t≡E ).
← if E is an expression of the form E1 op E2 where op is a binary operator
E1' is the postfix of E1, o
E2' is the postfix of E2
o then E1' E2' op is the postfix for E1 op E2
← if E is (E1), and E1' is a postfix
then E1' is the postfix for E
⇒
9-(5+2)⇒952+-
Syntax-Directed Definition(SDD) for translation
← SDD is a set of semantic rules predefined for each productions respectively
for translation.
← A translation is an input-output mapping procedure for translation of an input
X, o construct a parse tree for X.
o synthesize attributes over the parse tree.
← Suppose a node n in parse tree is labeled by X and X.a denotes the value
of attribute a of X at that node.
← compute X's attributes X.a using the semantic rules associated with X.
Fig 2.7. Example of a depth-first traversal of a tree. Fig 2.8. An extra leaf is constructed for a semantic action.
Example 2.8.
← SDD vs. SDTS for infix to postfix translation.
Fig 2.10. Top-down parsing while scanning the input from left to right.
Fig 2.11. Steps in the top-down construction of a parse tree.
← The selection of production for a nonterminal may involve trial-and-error.
=> backtracking
• G : { S->aSb | c | ab }
According to topdown parsing procedure, acb , aabb L(G)?
• cb aSb/acb aSb/acb aaSbb/acb X
S/a
(S→aSb)move ∈
(S→aSb) backtracking
acb/acb acb/acb
⇒ ⇒ ⇒
aSb/acb acb/acb ⇒
Is is ∈
• S/aabb aSb/aabb aSb/aabb aaSbb/aabb aaSbb/aabb aaaSbbb/aabb X
(S→aSb) move (S→aSb) move (S→aSb) backtracking
X
⇒
⇒ ⇒
⇒ ⇒ ⇒
aaSbb/aabb aacbb/aabb
(S→c) backtracking
⇒
aaSbb/aabb
⇒
aaabbb/aabb
⇒
X
(S→ab) backtracking
⇒ ⇒
aaSbb/aabb X ⇒
backtracking
aSb/aabb acb/aabb ⇒
⇒
(S→c) bactracking
∈ ⇒ ⇒
aSb/aabb aabb/aabb aabb/aabb aabb/aabb aaba/aabb
(S→ab) move move move
so, aabb L(G) ⇒ ⇒ ⇒ ⇒ ⇒
but process is too difficult. It needs 18 steps including 5 backtrackings.
• procedure of top-down parsing
let a pointed grammar∈ symbol and pointed input symbol be g, a respectively.
o if( g N ) select and expand a production whose left part equals to g next to
current production.
else if( g = a ) then make g and a be a symbol next to current symbol.
else if( g ≠a ) back tracking
let the pointed input symbol a be the symbol that moves back to steps
same with the number of current symbols of underlying production
eliminate the right side symbols of current production and let the pointed
symbol g be the left side symbol of current production.
• if( g N )
← select next production P whose left symbol equals to g and a set of first
terminal symbols of derivation from the right symbols of the production
P includes a input symbol a.
← expand derivation with that production P.
• else if( g = a ) then make g and a be a symbol next to current symbol.
• else if( g ≠a ) error
← G : { S→aSb | c | ab } => G1 : { S->aS' | c S'->Sb | ab∈} According to predictive⇒ parsing procedure,
acb , aabb L(G)?
• S/acb confused in { S→aSb, S→ab }
← so, a predictive parser requires some restriction in grammar, that is, there
should be only one production whose left part of productions are A and each
first terminal symbol of those productions have unique terminal symbol.
← Requirements for a grammar to be suitable for RDP: For each nonterminal either
• A → Bα, or
2)1) for 1 i, j n and i≠ j, ai ≠ aj
• A → a1α≦1 | a2≦α2 | … | anαn
A ε may also occur if none of ai can follow A in a derivation and if we have A→ε
← If the grammar is suitable, we can parse efficiently without backtrack.
General top-down parser with backtracking
↓
Recursive Descent Parser without backtracking
↓
Picture Parsing ( a kind of predictive parsing ) without backtracking
Left Factoring
← If a grammar contains two productions of
form S→ aα and S → aβ
it is not suitable for top down parsing without backtracking. Troubles of this form
can sometimes be removed from the grammar by a technique called the left factoring.
← In the left factoring, we replace { S→ aα, S→ aβ }
by { S → aS', S'→ α, S'→ β } cf. S→ a(α|β)
(Hopefully α and β start with different symbols)
← left factoring for G { S→aSb | c | ab }
S→aS' | c cf. S(=aSb | ab | c = a ( Sb | b) | c ) → a S' | c
S'→Sb | b
← A concrete example:
<stmt> → IF <boolean> THEN <stmt> |
IF <boolean> THEN <stmt> ELSE
<stmt> is transformed into
<stmt>→ IF <boolean> THEN <stmt> S'
S' → ELSE <stmt> | ε
← Example,
← for G1 : { S→aSb | c | ab }
According to predictive parsing procedure, acb , aabb∈L(G)?
← S/aabb⇒ unable to choose { S→aSb, S→ab ?}
← According for the feft factored gtrammar G1, acb , aabb∈L(G)?
G1 : { S→aS'|c S'→Sb|b} <= {S=a(Sb|b) | c }
← S/acb⇒aS'/acb⇒aS'/acb ⇒ aSb/acb ⇒ acb/acb ⇒ acb/acb⇒ acb/acb
(S→aS') move (S'→Sb⇒aS'b) (S'→c) move move
so, acb∈ L(G)
It needs only 6 steps whithout any backtracking.
cf. General top-down parsing needs 7 steps and I backtracking.
← S/aabb⇒aS'/aabb⇒aS'/aabb⇒aSb/aabb⇒aaS'b/aabb⇒aaS'b/aabb⇒aabb/
aabb⇒ ⇒
(S→aS') move (S'→Sb⇒aS'b) (S'→aS') move (S'→b) move move
so, aabb∈L(G)
but, process is finished in 8 steps without any backtracking.
cf. General top-down parsing needs 18 steps including 5 backtrackings.
Left Recursion
• A grammar is left recursive iff it contains a nonterminal A, such that
+ Aα, where is any string.
Grammar {S→ Sα | c} is left recursive because of S Sα
A⇒o
Aα because of S
Sbα
o Grammar {S→ Aα, A→ Sb | c} is also left recursive
⇒
⇒ ⇒
← If a grammar is left recursive, you cannot build a predictive top down parser for it.
•
•
If a parser is trying to match S & S→Sα, it has no idea how many times S must be
applied
•
Given a left recursive grammar, it is always possible to find another grammar that
generates the same language and is not left recursive.
εExample:
Result: 9 5 – 2 +
Example of translator design and execution
← A translation scheme and with left-recursion.
Initial specification for infix-to-postfix with left recursion eliminated
translator
expr → expr + term {printf{"+")} expr → term rest
expr → expr - term {printf{"-")} rest → + term {printf{"+")} rest
expr → term rest → - term {printf{"-")} rest
term → 0 {printf{"0")} rest → ε
term → 1 {printf{"1")} term → 0 {printf{"0")}
… term → 1 {printf{"1")}
term → 9 {printf{"0")} …
term → 9 {printf {"0")}
Fig 2.14. Function for the nonterminals expr, rest, and term.
Optimizer and Translator
0
1 id count
2 id increment
3
• Keywords are reserved, i.e., they cannot be used as identifiers.
Then a character string forms an identifier only if it is no a keyword.
← punctuation symbols
← operators : + - * / := < > …
Fig 2.15. Inserting a lexical analyzer between the input and the parser
A Lexical Analyzer
← c=getchcar(); ungetc(c,stdin);
← token representation
#define NUM 256
← Function lexan()
eg) input string 76 + a
input , output(returned value)
76 NUM, tokenval=76 (integer)
+ + A id ,
tokeval="a"
← example
← preset
insert("div",div);
insert("mod",mod);
← while parsing
lookup("count")=>0
insert("count",id); lookup("i") =>0
insert("i",id); lookup("i") =>4, id
llokup("div")=>1,div
Data memory
← An example of stack machine operation.
← for a input (5+a)*b, intermediate codes : push 5 rvalue 2 ....
L-value and r-value
• l-values a : address of location a
• r-values a : if a is location, then content of location a
if a is constant, then value a
• eg) a :=5 + b;
Translation of Expressions
• Infix expression(IE) → SDD/SDTS → Abstact macine codes(ASC) of postfix expression for
stack machine evaluation.
eg)
o IE: a + b, (⇒PE: a b + ) ⇒ IC: rvalue a rvalue b
+
o day := (1461 * y) div 4 + (153 * m + 2) div 5 + d
day 1462 y * 4 div 153 m * 2 + 5 div + d + :=)
⇒
(⇒
1) lvalue day 6) div 11) push 5 16) :=
2) push 1461 7) push 153 12) div
3) rvalue y 8) rvalue m 13) +
4) * 9) push 2 14) rvalue d
5) push 4 10) + 15) +
← A translation scheme for assignment-statement into abstract astack machine code e can
be expressed formally In the form as follows:
stmt → id := expr
⇒{ stmt.t := 'lvalue' || id.lexeme || expr.t || ':=' } eg) day :=a+b lvalue day rvalue a rvalue b + :=
Control Flow
← 3 types of jump instructions :
← Absolute target location
← Relative target location( distance :Current ↔Target)
• Symbolic target location(i.e. the machine supports labels)
← Control-flow instructions:
• label a: the jump's target a
← goto a: the next instruction is taken from statement labeled
a o gofalse a: pop the top & if it is 0 then jump to a
o gotrue a: pop the top & if it is nonzero then jump to a
← halt : stop execution
Translation of Statements
← Translation scheme for translation if-statement into abstract machine
code. stmt → if expr then stmt1
{ out := newlabel1)
stmt.t := expr.t || 'gofalse' out || stmt1.t || 'label' out }
Emitting a Translation
← Semantic Action(Tranaslation
Scheme): 1. stmt → if
expr { out := newlabel; emit('gofalse', out) }
then
stmt1 { emit('label', out) }
← stmt → id { emit('lvalue',
id.lexeme) } :=
expr { emit(':=') }
← stmt → i
expr { out := newlabel; emit('gofalse', out) }
then
stmt1 { emit('label', out) ; out1 := newlabel; emit('goto', out`1); }
else
stmt2 { emit('label', out1) ; }
if(expr==false) goto out
stmt1 goto out1
out : stmt2
out1:
Implementation
← procedure stmt()
← var test,out:integer;
← begin
← if lookahead = id then begin
• emit('lvalue',tokenval); match(id);
match(':='); expr(); emit(':=');
← end
← else if lookahead = 'if' then begin
• match('if');
• expr();
• out := newlabel();
• emit('gofalse', out);
• match('then');
• stmt;
• emit('label', out)
← end
← else error();
← end
⇒
eg) id+(id-
Description of the Translator
← Syntax directed translation scheme
(SDTS) to translate the infix expressions
into the postfix expressions,
Fig 2.19. Specification for infix-to-postfix translation
SDTS
← left recursion elimination
New SDTS
Fig 2.20. Specification for infix to postfix translator & syntax directed
translation scheme after eliminating left-recursion.
The Emitter Module emitter.c
emit (t,tval)
Upon receiving a ‘get next token’ command form the parser, the lexical analyzer reads
the input character until it can identify the next token. The LA return to the parser representation
for the token it has found. The representation will be an integer code, if the token is a simple
construct such as parenthesis, comma or colon.
LA may also perform certain secondary tasks as the user interface. One such task is
striping out from the source program the commands and white spaces in the form of blank, tab
and new line characters. Another is correlating error message from the compiler with the source
program.
3.3 TOKEN, LEXEME, PATTERN:
Token: Token is a sequence of characters that can be treated as a single logical
entity. Typical tokens are,
1) Identifiers 2) keywords 3) operators 4) special symbols 5)constants
Pattern: A set of strings in the input for which the same token is produced as output. This
set of strings is described by a rule called a pattern associated with the token.
Lexeme: A lexeme is a sequence of characters in the source program that is matched by
the pattern for a token.
Fig. 3.2: Example of Token, Lexeme and
Pattern 3.4. LEXICAL ERRORS:
Lexical errors are the errors thrown by your lexer when unable to continue. Which means that
there's no way to recognise a lexeme as a valid token for you lexer. Syntax errors, on the other
side, will be thrown by your scanner when a given set of already recognised valid tokens don't
match any of the right sides of your grammar rules. simple panic-mode error handling system
requires that we return to a high-level parsing function when a parsing or lexical error is
detected.
Recognition of tokens:
We learn how to express pattern using regular expressions. Now, we must study how to take the
patterns for all the needed tokens and build a piece of code that examins the input string and
finds a prefix that is a lexeme matching one of the patterns.
Stmt →if expr then stmt
| If expr then else stmt
|є
Expr →term relop term
| term
Term →id
|number
For relop ,we use the comparison operations of languages like Pascal or SQL where = is “equals”
and < > is “not equals” because it presents an interesting structure of lexemes.
The terminal of grammar, which are if, then , else, relop ,id and numbers are the names of tokens
as far as the lexical analyzer is concerned, the patterns for the tokens are described using regular
definitions.
digit → [0,9]
digits →digit+
number →digit(.digit)?(e.[+-]?digits)?
letter → [A-Z,a-z]
id →letter(letter/digit)*
if → if
then →then
else →else
relop →< | > |<= | >= | = = | < >
In addition, we assign the lexical analyzer the job stripping out white space, by recognizing the
“token” we defined by:
WS → (blank/tab/newline)+
Here, blank, tab and newline are abstract symbols that we use to express the ASCII characters of
the same names. Token ws is different from the other tokens in that ,when we recognize it, we do
not return it to parser ,but rather restart the lexical analysis from the character that follows the
white space . It is the following token that gets returned to the parser.
The above TD for an identifier, defined to be a letter followed by any no of letters or digits.A
sequence of transition diagram can be converted into program to look for the tokens specified
by the diagrams. Each state gets a segment of code.
3.8. FINITE AUTOMATON
A recognizer for a language is a program that takes a string x, and answers “yes” if x is a
sentence of that language, and “no” otherwise.
We call the recognizer of the tokens as a finite automaton.
A finite automaton can be: deterministic (DFA) or non-deterministic (NFA)
This means that we may use a deterministic or non-deterministic automaton as a lexical
analyzer.
Both deterministic and non-deterministic finite automaton recognize regular sets.
Which one?
– deterministic – faster recognizer, but it may take more space
– non-deterministic – slower, but it may take less space
– Deterministic automatons are widely used lexical analyzers.
First, we define regular expressions for tokens; Then we convert them into a DFA to get a
lexical analyzer for our tokens.
3.9. Non-Deterministic Finite Automaton (NFA)
A non-deterministic finite automaton (NFA) is a mathematical model that consists of: o
S - a set of states
o Σ - a set of input symbols (alphabet)
o move - a transition function move to map state-symbol pairs to sets of states.
o s0 - a start (initial) state
o F- a set of accepting states (final states)
ε- transitions are allowed in NFAs. In other words, we can move from one state to
another one without consuming any symbol.
A NFA accepts a string x, if and only if there is a path from the starting state to one of
accepting states such that edge labels along this path spell out x.
Example:
Example:
3.11. Converting RE to NFA
This is one way to convert a regular expression into a NFA.
There can be other ways (much efficient) for the conversion.
Thomson’s Construction is simple and systematic method.
It guarantees that the resulting NFA will have exactly one final state, and one start state.
Construction starts from simplest parts (alphabet symbols).
To create a NFA for a complex regular expression, NFAs of its sub-expressions are
combined to create its NFA.
To recognize an empty string ε:
N(r1) and N(r2) are NFAs for regular expressions r1 and r2.
For regular expression r1 r2
Example:
For a RE (a|b) * a, the NFA construction is shown below.
p1 {action 1}
p2 {action 2}
p3 {action 3}
……
……
Where, each p is a regular expression and each action is a program fragment describing
what action the lexical analyzer should take when a pattern p matches a lexeme. In Lex
the actions are written in C.
The third section holds whatever auxiliary procedures are needed by the
actions.Alternatively these procedures can be compiled separately and loaded with the
lexical analyzer.
Note: You can refer to a sample lex program given in page no. 109 of chapter 3 of the book:
Compilers: Principles, Techniques, and Tools by Aho, Sethi & Ullman for more clarity.
The LA scans the characters of the source pgm one at a time to discover tokens. Because of large
amount of time can be consumed scanning characters, specialized buffering techniques have been
developed to reduce the amount of overhead required to process an input character. Buffering
techniques:
Buffer pairs
Sentinels
The lexical analyzer scans the characters of the source program one a t a time to discover tokens.
Often, however, many characters beyond the next token many have to be examined before the
next token itself can be determined. For this and other reasons, it is desirable for thelexical
analyzer to read its input from an input buffer. Figure shows a buffer divided into two haves of,
say 100 characters each. One pointer marks the beginning of the token being discovered. A look
ahead pointer scans ahead of the beginning point, until the token is discovered .we view the
position of each pointer as being between the character last read and thecharacter next to be read.
In practice each buffering scheme adopts one convention either apointer is at the symbol last
read or the symbol it is ready to read.
Token beginnings look ahead pointerThe distance which the lookahead pointer may have to
travel past the actual token may belarge. For example, in a PL/I program we may see:
DECALRE (ARG1, ARG2… ARG n) Without knowing whether DECLARE is a keyword or an
array name until we see the character that follows the right parenthesis. In either case, the token
itself ends at the second E. If the look ahead pointer travels beyond the buffer half in which it
began, the other half must be loaded with the next characters from the source file. Since the
buffer shown in above figure is of limited size there is an implied constraint on how much look
ahead can be used before the next token is discovered. In the above example, ifthe look ahead
traveled to the left half and all the way through the left half to the middle, we could not reload
the right half, because we would lose characters that had not yet been groupedinto tokens. While
we can make the buffer larger if we chose or use another buffering scheme,we cannot ignore the
fact that overhead is limited.
SYNTAX ANALYSIS
E → id
Leftmost
derivation : E→E+E
E * E+E →id* E+E→id*id+E→id*id+id
The string is derive from the grammar w= id*id+id, which is consists of all terminal
symbols
Rightmost derivation
E→E+E
E+E * E→E+ E*id→E+id*id→id+id*id Given
grammar G : E → E+E | E*E | ( E ) | - E | id
Sentence to be derived : – (id+id)
LEFTMOST DERIVATION RIGHTMOST DERIVATION
E→-E E→-E
E→-(E) E→-(E)
E→-(E+E) E→-(E+E)
E → - ( id+E ) E → - ( E+id )
E → - ( id+id ) E → - ( id+id )
String that appear in leftmost derivation are called left sentinel forms.
String that appear in rightmost derivation are called right sentinel forms.
Sentinels:
Given a grammar G with start symbol S, if S → α , where α may contain non-
terminals or terminals, then α is called the sentinel form of G.
Yield or frontier of tree:
Each interior node of a parse tree is a non-terminal. The children of node can be
a terminal or non-terminal of the sentinel forms that are read from left to right.
The sentinel form in the parse tree is called yield or frontier of the tree.
4.2.2 PARSE TREE
Inner nodes of a parse tree are non-terminal symbols.
The leaves of a parse tree are terminal symbols.
A parse tree can be seen as a graphical representation of a derivation.
Ambiguity:
A grammar that produces more than one parse for some sentence is said to be
ambiguous grammar.
Example : Given grammar G : E → E+E | E*E | ( E ) | - E | id
The sentence id+id*id has the following two distinct leftmost derivations:
E→E+E E→E*E
E → id + E E→E+E*E
E → id + E * E E → id + E * E
E → id + id * E E → id + id * E
E → id + id * id E → id + id * id
Example:
To disambiguate the grammar E → E+E | E*E | E^E | id | (E), we can use
precedence of operators as follows:
(right to
left) /,* (left to
right) -,+ (left to
right)
We get the following unambiguous grammar:
E→E+T|T
T→T*F|F
F→G^F|G
G → id | (E)
Consider this example, G: stmt → if expr then stmt | if expr then stmt else stmt
| other This grammar is ambiguous since the string if E1 then if E2 then S1
else S2 has the following
Two parse trees for leftmost derivation :
second symbol of w ‘a’ and consider the next leaf ‘A’. Expand A using the first alternative.
Step3:
The second symbol ‘a’ of w also matches with second leaf of tree. So advance the
input pointer to third symbol of w ‘d’. But the third leaf of tree is b which does not
match with the input symbol d.
Hence discard the chosen production and reset the pointer to second position. This is called
backtracking.
Step4:
Now try the second alternative for A.
The table-driven predictive parser has an input buffer, stack, a parsing table and an
output stream.
Input buffer:
It consists of strings to be parsed, followed by $ to indicate the end of the input string.
Stack:
It contains a sequence of grammar symbols preceded by $ to indicate the bottom of the stack.
If X = a ≠ $, the parser pops X off the stack and advances the input pointer
to the next input symbol.
If X is a non-terminal , the program consults entry M[X, a] of the parsing table M.
LL(1) grammar:
The parsing table entries are single entries. So each location has not more than one entry.
This type of grammar is called LL(1) grammar.
Consider this following grammar:
S → iEtS | iEtSeS | a
E→b
After eliminating left factoring, we have
S → iEtSS’ | a
S’→ eS | ε
E→b
To construct a parsing table, we need FIRST() and FOLLOW() for all the non-
terminals. FIRST(S) = { i, a }
FIRST(S’) = {e, ε }
FIRST(E) = { b}
FOLLOW(S) = { $ ,e }
FOLLOW(S’) = { $ ,e }
FOLLOW(E) = {t}
Since there are more than one production, the grammar is not LL(1) grammar.
Actions performed in predictive parsing:
Shift
Reduce
Accept
Error
SHIFT-REDUCE PARSING
Shift-reduce parsing is a type of bottom -up parsing that attempts to construct a
parse tree for an input string beginning at the leaves (the bottom) and working up
towards the root (the top).
Example:
Consider the grammar:
S → aABe
A → Abc | b
B→d
The sentence to be recognized is abbcde.
�������������������� ��������������������
������������ � �� � ������
����������� � ���� ������
������������� � �� ��������
�
��������������������������������������������������������������
��������
������������������ ����������������������������������������������������������� ���������
���������� �� ��� ������������� �� ���� ����� ����� ��� ���� ����������� ����������� ���� ����� ������ ���
����������������������������������
��������
���������������������
� ���
� ���
� ���
� ��
��������������������������������
��������������������������� �
�����
�����
�������
���������
�����������
��������������������������������������������������������� ��������
���������������
����������������������������������������������������������������������
�������������������������������������������
���������������������������������������������
�
� ����������� � �����
�������������
����� �������� � �
�� �������� � �����
� �� ������� � �����
�������������
� ����� ���� � �
�������������
� ������� � �
������������
� ����� � ����
������������
� ��� � ���
��� � ������
�������������������������������
����� � �����������������������������������������������������������
������ � ������������������������������������������������������������������
������ � ������������������������������������������������������
����� � ���� ������� ���������� ����� �� ������� ������ ���� ��������� ���� ������ ��� ������ ��������
��������
����������������������������������
�����������������������������������������������������������
������������������������������������������������������������������������������
�������������������������������������������������������������������������������������
����������������������
��������
���������������������
�� ��
�� �����������������������
������������
��������
�����������
���
�������������
���������������������������
�������������������������������������������������
���������������������������
����������������������������������������������������������������������������������
��������
������������������������������������������������������������������� �����������������
�����
��������
���������������������
��������������
����������
������������������������������
����������������������������������������������������
�� � ���������
� � ��������
� � ������������
���������������������� ���������
��������
�
� � � � ������������������������
� � � ������������������������������
�
� � � �������������������������
����������������������������
�� ����������� �� ��������������������������������������� ���� ����
� �
�� ������ �� � ��
�� ��������������� ��� ��� ������������������������ ����
����
� �
�� � �� ������� � �� �� ������������� ���������������� �� �
� �
�� ������� � �� �� �����������������
�� ������������������������������������
��
� �
�� ������ ��
� �
�� ������ �
� �
� ������ ��
� �
� ������� �
���������
� � � � � � � �
������� � � ���� � � � ���� � ��������� � ������� � ��������� � ������� � ������� �
�
��������
��������������������������������������������
��
������������������������������ �����������������
���������������������������������������������������������������
����� � ���������������������������������������������
������������� ������ ����������������������������������
������������������������������������
�
� � � � � �� � � �
� �� �� �� �� �� �� �� �� ��
� �� �� �� �� �� �� �� �� ��
� �� �� �� �� �� �� �� �� ��
� � �
� �� �� �� �� � � � �� ��
�
� �� �� �� �� ��
��
� �� ��
� � � � � �
�� � � � � �
� � � �
� � �
� � �� � �� �� �� � �
� �� �� �� �� �� �� ��
� � �
� � � �
� � �
��
��
�
��������������������������������������
� ��������������� � ������������������������������������
������ � �� � ��������������� � �������� ���������� �����������������������������������������������
�����������������������������������������������
������ � ������������� ������������������������������������������������� � �� ��������������������
��������������������� �
��������������
��� � �������������������������������������������
������
�������������
�
����� ������������� ������������������� �� �
������������������������������������
���� ��������
���
����������������������������������������������������
��������� ����������� �������� ����� �� ������ ���� ����������� �������� ������ ���� ���
�������������������������
��������������������������������������������������������������
����� �����
� ��
�����������������������������������������
��������
�������������������� � � ��� � ��� � ��� � ��� � ��� � ��� ��������������������� ��������
����
�����������������������������
������������������������������������������
������������������������
�������������������������������������������������������������������������������� ���������
��������������������������������������������������������������������������������������
��������������������������������������������
�
����������������������������������������������� ��� �����������������������������������
��������������������������������������������������������������������� �������
����������
���������������������������������������������������������������������������� ��������������
���� ��� ������� ������ ��������� ���� ��� ��� ���� �������������� ��������� ��� ���� ������� ��� ��� ���
������������ � ��������� ����������� ��� �������� ���� ���� ��� ���� ��� ������� ��� ������
��������
�����������������������
�������������������
�������������������������
��� ����������� ���������� ���� ������������ ��������� ����������� ���� ������ ���� ���� ��
��������
����������������������������������������������������������������
������������������
������������������
���������� � ������������������������������������
�����������������������
��� ���� ����� ��� ����� ��� ���������� �� ��� ������� ��� ����� ��� � ������������ ��������
������������������������������������� �������������������������������
��������������
���������������������������
�� ���� ������ ��
��������������������� ���������������
���� �������������
������������������������������
�������� �������������
������������������������������������������������������������
�������������������������
���������������������������������� ��������������
�����
�� � �� � �� �
����������
�� ������� ������
��
����
����
� �������������������
��
�����
��� ��������� ��� � ��� ������� ��� �������� �� ������� �� ������� ���������
� �������������������������������������������������
� ������������������������������������������������������������������������
� ���� �������� ����� �� ������ ��� ������ �� ������� ��� ���� ����� � ���������������� ������ �� ��� ��
������������������������������������������������������������ ��������
�������������������������������
����������������������������������� �
��
��������������
���������
���������������������������������������������������� ����������������������������������
�� ������������������
���������������������� � �������������������������������������� ������ ��� ����
��������������
�� ������������������������������������� ���
���������������������
� ������������������������������������
� ������������������������ ���
��������� �� � ����� �� ����������
� ���� �����������������������
������� ��
������������������������
���
������� ��������� �� � ����������
����������
������ � � � �������
��������������
����������������������������������������
����������� ��������
�����������������������
������������������������� �
���
������� ��������� �� � ������ ����
������
��������
���
������������ ������ ��������������
�������������������������������������������������������������������
���������������������
���������������������������
����������� ����������������������������������������� ������������������
������������
���������� ���������������������������������������� �����������������������������
�����
��� � ��
���� � �
����� �
������������������
����������������������������������������������������������������������������������������������
�����������������
���������������������������������
��������������������
�������� � � ���������������������������������������������������������������������������������������
��������������������������������������������������������������������������������������������
���� ����������
������������������������������������������������������������������� �����������������
� ��������������
�������������������������������������������������������
������������������������
�����������������������������������������������������������
������������������������������������� ������ ��� ���� ����������������������������������
���������������������������������������������������
�
���������
��������������������������������������
� �����������������������
������ ���������������������������������
������ ��� ���� ������ ������ �
����������� � ���� ��� �� ��������������������������������������������������
���������
�� ��������������� � �������� ������������������������������������ � �������������
��������
������������������������
�������������������������������������������������
�����������
�����������
����������
����������������������
����� ������� ������ ���
� �� ������ ���
��������
���������������������������������������������
�������������������
�� ���
�������
���
�������
���
�����
����
� � ������������������
���������
� � �����
���
� � �����
���
�����
����
� ��� � ���
��������� �����
�� ��� �������
��������� ���� �
� ��� �
�� ��� ��� � ���
� ��� � ���
��������� �����
�� ��� ���
��������� ���� �
��� � ��� �
��������� �����
�� ��� ��� �
���
��� �������
� ����� ��������� �����
�� ��� ����� �
� ���� �
���
�����
��������� ����� ����
���
�� � � ����� �� �����
����
� ��� � ��� �
�� ��� ����� �
�� ��� �� �
�
� ��� � ��� ���
�����
�����
�������� �
����
�� ��� ��� �
��������� ��� �
���� ������
� � �����
���
� � �����
���
�����
����
���������� ��������������
������������������������������
������������������������������
������������������
������ ����
�� � � � � � � � �
�� �� �� � � �
�� �� ���
�� �� �� �� ��
�� �� �� �� ��
�� �� �� � � �
�� �� �� �� ��
�� �� �� � �
�� �� �� ��
�� �� ���
�� �� �� �� ��
��� �� �� �� ��
��� �� �� �� ��
��������������������������������
���������������������
����������������������� ������������
����������������
����� ����� ������
��������� ������������� �����
� ������� ������
������ �����������
INTRODUCTION
INTERMEDIATE LANGUAGES
Syntax tree
Postfix notation
The semantic rules for generating three-address code from common programming language
constructs are similar to those for constructing syntax trees or for generating postfix notation.
Graphical Representations:
Syntax tree:
A syntax tree depicts the natural hierarchical structure of a source program. A dag (Directed Acyclic
Graph) gives the same information but in a more compact way because common subexpressions are identified. A
syntax tree and dag for the assignment statement a : = b * - c + b * - c are as follows:
assign assign
a + a +
* * *
c c c
Postfix notation:
Syntax-directed definition:
Two representations of the syntax tree are as follows. In (a) each node is
represented as a record with a field for its operator and additional fields for pointers to
its children. In (b), nodes are allocated from an array of records and the index or
position of the node serves as the pointer to the node. All the nodes in the syntax tree
can be visited by following pointers, starting from the root at position 10.
1 id c
id a
2 uminus2 1
3 * 0 2
+
4 id b
5 id c
* *
6 uminus 5
id b id b
7 * 4 6
uminus uminus 8 + 3 7
9 id a
id c id c
8
10 assign 9
(a) (b)
Three-Address Code:
x : = y op z
where x, y and z are names, constants, or compiler-generated temporaries; op stands for any
operator, such as a fixed- or floating-point arithmetic operator, or a logical operator on boolean-
valued data. Thus a source language expression like x+ y*z might be translated into a sequence
t1 : = y * z
t2 : = x + t 1
The use of names for the intermediate values computed by a program allows
three-address code to be easily rearranged – unlike postfix notation.
Three-address code corresponding to the syntax tree and dag given above
t1 : = - c t1 : = -c
t2 : = b * t 1 t2 : = b * t 1
t3 : = - c t5 : = t 2 + t2
t4 : = b * t 3 a : = t5
t5 : = t 2 + t 4
a : = t5
(a) Code for the syntax tree (b) Code for the dag
The reason for the term “three-address code” is that each statement usually contains
three addresses, two for the operands and one for the result.
The unconditional jump goto L. The three-address statement with label L is the next to
be executed.
Conditional jumps such as if x relop y goto L. This instruction applies a relational operator ( <, =, >=,
etc. ) to x and y, and executes the statement with label L next if x stands in relation
relop to y. If not, the three-address statement following if x relop y goto L is executed
next, as in the usual sequence.
param x and call p, n for procedure calls and return y, where y representing a returned value
is optional. For example,
param x1
param x2
...
param xn
call p,n
generated as part of a call of the procedure p(x1, x2, …. ,xn ).
When three-address code is generated, temporary names are made up for the
interior nodes of a syntax tree. For example, id : = E consists of code to evaluate E into
some temporary t, followed by the assignment id.place : = t.
S.begin:
E.code
S1.code
goto S.begin
S.after: ...
Triples
Indirect triples
Quadruples:
A quadruple is a record structure with four fields, which are, op, arg1, arg2 and result.
The op field contains an internal code for the operator. The three-address statement
x : = y op z is represented by placing y in arg1, z in arg2 and x in result.
The contents of fields arg1, arg2 and result are normally pointers to the symbol-
table entries for the names represented by these fields. If so, temporary names
must be entered into the symbol table as they are created.
Triples:
To avoid entering temporary names into the symbol table, we might refer to a
temporary value by the position of the statement that computes it.
The fields arg1 and arg2, for the arguments of op, are either pointers to the
symbol table or pointers into the triple structure ( for temporary values ).
Since three fields are used, this intermediate code format is known as triples.
Indirect Triples:
For example, let us use an array statement to list pointers to triples in the
desired order. Then the triples shown above might be represented as follows:
DECLARATIONS
Before the first declaration is considered, offset is set to 0. As each new name is seen ,
that name is entered in the symbol table with offset equal to the current value of offset,
and offset is incremented by the width of the data object denoted by that name.
The procedure enter( name, type, offset ) creates a symbol-table entry for name,
gives its type type and relative address offset in its data area.
Attribute type represents a type expression constructed from the basic types
integer and real by applying the type constructors pointer and array. If type
expressions are represented by graphs, then attribute type might be a pointer to
the node representing a type expression.
The width of an array is obtained by multiplying the width of each element by the
number of elements in the array. The width of each pointer is assumed to be 4.
P D { offset : = 0 }
D D;D
P D
D D ; D | id : T | proc id ; D ; S
One possible implementation of a symbol table is a linked list of entries for names.
For example, consider the symbol tables for procedures readarray, exchange, and
quicksort pointing back to that for the containing procedure sort, consisting of the entire
program. Since partition is declared within quicksort, its table points to that of quicksort.
sort
nil header
a
x
readarray to readarray
exchange to exchange
quicksort
partition
header
i
j
The semantic rules are defined in terms of the following operations:
mktable(previous) creates a new symbol table and returns a pointer to the new table.
The argument previous points to a previously created symbol table, presumably that
for the enclosing procedure.
enter(table, name, type, offset) creates a new entry for name name in the symbol table pointed to by
table. Again, enter places type type and relative address offset in fields within the entry.
addwidth(table, width) records the cumulative width of all the entries in table in the
header associated with this symbol table.
enterproc(table, name, newtable) creates a new entry for procedure name in the symbol
table pointed to by table. The argument newtable points to the symbol table for this
procedure name.
M ɛ { t : = mktable (nil);
push (t,tblptr); push (0,offset) }
D D1;D2
The stack tblptr is used to contain pointers to the tables for sort, quicksort, and
partition when the declarations in partition are considered.
The top element of stack offset is the next available relative address for a
local of the current procedure.
BC {actionA}
are done before actionA at the end of the production occurs. Hence, the action
associated with the marker M is the first to be done.
The action for nonterminal M initializes stack tblptr with a symbol table for the
outermost scope, created by operation mktable(nil). The action also pushes
relative address 0 onto stack offset.
For each variable declaration id: T, an entry is created for id in the current
symbol table. The top of stack offset is incremented by T.width.
When the action on the right side of D proc id; ND1; S occurs, the width of all
declarations generated by D1 is on the top of stack offset; it is recorded using
addwidth. Stacks tblptr and offset are then popped.
At this point, the name of the enclosed procedure is entered into the symbol
table of its enclosing procedure.
ASSIGNMENT STATEMENTS
Suppose that the context in which an assignment appears is given by the following grammar.
P MD
M ɛ
D D ; D | id : T | proc id ; N D ; S
N ɛ
Nonterminal P becomes the new start symbol when these productions are added to
those in the translation scheme shown below.
S id : = E { p : = lookup ( id.name);
if p ≠ nil then
emit( p ‘ : =’ E.place)
else error }
if p ≠ nil then
E.place : = p
else error }
evaluate E1 into t1
evaluate E2 into t2
t : = t1 + t2
The lifetimes of these temporaries are nested like matching pairs of balanced parentheses.
statement value of c
0
$0 := a * b 1
$1 := c * d 2
$0 :=$0+$1 1
$1 := e * f 2
$0 :=$0-$1 1
x := $0 0
base + ( i – low ) x w
where low is the lower bound on the subscript and base is the relative address of the
storage allocated for the array. That is, base is the relative address of A[low].
The expression can be partially evaluated at compile time if it is rewritten as
i x w + ( base – low x w)
The subexpression c = base – low x w can be evaluated when the declaration of the
array is seen. We assume that c is saved in the symbol table entry for A , so the relative
address of A[i] is obtained by simply adding i x w to c.
Row-major (row-by-row)
Column-major (column-by-column)
A[11] A[11]
first column
first row A[ 1,2 ] A[21]
A[13] A[1,2]
A[ 2,1 ] A[2,2] second column
where, low1 and low2 are the lower bounds on the values of i1 and i2 and n2 is the
number of values that i2 can take. That is, if high2 is the upper bound on the value of i 2,
then n 2 = high 2 – low2 + 1.
Assuming that i1 and i2 are the only values that are known at compile time, we can
rewrite the above expression as
(( i1 x n2 ) + i2 ) x w + ( base – (( low1 x n2 ) + low2 ) x w)
Generalized formula:
The expression generalizes to the following expression for the relative address of A[i1,i2,…,ik]
SL:=E
EE+E
E(E)
EL
LElist ]
Lid
ElistElist , E
Elistid [ E
Elist.place : = E.place;
Elist.ndim : = 1 }
Consider the grammar for assignment statements as above, but suppose there
are two types – real and integer , with integers converted to reals when necessary. We
have another attribute E.type, whose value is either real or integer. The semantic rule
for E.type associated with the production E E + E is :
E E+E { E.type : =
if E1.type = integer and
E2.type = integer then integer
else real }
The entire semantic rule for E E + E and most of the other productions must be
modified to generate, when necessary, three-address statements of the form x : =
inttoreal y, whose effect is to convert integer y to a real of equal value, called x.
t1 : = i int* j
t3 : = inttoreal t1
t2 : = y real+ t3
x : = t2
BOOLEAN EXPRESSIONS
Boolean expressions have two primary purposes. They are used to compute
logical values, but more often they are used as conditional expressions in statements
that alter the flow of control, such as if-then-else, or while-do statements.
Boolean expressions are composed of the boolean operators ( and, or, and not )
applied to elements that are boolean variables or relational expressions. Relational
expressions are of the form E1 relop E 2, where E1 and E2 are arithmetic expressions.
There are two principal methods of representing the value of a boolean expression. They are :
To encode true and false numerically and to evaluate a boolean expression analogously
to an arithmetic expression. Often, 1 is used to denote true and 0 to denote false.
Numerical Representation
For example :
E E1 or E2 { E.place : = newtemp;
emit( E.place ‘: =’ E1.place ‘or’ E2.place ) }
E E1 and E2 { E.place : = newtemp;
emit( E.place ‘: =’ E1.place ‘and’ E2.place ) }
E not E1 { E.place : = newtemp;
emit( E.place ‘: =’ ‘not’ E1.place ) }
E (E1) { E.place : = E1.place }
E id1 relop id2 { E.place : = newtemp;
emit( ‘if’ id1.place relop.op id2.place ‘goto’ nextstat + 3);
emit( E.place ‘: =’ ‘0’ );
emit(‘goto’ nextstat +2);
emit( E.place ‘: =’ ‘1’) }
E true { E.place : = newtemp;
emit( E.place ‘: =’ ‘1’) }
E false { E.place : = newtemp;
emit( E.place ‘: =’ ‘0’) }
Short-Circuit Code:
We can also translate a boolean expression into three-address code without generating
code for any of the boolean operators and without having the code necessarily evaluate the
entire expression. This style of evaluation is sometimes called “short-circuit” or “jumping” code.
It is possible to evaluate boolean expressions without generating code for the boolean operators
and, or, and not if we represent the value of an expression by a position in the code sequence.
if E then S1
| if E then S1 else S2
| while E do S1
In each of these productions, E is the Boolean expression to be translated. In the
translation, we assume that a three-address statement can be symbolically labeled, and
that the function newlabel returns a new symbolic label each time it is called.
E.true is the label to which control flows if E is true, and E.false is the label to
which control flows if E is false.
to E.true
E.code
to E.false
E.true : to E.false
S1.code goto S.next
E.false:
S2.code
E.false : ...
S.next: ...
to E.false
E.true: S1.code
goto S.begin
E.false: ...
(c) while-do
Syntax-directed definition for flow-of-control statements
E E1 or E2 E1.true : = E.true;
E1.false : = newlabel;
E2.true : = E.true;
E2.false : = E.false;
E.code : = E1.code || gen(E1.false ‘:’) || E2.code
E E1 and E2 E.true : = newlabel;
E1.false : = E.false;
E2.true : = E.true;
E2.false : = E.false;
E.code : = E1.code || gen(E1.true ‘:’) || E2.code
E not E1 E1.true : = E.false;
E1.false : = E.true;
E.code : = E1.code
E (E1) E1.true : = E.true;
E1.false : = E.false;
E.code : = E1.code
E id1 relop id2 E.code : = gen(‘if’ id1.place relop.op id2.place
‘goto’ E.true) || gen(‘goto’ E.false)
CASE STATEMENTS
switch expression
begin
case value : statement
case value : statement
...
case value : statement
default : statement
end
switch E
begin
case V1 : S1
case V2 : S2
...
case Vn-1 : Sn-1
default : Sn
end
This case statement is translated into intermediate code that has the following form :
When keyword switch is seen, two new labels test and next, and a new
temporary t are generated.
As each case keyword occurs, a new label Li is created and entered into the
symbol table. A pointer to this symbol-table entry and the value Vi of case
constant are placed on a stack (used only to store cases).
Each statement case Vi : Si is processed by emitting the newly created label Li,
followed by the code for Si , followed by the jump goto next.
Then when the keyword end terminating the body of the switch is found, the code can be
generated for the n-way branch. Reading the pointer-value pairs on the case stack from the
bottom to the top, we can generate a sequence of three-address statements of the form
case V1 L1
case V2 L2
...
case Vn-1 Ln-1
case t Ln
label next
where t is the name holding the value of the selector expression E, and Ln is the
label for the default statement.
BACKPATCHING
The easiest way to implement the syntax-directed definitions for boolean expressions is to
use two passes. First, construct a syntax tree for the input, and then walk the tree in depth-first
order, computing the translations. The main problem with generating code for boolean expressions
and flow-of-control statements in a single pass is that during one single pass we may not know the
labels that control must go to at the time the jump statements are generated. Hence, a series of
branching statements with the targets of the jumps left unspecified is generated. Each statement will
be put on a list of goto statements whose labels will be filled in when the proper label can be
determined. We call this subsequent filling in of labels backpatching.
makelist(i) creates a new list containing only i, an index into the array of quadruples;
makelist returns a pointer to the list it has made.
merge(p1,p2) concatenates the lists pointed to by p1 and p2, and returns a pointer to
the concatenated list.
backpatch(p,i) inserts i as the target label for each of the statements on the list
pointed to by p.
Boolean Expressions:
E E1 or M E2
| E1 and M E2
| not E1
| (E1)
| id1 relop id2
| true
| false
M ɛ
Synthesized attributes truelist and falselist of nonterminal E are used to generate
jumping code for boolean expressions. Incomplete jumps with unfilled labels are
placed on lists pointed to by E.truelist and E.falselist.
Consider production E E1 and M E2. If E1 is false, then E is also false, so the statements on
E1.falselist become part of E.falselist. If E 1 is true, then we must next test E 2, so the
target for the statements E1.truelist must be the beginning of the code generated for E 2.
This target is obtained using marker nonterminal M.
Attribute M.quad records the number of the first statement of E 2.code. With the production M
we associate the semantic action
{ M.quad : = nextquad }
The variable nextquad holds the index of the next quadruple to follow. This value will be
backpatched onto the E1.truelist when we have seen the remainder of the production E E1 and
E2. The translation scheme is as follows:
S if E then S
| if E then S else S
| while E do S
| begin L end
| A
LL;S
| S
The nonterminal E has two attributes E.truelist and E.falselist. L and S also need
a list of unfilled quadruples that must eventually be completed by backpatching. These
lists are pointed to by the attributes L..nextlist and S.nextlist. S.nextlist is a pointer to a
list of all conditional and unconditional jumps to the quadruple following the statement S
in execution order, and L.nextlist is defined similarly.
S if E then M1 S1 N else M2 S2
{ backpatch (E.truelist, M1.quad);
backpatch (E.falselist, M2.quad);
S.nextlist : = merge (S1.nextlist, merge (N.nextlist, S2.nextlist)) }
We backpatch the jumps when E is true to the quadruple M 1.quad, which is the beginning of the
code for S1. Similarly, we backpatch jumps when E is false to go to the beginning of the code for S 2.
The list S.nextlist includes all jumps out of S1 and S2, as well as the jump generated by N.
The statement following L1 in order of execution is the beginning of S. Thus the L1.nextlist
list is backpatched to the beginning of the code for S, which is given by M.quad.
PROCEDURE CALLS
Scall id ( Elist )
Elist Elist , E
Elist E
Calling Sequences:
The translation for a call includes a calling sequence, a sequence of actions taken on entry
to and exit from each procedure. The falling are the actions that take place in a calling sequence :
When a procedure call occurs, space must be allocated for the activation record
of the called procedure.
The arguments of the called procedure must be evaluated and made available to
the called procedure in a known place.
The state of the calling procedure must be saved so it can resume execution after the call.
Also saved in a known place is the return address, the location to which the
called routine must transfer after it is finished.
Finally a jump to the beginning of the code for the called procedure must be
S call id ( Elist )
{ for each item p on queue do
emit (‘ param’ p );
emit (‘call’ id.place) }
Elist Elist , E
Elist E
{ initialize queue to contain only E.place }
Here, the code for S is the code for Elist, which evaluates the arguments, followed
by a param p statement for each argument, followed by a call statement.
queue is emptied and then gets a single pointer to the symbol table location for
the name that denotes the value of E.
MODULE-4 CODE GENERATION
The final phase in compiler model is the code generator. It takes as input an
intermediate representation of the source program and produces as output an
equivalent target program. The code generation techniques presented below can be
used whether or not an optimizing phase occurs before code generation.
symbol
table
Prior to code generation, the front end must be scanned, parsed and translated
into intermediate representation along with necessary type checking. Therefore,
input to code generation is assumed to be error-free.
Target program:
The output of the code generator is the target program. The output may be :
Absolute machine language
It can be placed in a fixed memory location and can be executed immediately.
Relocatable machine language
It allows subprograms to be compiled separately.
Assembly language
Code generation is made easier.
Memory management:
Names in the source program are mapped to addresses of data objects in
run-time memory by the front end and code generator.
Instruction selection:
The instructions of target machine should be complete and uniform.
Instruction speeds and machine idioms are important factors when efficiency
of target program is considered.
The quality of the generated code is determined by its speed and size.
The former statement can be translated into the latter statement as shown below:
Register allocation
Instructions involving register operands are shorter and faster than those
involving operands in memory.
Evaluation order
The order in which the computations are performed can affect the efficiency
of the target code. Some computation orders require fewer registers to hold
intermediate results than others.
TARGET MACHINE
Familiarity with the target machine and its instruction set is a prerequisite for
designing a good code generator.
The target computer is a byte-addressable machine with 4 bytes to
a word. It has n general-purpose registers, R0, R1, . . . , Rn-1.
It has two-address instructions of the form:
op source, destination
where, op is an op-code, and source and destination are data fields.
It has the following op-codes :
MOV (move source to destination)
ADD (add source to destination)
SUB (subtract source from destination)
The source and destination of an instruction are specified by combining
registers and memory locations with address modes.
absolute M M 1
register R R 0
literal #c c 1
For example : MOV R0, M stores contents of Register R0 into memory location M ;
MOV 4(R0), M stores the value contents(4+contents(R0)) into M.
Instruction costs :
Instruction cost = 1+cost for source and destination address modes. This cost
corresponds to the length of the instruction.
Address modes involving registers have cost zero.
Address modes involving memory location or literal have cost one.
Instruction length should be minimized if space is important. Doing so also
minimizes the time taken to fetch and perform the instruction.
For example : MOV R0, R1 copies the contents of register R0 into R1. It has cost
one, since it occupies only one word of memory.
The three-address statement a : = b + c can be implemented by many different
instruction sequences :
i) MOV b, R0
ADD c, R0 cost = 6
MOV R0, a
ii) MOV b, a
ADD c, a cost = 6
In order to generate good code for target machine, we must utilize its
addressing capabilities efficiently.
GOTO callee.code_area /*It transfers control to the target code for the called procedure */
where,
callee.static_area – Address of the activation record
callee.code_area – Address of the first instruction for called procedure
#here + 20 – Literal return address which is the address of the instruction following GOTO.
GOTO *callee.static_area
This transfers control to the address saved at the beginning of the activation record.
The statement HALT is the final instruction that returns control to the operating
Static allocation can become stack allocation by using relative addresses for storage
in activation records. In stack allocation, the position of activation record is stored in register
so words in activation records can be accessed as offsets from the value in this register.
Initialization of stack:
GOTO callee.code_area
where,
caller.recordsize – size of the activation record
#here + 16 – address of the instruction following the GOTO
Basic Blocks
Output: A list of basic blocks with each three-address statement in exactly one block
Method:
We first determine the set of leaders, the first statements of basic blocks. The rules
we use are of the following:
The first statement is a leader.
Any statement that is the target of a conditional or unconditional goto is a
leader.
Any statement that immediately follows a goto or conditional goto statement
is a leader.
For each leader, its basic block consists of the leader and all statements up to but
not including the next leader or the end of the program.
Consider the following source code for dot product of two vectors a and b of length 20
begin
prod :=0;
i:=1;
do begin
i :=i+1;
end
while i <= 20
end
(2) i := 1
(3) t1 := 4* i
(5) t3 := 4* i
(7) t5 := t2*t4
(8) t6 := prod+t5
(9) prod := t6
(10) t7 := i+1
(11) i := t7
A number of transformations can be applied to a basic block without changing the set of
expressions computed by the block. Two important classes of transformation are :
Structure-preserving transformations
Algebraic transformations
a:=b+c a:=b+c
b:=a–d b:=a-d
c:=b+c c:=b+c
d:=a–d d:=b
Since the second and fourth expressions compute the same expression, the basic
block can be transformed as above.
b) Dead-code elimination:
Suppose x is dead, that is, never subsequently used, at the point where the
statement x : = y + z appears in a basic block. Then this statement may be safely
removed without changing the value of the basic block.
d) Interchange of statements:
t1 : = b + c
t2 : = x + y
We can interchange the two statements without affecting the value of the
block if and only if neither x nor y is t1 and neither b nor c is t2.
Algebraic transformations:
Flow graph is a directed graph containing the flow-of-control information for the
set of basic blocks making up a program.
The nodes of the flow graph are basic blocks. It has a distinguished initial
node. E.g.: Flow graph for the vector dot product is given as follows:
prod : = 0 B1
i:=1
t1 : = 4 * i
t2 : = a [ t1 ]
t3 : = 4 * i B2
t4 : = b [ t3 ]
t5 : = t2 * t4
t6 : = prod + t5
prod : = t6
t7 : = i + 1
i : = t7
if i <= 20 goto B2
Loops
NEXT-USE INFORMATION
If the name in a register is no longer needed, then we remove the name from the
register and the register can be used to store some other names.
Input: Basic block B of three-address statements
Symbol Table:
y Live i
z Live i
(or)
(or)
ADD Rj, Ri
The algorithm takes as input a sequence of three-address statements constituting a basic block.
For each three-address statement of the form x : = y op z, perform the following actions:
Invoke a function getreg to determine the location L where the result of the computation
y op z should be stored.
Consult the address descriptor for y to determine y’, the current location of y. Prefer the
register for y’ if the value of y is currently both in memory and a register. If the value of y is
not already in L, generate the instruction MOV y’ , L to place a copy of y in L.
If the current values of y or z have no next uses, are not live on exit from the block,
and are in registers, alter the register descriptor to indicate that, after execution of x :
= y op z , those registers will no longer contain y or z.
The assignment d : = (a-b) + (a-c) + (a-c) might be translated into the following
three-address code sequence:
t:=a–b
u:=a–c
v:=t+u
d:=v+u
with d live at the end.
Register empty
The table shows the code sequences generated for the indexed assignment statements
a : = b [ i ] and a [ i ] : = b
The table shows the code sequences generated for the pointer assignments
a : = *p and *p : = a
a : = *p MOV *Rp, a 2
*p : = a MOV a, *Rp 2
Statement Code
x : = y +z MOV y, R0
if x < 0 goto z ADD z, R0
MOV R0,x
CJ< z
A DAG for a basic block is a directed acyclic graph with the following labels on nodes:
Leaves are labeled by unique identifiers, either variable names or constants.
Interior nodes are labeled by an operator symbol.
Nodes are also optionally given a sequence of identifiers for labels to store
the computed values.
DAGs are useful data structures for implementing transformations on basic blocks.
It gives a picture of how the value computed by a statement is used in
subsequent statements.
It provides a good way of determining common sub - expressions.
Algorithm for construction of DAG
Output: A DAG for the basic block containing the following information:
A label for each node. For leaves, the label is an identifier. For interior nodes, an
operator symbol.
For each node a list of attached identifiers to hold the computed values.
Case (i) x : = y OP z
Case (ii) x : = OP y
Case (iii) x : = y
Method:
Step 2: For the case(i), create a node(OP) whose left child is node(y) and right child is
For case(ii), determine whether there is node(OP) with one child node(y). If not
create such a node.
Step 3: Delete x from the list of identifiers for node(x). Append x to the list of
attached identifiers for the node n found in step 2 and set node(x) to n.
t1 := 4* i
t2 := a[t1]
t3 := 4* i
t4 := b[t3]
t5 := t2*t4
t6 := prod+t5
prod := t6
t7 := i+1
i := t7
if i<=20 goto (1)
Stages in DAG Construction
Application of DAGs:
The advantage of generating code for a basic block from its dag representation is that,
from a dag we can easily see how to rearrange the order of the final computation sequence
than we can starting from a linear sequence of three-address statements or quadruples.
MOV a , R0
ADD b , R0
MOV c , R1
ADD d , R1
MOV R0 , t1
MOV e , R0
SUB R1 , R0
MOV t1 , R1
SUB R0 , R1
MOV R1 , t4
t2 : = c + d
t3 : = e – t 2
t1 : = a + b
t4 : = t 1 – t 3
MOV c , R0
ADD d , R0
MOV a , R0
SUB R0 , R1
MOV a , R0
ADD b , R0
SUB R1 , R0
MOV R0 , t4
In this order, two instructions MOV R0 , t1 and MOV t1 , R1 have been saved.
A Heuristic ordering for Dags
Algorithm:
2 + - 3
4
*
5 - + 8
6 + 7 c d 11 e 12
a b
9 10
Initially, the only node with no unlisted parents is 1 so set n=1 at line (2) and list 1 at line (3).
Now, the left argument of 1, which is 2, has its parents listed, so we list 2 and set n=2 at line (6).
Now, at line (4) we find the leftmost child of 2, which is 6, has an unlisted parent 5. Thus we
select a new n at line (2), and node 3 is the only candidate. We list 3 and proceed down its
left chain, listing 4, 5 and 6. This leaves only 8 among the interior nodes so we list that.
t8 : = d + e
t6 : = a + b
t5 : = t 6 – c
t 4 : = t 5 * t8
t3 : = t4 – e
t2 : = t 6 + t4
t 1 : = t 2 * t3
This will yield an optimal code for the DAG on machine whatever be the number of registers.
MODULE-4 - CODE OPTIMIZATION
INTRODUCTION
The code produced by the straight forward compiling algorithms can often be made
to run faster or take less space, or both. This improvement is achieved by program
transformations that are traditionally called optimizations. Compilers that apply code-
improving transformations are called optimizing compilers.
Simply stated, the best program transformations are those that yield the most benefit
for the least effort.
The transformation must preserve the meaning of programs. That is, the
optimization must not change the output produced by a program for a given input, or
cause an error such as division by zero, that was not present in the original source
program. At all times we take the “safe” approach of missing an opportunity to apply
a transformation rather than risk changing what the program does.
The transformation must be worth the effort. It does not make sense for a compiler
writer to expend the intellectual effort to implement a code improving transformation and
to have the compiler expend the additional time compiling source programs if this effort
is not repaid when the target programs are executed. “Peephole” transformations of this
kind are simple enough and beneficial enough to be included in any compiler.
Organization for an Optimizing Compiler:
Function-Preserving Transformations
There are a number of ways in which a compiler can improve a program without
changing the function it computes.
The transformations
The above code can be optimized using the common sub-expression elimination as
t1: = 4*i
t2: = a [t1]
t3: = 4*j
t5: = n
t6: = b [t1] +t5
The common sub expression t4: =4*i is eliminated as its computation is alre ady in
t1. And value of i is not been changed from definition to use.
Copy Propagation:
Assignments of the form f : = g called copy statements, or copies for short. The
idea behind the copy-propagation transformation is to use g for f, whenever
possible after the copy statement f: = g. Copy propagation means use of one
variable instead of another. This may not appear to be an improvement, but as
we shall see it gives us an opportunity to eliminate x.
For example:
x=Pi;
……
A=x*r*r;
A=Pi*r*r;
Dead-Code Eliminations:
A variable is live at a point in a program if its value can be used subsequently; otherwise, it
is dead at that point. A related idea is dead or useless code, statements that compute
values that never get used. While the programmer is unlikely to introduce any dead code
intentionally, it may appear as the result of previous transformations. An optimization can
be done by eliminating dead code.
Example:
i=0;
if(i=1)
{
a=b+5;
}
Here, ‘if’ statement is dead code because this condition will never get satisfied.
Constant folding:
We can eliminate both the test and printing from the object code. More generally,
deducing at compile time that the value of an expression is a constant and using
the constant instead is known as constant folding.
One advantage of copy propagation is that it often turns the copy statement into
dead code.
For example,
a=3.14157/2 can be replaced by
a=1.570 there by eliminating a division operation.
Loop Optimizations:
We now give a brief introduction to a very important place for optimizations, namely
loops, especially the inner loops where programs tend to spend the bulk of their time.
The running time of a program may be improved if we decrease the number of
instructions in an inner loop, even if we increase the amount of code outside that loop.
Three techniques are important for loop optimization:
Code Motion:
Induction Variables :
Loops are usually processed inside out. For example consider the loop around B3.
Note that the values of j and t4 remain in lock-step; every time the value of j
decreases by 1, that of t4 decreases by 4 because 4*j is assigned to t4. Such
identifiers are called induction variables.
When there are two or more induction variables in a loop, it may be possible to
get rid of all but one, by the process of induction-variable elimination. For the
inner loop around B3 in Fig. we cannot get rid of either j or t4 completely; t4 is
used in B3 and j in B4. However, we can illustrate reduction in strength and
illustrate a part of the process of induction-variable elimination. Eventually j will
be eliminated when the outer loop of B2 - B5 is considered.
Example:
As the relationship t4:=4*j surely holds after such an assignment to t4 in Fig. and t4 is not
changed elsewhere in the inner loop around B3, it follows that just after the statement
j:=j-1 the relationship t4:= 4*j-4 must hold. We may therefore replace the assignment t
4:= 4*j by t4:= t4-4. The only problem is that t 4 does not have a value when we enter
block B3 for the first time. Since we must maintain the relationship t4=4*j on entry to the
block B3, we place an initializations of t4 at the end of the block where j itself is
before after
Reduction In Strength:
Structure-Preserving Transformations
Algebraic Transformations
Structure-Preserving Transformations:
Common sub-expression
elimination Dead code elimination
Renaming of temporary variables
Interchange of two independent adjacent statements.
Common sub expressions need not be computed over and over again. Instead they can
be computed once and kept in store from where it’s referenced when encountered again
– of course providing the variable values in the expression still remain constant.
Example:
=b+c
=a-d
=b+c
=a-d
nd th
The 2 and 4 statements compute the same expression: b+c and a-d
= b+c
= a-d
=a
=b
Dead code elimination:
It’s possible that a large amount of dead (useless) code may exist in the program. This
might be especially caused when introducing variables and procedures as part of construction
or error-correction of a program – once declared and defined, one forgets to remove them in
case they serve no purpose. Eliminating these will definitely optimize the code.
Two statements
t1:=b+c
t2:=x+y
can be interchanged or reordered in its computation in the basic block when value
of t 1 does not affect the value of t2.
Algebraic Transformations:
a :=b+c
e :=c+d+b
:=b+c t
:=c+d
e :=t+b
Example:
Dominators:
In a flow graph, a node d dominates node n, if every path from initial node of the
flow graph to n goes through d. This will be denoted by d dom n. Every initial node
dominates all the remaining nodes in the flow graph and the entry of a loop dominates
all nodes in the loop. Similarly every node dominates itself.
Example:
D(1)={1}
D(2)={1,2}
D(3)={1,3}
D(4)={1,3,4}
D(5)={1,3,4,5}
D(6)={1,3,4,6}
D(7)={1,3,4,7}
D(8)={1,3,4,7,8}
D(9)={1,3,4,7,8,9}
D(10)={1,3,4,7,8,10}
Natural Loop:
A loop must have a single entry point, called the header. This entry point-
dominates all nodes in the loop, or it would not be the sole entry to the loop.
There must be at least one way to iterate the loop(i.e.)at least one path back to the header.
One way to find all the loops in a flow graph is to search for edges in the flow graph
whose heads dominate their tails. If a→b is an edge, b is the head and a is the tail.
These types of edges are called as back edges.
Example:
7→4 4DOM7
10 →7 7 DOM 10
4→3
8→3
→1
Output: The set loop consisting of all nodes in the natural loop n→d.
Method: Beginning with node n, we consider each node m*d that we know is in loop, to
make sure that m’s predecessors are also placed in loop. Each node in loop, except for
d, is placed once on stack, so its predecessors will be examined. Note that because d is
put in the loop initially, we never examine its predecessors, and thus find only those
nodes that reach n without going through d.
Procedure insert(m);
if m is not in loop then begin
loop := loop U {m};
push m onto stack
end;
stack : = empty;
loop : = {d};
insert(n);
while stack is not empty do begin
pop m, the first element of stack, off stack;
for each predecessor p of m do insert(p)
end
Inner loop:
If we use the natural loops as “the loops”, then we have the useful property that
unless two loops have the same header, they are either disjointed or one is entirely
contained in the other. Thus, neglecting loops with the same header for the moment,
we have a natural notion of inner loop: one that contains no other loop.
When two natural loops have the same header, but neither is nested within the
other, they are combined and treated as a single loop.
Pre-Headers:
The pre-header has only the header as successor, and all edges which formerly
entered the header of L from outside L instead enter the pre-header.
Initially the pre-header is empty, but transformations on L may place statements in it.
header pre-header
loop L
hea der
loop L
Reducible flow graphs are special flow graphs, for which several code
optimization transformations are especially easy to perform, loops are
unambiguously defined, dominators can be easily calculated, data flow analysis
problems can also be solved efficiently.
Definition:
A flow graph G is reducible if and only if we can partition the edges into two
disjoint groups, forward edges and back edges, with the following properties.
The forward edges from an acyclic graph in which every node can be reached
from initial node of G.
If we know the relation DOM for a flow graph, we can find and remove all the
back edges.
If the forward edges form an acyclic graph, then we can say the flow graph reducible.
In the above example remove the five back edges 4→3, 7→4, 8→3, 9→1 and
10→7 whose heads dominate their tails, the remaining graph is acyclic.
The key property of reducible flow graphs for loop analysis is that in such flow graphs
every set of nodes that we would informally regard as a loop must contain a back edge.
PEEPHOLE OPTIMIZATION
Redundant-instructions elimination
Flow-of-control optimizations
Algebraic simplifications
Use of machine idioms
Unreachable Code
Redundant Loads And Stores:
MOV R0,a
MOV a,R0
we can delete instructions (2) because whenever (2) is executed. (1) will ensure that the
value of a is already in register R 0.If (2) had a label we could not be sure that (1) was
always executed immediately before (2) and so we could not remove (2).
Unreachable Code:
#define debug 0
….
If ( debug ) {
goto L2
L2: …………………………(a)
If debug ≠1 goto L2
L2: ……………………………(b)
L2: ……………………………(c)
As the argument of the first statement of (c) evaluates to a constant true, it can be
replaced by goto L2. Then all the statement that print debugging aids are manifestly
unreachable and can be eliminated one at a time.
Flows-Of-Control Optimizations:
The unnecessary jumps can be eliminated in either the intermediate code or the target code
by the following types of peephole optimizations. We can replace the jump sequence
goto L1
….
L1: gotoL2
by the sequence
goto L2
….
L1: goto L2
If there are now no jumps to L1, then it may be possible to eliminate the statement
L1:goto L2 provided it is preceded by an unconditional jump .Similarly, the sequence
if a < b goto L1
….
L1: goto L2
can be replaced by
If a < b goto L2
….
L1: goto L2
goto L1
……..
L1: if a < b goto L2
L3: …………………………………..(1)
May be replaced by
If a < b goto L2
goto L3
…….
L3: ………………………………….(2)
While the number of instructions in (1) and (2) is the same, we sometimes skip the
unconditional jump in (2), but never in (1).Thus (2) is superior to (1) in execution time
Algebraic Simplification:
There is no end to the amount of algebraic simplification that can be attempted through
peephole optimization. Only a few algebraic identities occur frequently enough that it is
worth considering implementing them .For example, statements such as
:= x+0
Or
x := x * 1
Reduction in Strength:
2
X → X*X
The target machine may have hardware instructions to implement certain specific operations
efficiently. For example, some machines have auto-increment and auto-decrement addressing
modes. These add or subtract one from an operand before or after using its value.
The use of these modes greatly improves the quality of code when pushing or
popping a stack, as in parameter passing. These modes can also be used in code
for statements like i : =i+1.
i:=i+1 → i++
i:=i-1 → i- -
This equation can be read as “ the information at the end of a statement is either
generated within the statement , or enters at the beginning and is not killed as
control flows through the statement.”
The details of how data-flow equations are set and solved depend on three factors.
The notions of generating and killing depend on the desired information, i.e., on
the data flow analysis problem to be solved. Moreover, for some problems,
instead of proceeding along with flow of control and defining out[s] in terms of
in[s], we need to proceed backwards and define in[s] in terms of out[s].
Since data flows along control paths, data-flow analysis is affected by the constructs in
a program. In fact, when we write out[s] we implicitly assume that there is unique end
point where control leaves the statement; in general, equations are set up at the level
of basic blocks rather than statements, because blocks do have unique end points.
There are subtleties that go along with such statements as procedure calls,
assignments through pointer variables, and even assignments to array variables.
Within a basic block, we talk of the point between two adjacent statements, as well as
the point before the first statement and after the last. Thus, block B1 has four points:
one before any of the assignments and one after each of the three assignments.
B1
d1 : i :=m-1
d2: j :=n
d3 a = u1
B2
d4 : I := i+1
B3
d5: j := j-1
B4
B5 B6
d6 :a :=u2
Now let us take a global view and consider all the points in all the blocks. A path from p 1 to
pn is a sequence of points p1, p2,….,pn such that for each i between 1 and n-1, either
Reaching definitions:
These statements certainly define a value for x, and they are referred to as
unambiguous definitions of x. There are certain kinds of statements that may
define a value for x; they are called ambiguous definitions. The most usual
forms of ambiguous definitions of x are:
We say a definition d reaches a point p if there is a path from the point immediately
following d to p, such that d is not “killed” along that path. Thus a point can be reached
by an unambiguous definition and an ambiguous definition of the same
variable appearing later along one path.
Flow graphs for control flow constructs such as do-while statements have a
useful property: there is a single beginning point at which control enters and a
single end point that control leaves from when execution of the statement is
over. We exploit this property when we talk of the definitions reaching the
beginning and the end of statements with the following syntax.
E id + id| id
Expressions in this language are similar to those in the intermediate code, but
the flow graphs for statements have restricted forms.
S1
S1
If E goto s1
S2
S1 S2 If E goto s1
S1;S2
i)
S d:a:=b+c
gen [S] = { d }
kill [S] = Da – { d }
out [S] = gen [S] U ( in[S] – kill[S] )
Observe the rules for a single assignment of variable a. Surely that assignment is
a definition of a, say d. Thus
Gen[S]={d}
ii )
S S1
S2
gen[S]=gen[S2] U (gen[S1]-kill[S2])
Kill[S] = kill[S2] U (kill[S1] – gen[S2])
in [S1] = in [S]
in [S2] = out [S1]
out [S] = out [S2]
Under what circumstances is definition d generated by S=S1; S2? First of all, if it
is generated by S2, then it is surely generated by S. if d is generated by S1, it will
reach the end of S provided it is not killed by S2. Thus, we write
gen[S]=gen[S2] U (gen[S1]-kill[S2])
There is a subtle miscalculation in the rules for gen and kill. We have made the
assumption that the conditional expression E in the if and do statements are
“uninterpreted”; that is, there exists inputs to the program that make their
branches go either way.
We assume that any graph-theoretic path in the flow graph is also an execution path,
i.e., a path that is executed when the program is run with least one possible input.
When we compare the computed gen with the “true” gen we discover that the
true gen is always a subset of the computed gen. on the other hand, the true kill
is always a superset of the computed kill.
These containments hold even after we consider the other rules. It is natural to wonder
whether these differences between the true and computed gen and kill sets present a
serious obstacle to data-flow analysis. The answer lies in the use intended for these data.
Overestimating the set of definitions reaching a point does not seem serious; it
merely stops us from doing an optimization that we could legitimately do. On the
other hand, underestimating the set of definitions is a fatal error; it could lead us
into making a change in the program that changes what the program computes.
For the case of reaching definitions, then, we call a set of definitions safe or
conservative if the estimate is a superset of the true set of reaching definitions.
We call the estimate unsafe, if it is not necessarily a superset of the truth.
Returning now to the implications of safety on the estimation of gen and kill for reaching
definitions, note that our discrepancies, supersets for gen and subsets for kill are both in
the safe direction. Intuitively, increasing gen adds to the set of definitions that can reach
a point, and cannot prevent a definition from reaching a place that it truly reached.
Decreasing kill can only increase the set of definitions reaching any given point.
However, there are other kinds of data-flow information, such as the reaching-
definitions problem. It turns out that in is an inherited attribute, and out is a
synthesized attribute depending on in. we intend that in[S] be the set of definitions
reaching the beginning of S, taking into account the flow of control throughout the
entire program, including statements outside of S or within which S is nested.
The set out[S] is defined similarly for the end of s. it is important to note the
distinction between out[S] and gen[S]. The latter is the set of definitions that
reach the end of S without following paths outside S.
If a definition reaches the end of S if and only if it reaches the end of one or
both sub statements; i.e,
Out[S]=out[S1] U out[S2]
Representation of sets:
A bit vector representation for sets also allows set operations to be implemented
efficiently. The union and intersection of two sets can be implemented by logical or
and logical and, respectively, basic operations in most systems-oriented programming
languages. The difference A-B of sets A and B can be implemented by taking the
complement of B and then using logical and to compute A .
Space for data-flow information can be traded for time, by saving information
only at certain points and, as needed, recomputing information at intervening
points. Basic blocks are usually treated as a unit during global flow analysis, with
attention restricted to only those points that are the beginnings of blocks.
Since there are usually many more points than blocks, restricting our effort to blocks
is a significant savings. When needed, the reaching definitions for all points in a
block can be calculated from the reaching definitions for the beginning of a block.
Use-definition chains:
Evaluation order:
The techniques for conserving space during attribute evaluation, also apply to the
computation of data-flow information using specifications. Specifically, the only
constraint on the evaluation order for the gen, kill, in and out sets for statements is
that imposed by dependencies between these sets. Having chosen an evaluation
order, we are free to release the space for a set after all uses of it have occurred.
Earlier circular dependencies between attributes were not allowed, but we have
seen that data-flow equations may have circular dependencies.
Data-flow analysis must take all control paths into account. If the control paths
are evident from the syntax, then data-flow equations can be set up and
solved in a syntax-directed manner.
When programs can contain goto statements or even the more disciplined
break and continue statements, the approach we have taken must be
modified to take the actual control paths into account.
Several approaches may be taken. The iterative method works arbitrary flow
graphs. Since the flow graphs obtained in the presence of break and
continue statements are reducible, such constraints can be handled
systematically using the interval-based methods
However, the syntax-directed approach need not be abandoned when break
and continue statements are allowed.
Global transformations are not substitute for local transformations; both must be performed.
The available expressions data-flow problem discussed in the last section allows
us to determine if an expression at point p in a flow graph is a common sub-
expression. The following algorithm formalizes the intuitive ideas presented for
eliminating common sub-expressions.
6
METHOD: For every statement s of the form x := y+z such that y+z is available at
the beginning of block and neither y nor r z is defined prior to statement s in that
block, do the following.
To discover the evaluations of y+z that reach s’s block, we follow flow
graph edges, searching backward from s’s block. However, we do not go
through any block that evaluates y+z. The last evaluation of y+z in each
block encountered is an evaluation of y+z that reaches s.
The search in step(1) of the algorithm for the evaluations of y+z that reach
statement s can also be formulated as a data-flow analysis problem. However, it
does not make sense to solve it for all expressions y+z and all statements or
blocks because too much irrelevant information is gathered.
Not all changes made by algorithm are improvements. We might wish to limit the
number of different evaluations reaching s found in step (1), probably to one.
Algorithm will miss the fact that a*z and c*z must have the same value in
a :=x+y c :=x+y
vs
b :=a*z d :=c*z
Because this simple approach to common sub expressions considers only the
literal expressions themselves, rather than the values computed by expressions.
Copy propagation:
Various algorithms introduce copy statements such as x :=copies may also be generated
directly by the intermediate code generator, although most of these involve temporaries
local to one block and can be removed by the dag construction. We may substitute y for x in
all these places, provided the following conditions are met every such use u of x.
INPUT: a flow graph G, with ud-chains giving the definitions reaching block B,
and with c_in[B] representing the solution to equations that is the set of copies
x:=y that reach block B along every path, with no assignment to x or y following
the last occurrence of x:=y on the path. We also need ud-chains giving the uses
of each definition.
Determine those uses of x that are reached by this definition of namely, s: x: =y.
Determine whether for every use of x found in (1) , s is in c_in[B], where B is the
block of this particular use, and moreover, no definitions of x or y occur prior to
this use of x within B. Recall that if s is in c_in[B]then s is the only definition of x
that reaches B.
If s meets the conditions of (2), then remove s and replace all uses of x found
in (1) by y.
Ud-chains can be used to detect those computations in a loop that are loop-
invariant, that is, whose value does not change as long as control stays within the
loop. Loop is a region consisting of set of blocks with a header that dominates all
the other blocks, so the only way to enter the loop is through the header.
OUTPUT: the set of three-address statements that compute the same value each
time executed, from the time control enters the loop L until control next leaves L.
Mark “invariant” those statements whose operands are all either constant or
have all their reaching definitions outside L.
Repeat step (3) until at some repetition no new statements are marked “invariant”.
Mark “invariant” all those statements not previously so marked all of whose
operands either are constant, have all their reaching definitions outside L, or
have exactly one reaching definition, and that definition is a statement in L
marked invariant.
Having found the invariant statements within a loop, we can apply to some of
them an optimization known as code motion, in which the statements are
moved to pre-header of the loop. The following three conditions ensure that
code motion does not change what the program computes. Consider s: x: =y+z.
The block containing s dominates all exit nodes of the loop, where an exit of a
loop is a node with a successor not in the loop.
METHOD:
To understand why no change to what the program computes can occur, condition (2i)
and (2ii) of this algorithm assure that the value of x computed at s must be the value of
x after any exit block of L. When we move s to a pre-header, s will still be the definition
of x that reaches the end of any exit block of L. Condition (2iii) assures that any uses of
x within L did, and will continue to, use the value of x computed by s.
The condition (1) can be relaxed if we are willing to take the risk that we may
actually increase the running time of the program a bit; of course, we never
change what the program computes. The relaxed version of code motion
condition (1) is that we may move a statement s assigning x only if:
1’. The block containing s either dominates all exists of the loop, or x is not
used outside the loop. For example, if x is a temporary variable, we can be
sure that the value will be used only in its own block.
If code motion algorithm is modified to use condition (1’), occasionally the running time will
increase, but we can expect to do reasonably well on the average. The modified algorithm
may move to pre-header certain computations that may not be executed in the
loop. Not only does this risk slowing down the program significantly, it may
also cause an error in certain circumstances.
Even if none of the conditions of (2i), (2ii), (2iii) of code motion algorithm are met by
an assignment x: =y+z, we can still take the computation y+z outside a loop. Create
a new temporary t, and set t: =y+z in the pre-header. Then replace x: =y+z by x: =t
in the loop. In many cases we can propagate out the copy statement x: = t.
Definitions of variables used by s are either outside L, in which case they reach
the pre-header, or they are inside L, in which case by step (3) they were moved
to pre-header ahead of s.
The dominator information is changed slightly by code motion. The pre-header is now
the immediate dominator of the header, and the immediate dominator of the pre-header
is the node that formerly was the immediate dominator of the header. That is, the pre-
header is inserted into the dominator tree as the parent of the header.
However, our methods deal with variables that are incremented or decremented
zero, one, two, or more times as we go around a loop. The number of changes
to an induction variable may even differ at different iterations.
We shall look for basic induction variables, which are those variables i whose only
assignments within loop L are of the form i := i+c or i-c, where c is a constant.
METHOD:
Consider each basic induction variable i whose only uses are to compute
other induction variables in its family and in conditional branches. Take some
j in i’s family, preferably one such that c and d in its triple are as simple as
possible and modify each test that i appears in to use j instead. We assume
in the following tat c is positive. A test of the form ‘if i relop x goto B’, where x
is not an induction variable, is replaced by
r := c*x /* r := x if c is 1. */
r := r+d /* omit if d is 0 */
if j relop r goto B