Automata and Compiler Design: D.Rahul
Automata and Compiler Design: D.Rahul
Automata and Compiler Design: D.Rahul
D.Rahul
Assistant Professor
Information Technology
Formal Language and Regular Expressions: Languages, Definition
Language regular expressions, Finite Automata-DFA, NFA. Conversion
of regular expression to NFA, NFA to DFA, Applications of Finite
Automata to lexical analysis, lex tools.
Error
Messages
.
The analysis part breaks up the source program into
constant piece and creates an
intermediate representation of the source program.
The synthesis part constructs the desired target program
from the intermediate representation.
Phases of Compiler
.
• Lexical Analyzer reads the source program character by
character and returns the
• tokens of the source program.
• • A token describes a pattern of characters having same
meaning in the source
• program. (such as identifiers, operators, keywords, numbers,
delimeters and so
• on)
• Ex: newval := oldval + 12 => tokens: newval identifier
• := assignment operator
• oldval identifier
+
add operator
12 a number
MULT id2,id3,temp1
ADD temp1,#1,id1
Produces the target language in a specific architecture.
The target program is normally is a relocatable object file
containing the machine codes.
Ex:
( assume that we have an architecture with instructions
whose at least one of its operands is a machine register)
MOVEid2,R1
MULT id3,R1
ADD #1,R1
MOVER1,id1
• A number of tools have been developed
variously called compiler –compiler ,
compiler generator or translator writing system
• The input for these systems may contain
• 1. a description of source language.
• 2. a description of what output to be
• generated.
• 3. a description of the target
machine.
The principal aids provided by compiler-compiler are
1. Scanner Generator
2. Parser generator
3. Facilities for code generation
• The Role of the Lexical Analyzer
source tokens
a.out
program
• Compiler is a form of translator that translate a program
written in one language “
• The Source Language” to an equivalent program in a second
language “ The Target
• language ” or “ The Object Language “
Prog in source prog in target
Language compiler language
• Examples:
• Document processing: Latex, HTML, XML
• User interfaces: interactive applications, file systems, databases
• Natural language treatment
• Automata Theory, La
• Interpreters: Instead of producing a target program as a
translation, an interpreter
• performs the operations implied by the source program
no COMPILER INTERPRETER
1 It takes entire program as input It takes single instruction
as input
2 Intermediate object code is No intermediate object
generated code is generated
3 Conditional control statements Conditional control
are executes faster statements are
executes slower
4 Memory requirement is more Memory requirement is
less
5 Program need not be compiled Every time higher level
every program is converted
time into lower level
program
6 Errors are displayed after entire Errors are displayed for
program is checked every instruction
interpreted (if any)
7 Ex: C Compiler Ex : Basic
1.Incremental compiler :
It is a compiler it performs the recompilation of only modified
source rather than compiling the whole source program.
Features:
1. It tracks the dependencies between output and
source program.
2.It produces the same result as full recompilation.
3.It performs less task than recompilation.
4. The process of incremental compilation is effective for
maintenance.
2.Cross compiler:
Basically there exists 3 languages
1.source language i.e application program.
2.Target language in which machine code is return.
3.Implementation language in which a compiler is
return.
All these 3 languages are different. In other words
there may be a compiler which run on one machine and
produces the target code for another machine. Such a
compiler is called cross compiler.
To represent cross compiler T diagram is drawn as follows.
I
S T
I
• L N L N
• S S M M
•
• M
• The rules for T-diagrams are very simple. A compiler written
in some language “C” (could be anything from machine code
on up) that translates programs in language A to language B
looks like this (these diagrams are from
• Now suppose you have a machine that can directly run HP
machine code, and a compiler from ML to HP machine code,
and you want to get a ML compiler running on different
machine code P. You can start by writing an ML-to-P
compiler in ML, and compile that to get an ML-to-P compiler
in HP:
Bootstrapping Compilers and T-diagrams
Symbol
• A token is a pair a token name and an optional token value
• A pattern is a description of the form that the lexemes of a
token may take
• A lexeme is a sequence of characters in the source program
that matches the pattern for a token
Token Informal description Sample lexemes
if Characters i, f if
else Characters e, l, s, e else
comparison < or > or <= or >= or == or != <=, !=
• Example:
letter_ -> A | B | … | Z | a | b | … | Z | _
digit -> 0 | 1 | … | 9
id -> letter_ (letter_ | digit)*
• One or more instances: (r)+
• Zero of one instances: r?
• Character classes: [abc]
• Example:
– letter_ -> [A-Za-z_]
– digit -> [0-9]
– id -> letter_(letter|digit)*
• Starting point is the language grammar to understand the
tokens:
stmt -> if expr then stmt
| if expr then stmt else stmt
|Ɛ
expr -> term relop term
| term
term -> id
| number
• The next step is to formalize the patterns:
digit -> [0-9]
Digits -> digit+
number -> digit(.digits)? (E[+-]? Digit)?
letter -> [A-Za-z_]
id -> letter (letter|digit)*
If -> if
Then -> then
Else -> else
Relop -> < | > | <= | >= | = | <>
• We also need to handle whitespaces:
ws -> (blank | tab | newline)+
Transition diagrams
/* regular definitions
delim [ \t\n]
ws {delim}+
letter [A-Za-z]
digit [0-9]
id {letter}({letter}|{digit})*
number {digit}+(\.{digit}+)?(E[+-]?{digit}+)?
%%
{ws} {/* no action and no return */}
if {return(IF);}
then {return(THEN);}
else {return(ELSE);} {id} {yylval = (int) installID();
return(ID); }
{number} {yylval = (int) installNum(); return(NUMBER);}
…
Int installID() {/* funtion to install the lexeme, whose
first character is pointed to by yytext, and whose
length is yyleng, into the symbol table and return a
pointer thereto */
}
75
• Transition
s1 a s2
• Is read
In state s1 on input “a” go to state s2
• If end of input
– If in accepting state => accept, othewise => reject
• If no transition possible => reject
76
• A state
• An accepting state
a
• A transition
77
• Alphabet {0,1}
• What language does this recognize?
0
1
0 0
1
1
78
• Alphabet still { 0, 1 }
79
• Another kind of transition: -moves
A B
80
• Deterministic Finite Automata (DFA)
– One transition per input per state
– No -moves
• Nondeterministic Finite Automata (NFA)
– Can have multiple transitions for one input in a given
state
– Can have -moves
• Finite automata have finite memory
– Need only to encode the current state
81
• A DFA can take only one path through the state graph
– Completely determined by input
82
• An NFA can get into multiple states
1
0 1
• Input: 1 0 1
83
• NFAs and DFAs recognize the same set of languages (regular
languages)
84
• For a given language the NFA can be simpler than the DFA
1
0 0
NFA
1 0
0 0
DFA
1
1
• DFA can be exponentially larger than NFA
85
• High-level sketch
NFA
Regular
expressions DFA
Lexical Table-driven
Specification Implementation of DFA
86
• For each kind of rexp, define an NFA
– Notation: NFA for rexp A
• For
• For input a
a
87
• For AB
A B
• For A | B
B
A
88
• For A*
A
89
• Consider the regular expression
(1 | 0)*1
• The NFA is
C 1 E
A B 1
0 G H
I J
D F
90
NFA
Regular
expressions DFA
Lexical Table-driven
Specification Implementation of DFA
91
• Simulate the NFA
• Each state of resulting DFA
= a non-empty subset of states of the NFA
• Start state
= the set of NFA states reachable through -moves from NFA
start state
• Add a transition S a S’ to DFA iff
– S’ is the set of NFA states reachable from the states in S
after seeing the input a
• considering -moves as well
92
C 1 E
A B 1
0 G H
I J
D F
0
0
FGABCDHI
ABCDHI 0 1
1
1 EJGABCDHI
93
• An NFA may be in many states at any time
94
• A DFA can be implemented by a 2D table T
– One dimension is “states”
– Other dimension is “input symbols”
– For every transition Si a Sk define T[i,a] = k
• DFA “execution”
– If in state Si and input a, read T[i,a] = k and skip to state Sk
– Very efficient
95
0
0
T
S 0 1
1
1 U
0 1
S T U
T T U
U T U
96
• NFA -> DFA conversion is at the heart of tools such as flex or
jflex
97
Bottom up parsing handle pruning LR Grammar Parsing, LALR Parsing,
Parsing ambiguous grammars, YACC Programming specification.
S aBc
B bc | b
S S
input: abc
a B c a B
c fails, backtrack
b c b
a grammar a grammar suitable
for predictive
eliminate left parsing (a LL(1)
grammar)
left recursion factor no %100 guarantee.
current token
stmt if ...... |
while ...... |
begin ...... |
for .....
1. Shift : The next input symbol is shifted onto the top of the stack.
2. Reduce: Replace the handle on the top of the stack by the non-
terminal.
3. Accept: Successful completion of parsing.
4. Error: Parser discovers a syntax error, and calls an error recovery
routine.
CFG
LR
LALR
SLR
2. LR-Parsers
– covers wide range of grammars.
• SLR – simple LR parser
• LR – most general LR parser
• LALR – intermediate LR parser (lookhead LR parser)
• Operator grammar
– small, but an important class of grammars
– we may have an efficient operator precedence parser (a shift-
reduce parser) for an operator grammar.
• In an operator grammar, no production rule can
have:
– at the right side
– two adjacent non-terminals at the right side.
• Ex:
EAB EEOE EE+E |
Aa Eid E*E |
Bb O+|*|/
E/E | id
not operator grammar not operator grammar operator
grammar
• In operator-precedence parsing, we define three disjoint
precedence relations between certain pairs of terminals.
e
table for this grammar + <. .> <. .>
Algorithm:
set p to point to the first symbol of w$ ;
repeat forever
if ( $ is on top of the stack and p points to $ ) then
return
else {
let a be the topmost terminal symbol on the stack and
let b be the symbol pointed to by p;
if ( a <. b or a =· b ) then { /* SHIFT */
push b onto the stack;
advance p to the next input symbol;
}else if ( a .> b ) then /* REDUCE */
repeat pop stack
until ( the top of stack terminal is related
by <. to the terminal most recently popped );
else error();
}
stack input action
$ id+id*id$ $ <. id shift
$id +id*id$ id .> + reduce E id
$ +id*id$ shift
$+ id*id$ shift
$+id *id$ id .> * reduce E id
$+ *id$ shift
$+* id$ shift
$+*id $ id .> $ reduce E id
$+* $ * .> $ reduce E E*E
$+ $ + .> $ reduce E E+E
$ $ accept
•We use associativity and precedence relations among operators. 1.
If operator O1 has higher precedence than operator O2,
2. O1 .> O2 and O2 <. O1
If operator O1 and operator O2 have equal precedence,
they are left-associative O1 .> O2 and O2 .> O1
they are right-associative O1 <. O2 and O2 <. O1
3. For all operators O,
O <. id, id .> O, O <. (, (<. O, O .> ), ) .> O, O .> $, and $ <.
O
4. Also, let
(=·) $ <. ( id .> ) ) .> $
( <. ( $ <. id id .> $ ) .> )
( <. id
+ - * / ^ id ( ) $
+ .> .> <. <. <. <. <. .> .>
• Advantages:
– simple
– powerful enough for expressions in programming languages
Error Cases:
1. No relation holds between the terminal on the top of
stack and the next input symbol.
2. A handle is found (reduction step), but there is no
production with this handle as a right side
Error Recovery:
1. Each empty entry is filled with a pointer to an error
routine.
2. Decides the popped handle “looks like” which right hand
side. And tries to recover from that situation.
• The most powerful shift-reduce parsing (yet
efficient) is:
LR(k) parsing.
.
Action Table Goto Table
.
terminals and $ non-terminal
S1 s s
t four different t each item is
X1 a actions a a state number
S0 t t
e e
s s
• A configuration of a LR parsing is:
E’ E
E E+T
kernel items
{ E’ E.
ET E .E+T
T T*F E .T
TF T .T*F
F (E) T .F
F id F .(E)
F .id }
• If I is a set of LR(0) items and X is a grammar
symbol (terminal or non-terminal), then
goto(I,X) is defined as follows:
– If A .X in I
then every item in closure({A X.}) will be in
goto(I,X).
Example:
I ={ E’ .E, E .E+T, E .T,
T .T*F, T .F,
F .(E), F .id }
goto(I,E) = { E’ E., E E.+T }
goto(I,T) = { E T., T T.*F }
goto(I,F) = {T F. }
goto(I,() = { F (.E), E .E+T, E .T, T
• To create the SLR parsing tables for a
grammar G, we will create the canonical
LR(0) collection of the grammar G’.
• Algorithm:
C is { closure({S’.S}) }
repeat the followings until no more set of LR(0) items can
be added to C.
for each I in C and each grammar symbol X
if goto(I,X) is not empty and not in C
add goto(I,X) to C
I0: E’ .E I1: E’ E. I6: E E+.T I9: E
E+T.
E .E+T E E.+T T .T*F
T T.*F
E .T T .F
T .T*F I2: E T. F .(E)
I10: T T*F.
T .F T T.*F F .id
F .(E)
I8: F (E.)
T .T*F E E.+T
T .F
F .(E)
F .id
I5: F id.
I0 E I1 + I6 T I9 * to I7
F to I3
( to I4
T
id to I5
I2 I7
* I10
F
I3 F to I4
to I5
(
I4 I8
to I2 id I
( 11
I5 to I3 to I6
E
to I4 )
id id T
F +
(
1. Construct the canonical collection of sets of
LR(0) items for G’. C{I0,...,In}
Problem
FOLLOW(A)={a,b}
FOLLOW(B)={a,b}
a reduce by A b reduce by A
reduce by B reduce by B
reduce/reduce conflict reduce/reduce conflict
• In SLR method, the state i makes a reduction
by A when the current token is a:
– if the A. in the Ii and a is FOLLOW(A)
can be written as
A .,a1/a2/.../an
S
S AaAb I0: S’ .S ,$ AI1: S’ S. ,$
a to I4
S BbBa S .AaAb ,$
A B
S .BbBa ,$ I2: S A.aAb ,$
b to I5
B A . ,a
B . ,b I3: S B.bBa ,$
A a
I9:S L=R.,$
R
I6:S L=.R,$ to I9 I13:L *R.,$
R .L,$
L I10:R L.,$
to I 10
L .*R,$ *
R I4 and I11
to I11 I11:L *.R,$
L .id,$ to I13
id R .L,$ L
to I12 to I 10 I5 and I12
L .*R,$
I7:L *R.,$/= *
L .id,$ to I11 I7 and I13
id
I8: R L.,$/= to I12
I12:L id.,$ I8 and I 10
1. Construct the canonical collection of sets of
LR(1) items for G’. C{I0,...,In}
.
I1 : A ,a .
I2: A ,b
B .,b B .,c
reduce/reduce conflict
.
I12: A ,a/b
.
B ,b/c
S’ S .
I0:S’ S,$ .
I1:S’ S ,$ .
I411:L * R,$/= R
1) S L=R .
S L=R,$ S *
.. .
R L,$/=
to I 713
2) S R .
S R,$ L I2:S L =R,$ to I6 .
L *R,$/=
L
to I810
3) L *R
.L
*R,$/= R
R L ,$ .
L id,$/=
*
id
to I411
4) L id to I512
5) R L .
L id,$/=
I3:S
.
i
I512:L
.
.
R L,$
R ,$ d
id ,$/=
.
I6:S L= R,$
R I9:S L=R ,$.
..
R L,$
L *R,$
L
*
to I9
to I 810
Same Cores
I4 and I 11
.
L id,$
id
to I411
to I512
I5 and I12
I7 and I13
I713:L
*R.
I : ,$/=
810 R I8 and I 10
.
L ,$/=
id * = $ S L R
0 s5 s4 1 2 3
1 acc
2 s6 r5
3 r2
4 s5 s4 8 7
5 r4 r4 no shift/reduce or
6 s12 s11 10 9 no reduce/reduce conflict
7
8
r3
r5
r3
r5
so, it is a LALR(1) rammar
9 r1 g
• All grammars used in the construction of LR-parsing tables must be un-
ambiguous.
• Can we create LR-parsing tables for ambiguous grammars ?
– Yes, but they will have conflicts.
– We can resolve these conflicts in favor of one of them to
disambiguate the grammar.
– At the end, we will have again an unambiguous grammar.
• Why we want to use an ambiguous grammar?
– Some of the ambiguous grammars are much natural, and a
corresponding unambiguous grammar can be very complex.
– Usage of an ambiguous grammar may eliminate unnecessary
reductions.
• Ex.
E E+T | T
E E+E | E*E | (E) | id T T*F | F
F (E) | id
I0: E’
E
..E+E
E E I1: E’ E ..
E E +E
+
. .
I4: E E + E
E E+E (
E
. .
I7: E E+E + I4
E E +E *
E .E*E E E *E. .
E E*E I2 .
E E *E
I5
E
E
..(E)id * ..
E (E)
E id
id
I3
(
(
.
I5: E E * E
. E
.
I8: E E*E + I4
I2: E ( ..E+EE) E E+E
.
E E*E id
(
I2 ..
E E +E *
E E *E I5
E
E .E*E ..
E (E) I3
E ..(E)id E E id
id E id
..
I6: E (E ) ) .
I9: E (E)
E E +E +
I : E id.
3 .
E E *E * I4
I5
State I7 has shift/reduce conflicts for symbols + and *.
I0 E I1 + I4 E I7
I0 E I1 * I5 E I7
proc A {
- match the current token with a, and move to the
next token;
- call ‘B’;
- match the current token with b, and move to the
next token;
}
A aBb | bAB
proc A {
case of the current token {
‘a’: - match the current token with a, and move to
the next token;
- call ‘B’;
- match the current token with b, and move to
the next token;
‘b’: - match the current token with b, and move to
the next token;
- call ‘A’;
- call ‘B’;
}
}
• When to apply -productions.
A aA | bB |
input buffer
stack Non-recursive
output
Predictive Parser
Parsing Table
input buffer
– our string to be parsed. We will assume that its end is marked with
a special symbol $.
output
– a production rule representing a step of the derivation sequence
(left-most derivation) of the string in the input buffer.
stack
– contains the grammar symbols
– at the bottom of the stack, there is a special end marker symbol $.
– initially the stack contains only the symbol $ and the starting symbol
S. $S initial stack
– when the stack is emptied (ie. only $ left in the stack), the parsing is
completed.
parsing table
– a two-dimensional array M[A,a]
– each row is a non-terminal symbol
– each column is a terminal symbol or the special symbol $
– each entry holds a production rule.
• The symbol at the top of the stack (say X) and
the current symbol in the input string (say a)
determine the parser action.
• There are four possible parser actions.
Derivation(left-most): SaBaabBaabbBaabba
S
parse tree
a B a
b B
b B
E TE’
E’ +TE’ |
T FT’
T ’ *FT ’ |
F (E) | id
id + * ( ) $
E E E TE’
TE’
E’ E’ +TE’ E’ E’
T T T FT’
FT’
T’ T’ T’ *FT’ T’ T’
F F id F (E)
stack input
$E id+id$ E TE’
$E’T id+id$ T FT’
$E’ T’F id+id$ F id
$ E’ T’id id+id$
$ E’ T’ +id$ T’
$ E’ +id$ E’ +TE’
$ E’ T+ +id$
$ E’ T id$ T FT’
$ E’ T’ F id$ F id
$ E’ T’id id$
$ E’ T’ $ T’
$ E’ $ E’
$ $ accept
• Two functions are used in the construction of
LL(1) parsing tables:
– FIRST FOLLOW
• If ( A B is a production rule ) or
( A B is a production rule and is in
FIRST() ) everything in
FOLLOW(A) is in FOLLOW(B).
E TE’
E’ +TE’ |
T FT’
T’ *FT’ |
F (E) | id
FOLLOW(E) = { $, ) }
FOLLOW(E’) = { $, ) }
FOLLOW(T) = { +, ), $ }
’
• for each production rule A of a grammar
G
– for each terminal a in FIRST()
add A to M[A,a]
– If in FIRST()
for each terminal a in FOLLOW(A) add A to
M[A,a]
– If in FIRST() and $ in FOLLOW(A)
add A to M[A,$]
E’ FIRST()={} none
but since in FIRST()
and FOLLOW(E’)={$,)} E’ into M[E’,$] and M[E’,)]
1. Top-Down Parser
– the parse tree is created top to bottom, starting from the
root.
2. Bottom-Up Parser
– the parse is created bottom to top; starting from the
leaves
• We will see that the top-down parsers try to find the left-most
derivation of the given source program.
• We will see that the bottom-up parsers try to find the right-most
derivation of the given source program in the reverse order.
• Inner nodes of a parse tree are non-terminal symbols.
• The leaves of a parse tree are terminal symbols.
E -E E
-(E) E -(E+E)
E
- E - E
- E
( E ) ( E )
E E E + E
- E - E
- -(id+id)
(id+E) ( E ) ( E )
E + E E + E
id id id
• A grammar produces more than one parse tree for a sentence is
called as an ambiguous grammar.
E
E E+E id+E id+E*E
E + E
id+id*E id+id*id
id E * E
id id
E
E E*E E+E*E id+E*E
id+id*E id+id*id *
E E
E + E id
id
id
• For the most parsers, the grammar must be unambiguous.
• unambiguous grammar
unique selection of the parse tree for a sentence
stmt stmt
E2 S1 E2 S1 S2
1 2
• We prefer the second parse tree (else matches with closest if).
• So, we have to disambiguate our grammar to reflect this choice.
In general,
E T E’
E’ +T E’ |
T F T’
T’ *F T’ |
F id | (E)
• A grammar cannot be immediately left-recursive, but it still can be
left-recursive.
• By just eliminating the immediate left-recursion, we may not get
a grammar which is not left-recursive.
S Aa | b
A Sc | d This grammar is not immediately left-recursive,
but it is still left-recursive.
S Aa Sca or
A Sc Aac causes to a left-recursion
- Order of non-terminals: A, S
for A:
- we do not enter the inner loop.
- Eliminate the immediate left-recursion in A
A SdA’ | fA’
A’ cA’ |
for S:
- Replace S Aa with S SdA’a | fA’a
So, we will have S SdA’a | fA’a | b
- Eliminate the immediate left-recursion in S
S fA’aS’ | bS ’
S’ dA’aS’ |
So, the resulting equivalent grammar which is not
left-recursive is:
S fA’aS’ | bS’
S’ dA’aS’ |’
A SdA | fA
’
A’ cA’ |
• A predictive parser (a top-down parser without
backtracking) insists that the grammar must be left-
factored.
convert it into
A A’ | 1 | ... | m
A’ 1 | ... | n
Left-Factoring – Example1
A ad | a | ab | abc | b
A aA’ | b
A’ d | | b | bc
A aA’ | b
A’ d | | bA’’
A’’ | c
• Non-Recursive predictive parsing is a table-driven parser.
• It is a top-down parser.
• It is also known as LL(1) Parser.
input buffer
stack Non-recursive
output
Predictive Parser
Parsing Table
input buffer
– our string to be parsed. We will assume that its end is marked with a
special symbol $.
output
– a production rule representing a step of the derivation sequence
(left-most derivation) of the string in the input buffer.
stack
– contains the grammar symbols
– at the bottom of the stack, there is a special end marker symbol $.
– initially the stack contains only the symbol $ and the starting symbol
S. $S initial stack
– when the stack is emptied (ie. only $ left in the stack), the
parsing is completed.
parsing table
– a two-dimensional array M[A,a]
– each row is a non-terminal symbol
– each column is a terminal symbol or the special symbol $
– each entry holds a production rule.
• The symbol at the top of the stack (say X) and the
current symbol in the input string (say a) determine
the parser action.
• There are four possible parser actions.
Derivation(left-most): SaBaabBaabbBaabba
S
parse tree
a B a
b B
b B
E TE’
E’ +TE’ |
T FT’
T’ *FT’ |
F (E) | id
id + * ( ) $
E E E TE’
TE’
E’ E’ +TE’ E’ E’
T T T FT’
FT’
T’ T’ T’ T’ T’
*FT’
stack input output
$E id+id$ E TE’
$E’T id+id$ T FT’
$E’ T ’F id+id$ F id
$ E’ T ’id id+id$
$ E’ T ’ +id$ T’
$ E’ +id$ E’ +TE’
$ E’ T+ +id$
$ E’ T id$ T FT’
$ E’ T’ F id$ F id
$ E’ T’id id$
$ E’ T’ $ T’
$ E’ $ E’
$ $ accept
• Two functions are used in the construction of LL(1) parsing tables:
– FIRST FOLLOW
• If ( A B is a production rule ) or
( A B is a production rule and is in FIRST() )
everything in FOLLOW(A) is in FOLLOW(B).
FOLLOW(E) = { $, ) }
FOLLOW(E’) = { $, ) }
FOLLOW(T) = { +, ), $ }
FOLLOW(T’) = { +, ), $ }
FOLLOW(F) = {+, *, ), $ }
• for each production rule A of a grammar G
– for each terminal a in FIRST()
add A to M[A,a]
– If in FIRST()
for each terminal a in FOLLOW(A) add A to M[A,a]
– If in FIRST() and $ in FOLLOW(A)
add A to M[A,$]
277
a := b + c* 100
The seven tokens are grouped into a parse tree
Assignment stmt
identifier expression
:=
a expression expression
+
identifier
c*100
b
278
list list + digit (2.2
Given the grammar:
list list - digit )
list digit (2.3) (2.4
digit 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 )
(2.5
)
What is the parse list
tree for 9-5+2?
list digit
list digit
digit
9 - 5 + 2
279
The AST is a condensed/simplified/abstract form of
the parse tree in which:
1. Operators are directly associated with interior nodes
(non-terminals)
2. Chains of single productions are collapsed.
3. Terminals that have no attributes are ignored, i.e.,
the corresponding leaf nodes are discarded.
280
list
list digit +
list digit
- 2
digit
9 - 5 + 2 9 5
281
• Convenient representation for semantic analysis and
intermediate-language (IL) generation
• Useful for building other programming language tools e.t., a
syntax-directed editor
282
Syntax-directed translation is a method of translating a string into a
sequence of actions by attaching on such action to each rule of a grammar.
283
A. Syntax-Directed Definitions:
• give high-level specifications for translations
• hide many implementation details such as order of evaluation of
semantic actions.
• We associate a production rule with a set of semantic actions, and we do
not say when they will be evaluated.
B. Translation Schemes:
• Indicate the order of evaluation of semantic actions associated with
a production rule.
• In other words, translation schemes give a little bit information about
implementation details.
284
term ::= ID
{ term.place := ID.place ; term.code = “” }
285
A bottom-up parser generator
It provides semantic stack manipulation and supports
specification of semantic routines.
Developed by Steve Johnson and others at AT&T Bell Lab.
Can use scanner generated by Lex or hand-coded scanner in C
Used by many compilers and tools, including production
compilers.
286
• Grammar symbols are associated with attributes to associate
information with the programming language constructs that
they represent.
• Values of these attributes are evaluated by the semantic rules
associated with the production rules.
• Evaluation of these semantic rules:
– may generate intermediate codes
– may put information into the symbol table
– may perform type checking
– may issue error messages
– may perform some other activities
– in fact, they may perform almost any activities.
• An attribute may hold almost any thing.
– a string, a number, a memory location, a complex record.
287
• When we associate semantic rules with productions, we use
two notations:
– Syntax-Directed Definitions
– Translation Schemes
• Syntax-Directed Definitions:
– give high-level specifications for translations
– hide many implementation details such as order of
evaluation of semantic actions.
– We associate a production rule with a set of semantic
actions, and we do not say when they will be evaluated.
288
• Translation Schemes:
– indicate the order of evaluation of semantic actions
associated with a production rule.
– In other words, translation schemes give a little bit
information about implementation details.
289
• A syntax-directed definition is a generalization of a context-
free grammar in which:
– Each grammar symbol is associated with a set of attributes.
– This set of attributes for a grammar symbol is partitioned
into two subsets called synthesized and inherited attributes
of that grammar symbol.
– Each production rule is associated with a set of semantic
rules.
• Semantic rules set up dependencies between attributes which
can be represented by a dependency graph.
• This dependency graph determines the evaluation order of
these semantic rules.
• Evaluation of a semantic rule defines the value of an attribute.
But a semantic rule may also have some side effects such as290
• A parse tree showing the values of attributes at each node
is called an annotated parse tree.
• The process of computing the attributes values at the nodes
is called annotating (or decorating) of the parse tree.
• Of course, the order of these computations depends on the
dependency graph induced by the semantic rules.
291
• In a syntax-directed definition, each production A→α is
associated with a set of semantic rules of the form:
b=f(c1,c2,…,cn) where f is a function,
and b can be one of the followings:
b is a synthesized attribute of A and c1,c2,…,cn are
attributes of the grammar symbols in the production (
A→α ).
OR
b is an inherited attribute one of the grammar symbols
in α (on the
right side of the production), and c1,c2,…,cn are
attributes of the grammar symbols in the production (
A→α ).
292
• So, a semantic rule b=f(c1,c2,…,cn) indicates that the
attribute b depends on attributes c1,c2,…,cn.
• In a syntax-directed definition, a semantic rule may just
evaluate a value of an attribute or it may have some
side effects such as printing values.
293
Production
L → E return Semantic Rules
E → E1 + T print(E.val)
E →T E.val = E1.val + T.val
T → T1 * F E.val = T.val
T→F T.val = T1.val * F.val
F→ ( E) T.val = F.val
F → digit F.val = E.val
F.val = digit.lexval
• Symbols E, T, and F are associated with a synthesized
attribute val.
• The token digit has a synthesized attribute lexval (it is
assumed that it is evaluated by the lexical analyzer).
294
L
Input: 5+3*4
E.val=17 return
E.val=5 + T.val=12
digit.lexval=5 digit.lexval=3
295
L
Input: 5+3*4
E.val=17
E.val=5 T.val=12
digit.lexval=5 digit.lexval=3
296
Production Semantic Rules
E → E1 + T E.loc=newtemp(), E.code = E1.code ||
T.code
|| add E1.loc,T.loc,E.loc
297
• Symbols E, T, and F are associated with synthesized
attributes loc and code.
• The token id has a synthesized attribute name (it is
assumed that it is evaluated by the lexical analyzer).
• It is assumed that || is the string concatenation operator.
298
Production Semantic Rules
D → TL L.in = T.type
T → int T.type = integer
T → real T.type = real
L → L1 id L1.in = L.in, addtype(id.entry,L.in)
L → id addtype(id.entry,L.in)
299
Input: real p q
D L.in=real
T L T.type=real L1.in=real
addtype(q,real)
id id.entry=p
301
• To implement S-Attributed Definitions and L-Attributed
Definitions are easy (we can evaluate semantic rules in a
single pass during the parsing).
• Implementations of S-attributed Definitions are a little bit
easier than implementations of L-Attributed Definitions
302
• We put the values of the synthesized attributes of the
grammar symbols into a parallel stack.
– When an entry of the parser stack holds a grammar
symbol X (terminal or non-terminal), the
corresponding entry in the parallel stack will hold the
synthesized attribute(s) of the symbol X.
303
A XYZ A.a=f(X.x,Y.y,Z.z) where all attributes are
synthesized.
stack parallel-stack
top
Z Z.z
Y Y.y
top
X X.x A A.a
. . . .
304
Production Semantic Rules
L → E return print(val[top-1])
E → E1 + T val[ntop] = val[top-2] + val[top]
E →T
T → T1 * F val[ntop] = val[top-2] * val[top]
T →F
F → ( E) val[ntop] = val[top-1]
F → digit
305
..
I0: L’→ L
L I1: L’→L . I7: L →Er . . *
L→
..
Er
E+T E ..
r
. T .
I 11: E →E+T
T →T *F
9
E→
E→
..T
I2: L →E r
E →E +T
+
..
I8: E →E+ T
T → T*F (
F 4
5
T→
T→
F→ ..
T*F
F
(E)
T I3: E →T
T →T *F
.. ..
T→ F
F → (E)
F→ d
d
6
F→ d
F I4: T →F . *
.
( . ..
I9: T →T* F F
I12: T →T*F .
I5: F →
E→ .. ( E)
E+T E
F → (E)
F→ d (
5
E→
T→ ..T
T*F
..
d 6
T→
F→ .. F
(E)
T
3
I10:F →(E )
E →E +T
)
+
.
I13: F →(E)
F→ d F
( 4 8
d
I6: F →d . d
5
6
306
0E2+8F4 5-3 *4r T→F T.val=F.val – do nothing
308
• Remember that: In a recursive predicate parser, each non-
terminal corresponds to a procedure.
procedure A() {
call B(); A→
B
}
procedure B() {
if (currtoken=0) { consume 0; call B(); } B→
0B
else if (currtoken=1) { consume 1; call B(); } B→
1B
else if (currtoken=$) {} // $ is end-marker B →
else error(“unexpected token”);
}
309
procedure A() {
int n0,n1; Synthesized attributes of non-terminal B
call B(&n0,&n1); are the output parameters of procedure B.
print(n0); print(n1);
} All the semantic rules can be
evaluated
procedure B(int *n0, int *n1) { at the end of parsing of production
rules
if (currtoken=0)
{int a,b; consume 0; call B(&a,&b); *n0=a+1; *n1=b;}
else if (currtoken=1)
{ int a,b; consume 1; call B(&a,&b); *n0=a; *n1=b+1; }
else if (currtoken=$) {*n0=0; *n1=0; } // $ is end-marker
else error(“unexpected token”);
}
310
• S-Attributed Definitions can be efficiently implemented.
• We are looking for a larger (larger than S-Attributed
Definitions) subset of syntax-directed definitions which can
be efficiently evaluated.
L-Attributed Definitions
311
• A syntax-directed definition is L-attributed if each
inherited attribute of Xj, where 1jn, on the right side of
A → X1X2...Xn depends only on:
1. The attributes of the symbols X1,...,Xj-1 to the left of Xj
in the production and
2. the inherited attribute of A
312
Productions Semantic Rules
A → LM L.in=l(A.i), M.in=m(L.s), A.s=f(M.s)
A → QR R.in=r(A.in), Q.in=q(R.s), A.s=f(Q.s)
• This syntax-directed definition is not L-attributed because
the semantic rule Q.in=q(R.s) violates the restrictions of
L-attributed definitions.
• When Q.in must be evaluated before we enter to Q because
it is an inherited attribute.
• But the value of Q.in depends on R.s which will be
available after we return from R. So, we are not be able to
evaluate the value of Q.in before we enter to Q.
313
• In a syntax-directed definition, we do not say anything
about the evaluation times of the semantic rules (when the
semantic rules associated with a production should be
evaluated?).
315
• If our syntax-directed definition is S-attributed, the
construction of the corresponding translation scheme will be
simple.
• Each associated semantic rule in a S-attributed syntax-
directed definition will be inserted as a semantic action into
the end of the right side of the associated production.
E → TR
R → + T { print(“+”) } R1
R→
T → id { print(id.name) }
a+b+c ab+c+
317
E
T R
id {print(“a”)} + T {print(“+”)} R
id {print(“b”)} + T {print(“+”)}
R
id {print(“c”)}
320
D → T id { addtype(id.entry,T.type), L.in = T.type } L
321
procedure D() {
int Ttype,Lin,identry;
call T(&Ttype); consume(id,&identry);
addtype(identry,Ttype); Lin=Ttype;
call L(Lin); a synthesized attribute
(an output parameter)
}
procedure T(int *Ttype) {
if (currtoken is int) { consume(int); *Ttype=TYPEINT; }
else if (currtoken is real) { consume(real);
*Ttype=TYPEREAL; }
else { error(“unexpected type”); } an inherited attribute
(an input parameter)
}
322
procedure L(int Lin) {
if (currtoken is id) { int L1in,identry;
consume(id,&identry);
addtype(identry,Lin); L1in=Lin; call
L(L1in); }
else if (currtoken is endmarker) { }
else { error(“unexpected token”); }
}
323
E → T { A.in=T.loc } A { E.loc=A.loc }
A → + T { A1.in=newtemp(); emit(add,A.in,T.loc,A1.in)
}
A1 { A.loc = A1.loc}
A→ { A.loc = A.in }
T → F { B.in=F.loc } B { T.loc=B.loc }
B → * F { B1.in=newtemp(); emit(mult,B.in,F.loc,B1.in) }
B1 { B.loc = B1.loc}
B→ { B.loc = B.in }
F → ( E ) { F.loc = E.loc }
F → id { F.loc = id.name }
324
procedure E(char **Eloc) {
char *Ain, *Tloc, *Aloc;
call T(&Tloc); Ain=Tloc;
call A(Ain,&Aloc); *Eloc=Aloc;
}
procedure A(char *Ain, char **Aloc) {
if (currtok is +) {
char *A1in, *Tloc, *A1loc;
consume(+); call T(&Tloc); A1in=newtemp();
emit(“add”,Ain,Tloc,A1in);
call A(A1in,&A1loc); *Aloc=A1loc;
}
else { *Aloc = Ain }
} 325
procedure T(char **Tloc) {
char *Bin, *Floc, *Bloc;
call F(&Floc); Bin=Floc;
call B(Bin,&Bloc); *Tloc=Bloc;
}
procedure B(char *Bin, char **Bloc) {
if (currtok is *) {
char *B1in, *Floc, *B1loc;
consume(+); call F(&Floc); B1in=newtemp();
emit(“mult”,Bin,Floc,B1in);
call B(B1in,&B1loc); Bloc=B1loc;
}
326
else { *Bloc = Bin }
}
procedure F(char **Floc) {
if (currtok is “(“) { char *Eloc; consume(“(“);
call E(&Eloc); consume(“)”); *Floc=Eloc }
else { char *idname; consume(id,&idname);
*Floc=idname }
}
327
• Using a top-down translation scheme, we can
implement any L-attributed definition based
on a LL(1) grammar.
• Using a bottom-up translation scheme, we can also
implement any L-attributed definition based on a
LL(1) grammar (each LL(1) grammar is also an LR(1)
grammar).
• In addition to the L-attributed definitions based on
LL(1) grammars, we can implement some of L-
attributed definitions based on LR(1) grammars (not
all of them) using the bottom-up translation scheme.
328
• In bottom-up evaluation scheme, the
semantic actions are evaluated during the
reductions.
• During the bottom-up evaluation of S-
attributed definitions, we have a parallel
stack to hold synthesized attributes.
• Problem: where are we going to hold
inherited attributes?
• A Solution:
329
– We will convert our grammar to an equivalent
grammar to guarantee to the followings.
– All embedding semantic actions in our translation
scheme will be moved into the end of the production
rules.
– All inherited attributes will be copied into the
synthesized attributes (most of the time synthesized
attributes of new non-terminals).
– Thus we will be evaluate all semantic actions during
reductions, and we find a place to store an inherited
attribute.
330
• To transform our translation scheme into an equivalent
translation scheme:
1. Remove an embedding semantic action Si, put new a non-
terminal Mi instead of that semantic action.
2. Put that semantic action Si into the end of a new
production rule Mi for that non-terminal Mi.
3. That semantic action Si will be evaluated when this new
production rule is reduced.
4. The evaluation order of the semantic rules are not
changed by this transformation.
331
A {S1} X1 {S2} X2 ... {Sn} Xn
A M1 X1 M2 X2 ... Mn Xn
M1 {S1}
M2 {S2}
.
.
Mn {Sn}
332
E → TR
R → + T { print(“+”) } R1
R→
T → id { print(id.name) }
E → TR
R → + T M R1
R→
T → id { print(id.name) }
M → { print(“+”) }
333
• Let us assume that every non-terminal A has an inherited
attribute A.i, and every symbol X has a synthesized
attribute X.s in our grammar.
• For every production rule A X1 X2 ... Xn ,
– introduce new marker non-terminals M1,M2,...,Mn and
– replace this production rule with A M1 X1 M2 X2 ...
Mn Xn
– the synthesized attribute of Xi will be not changed.
– the inherited attribute of Xi will be copied into the
synthesized attribute of Mi by the new semantic action
added at the end of the new production rule Mi.
– Now, the inherited attribute of Xi can be found in the
synthesized attribute of Mi (which is immediately
available in the stack.
334
S {A.i=1} A {S.s=k(A.i,A.s)}
A {B.i=f(A.i)} B {C.i=g(A.i,B.i,B.s)} C {A.s=
h(A.i,B.i,B.s,C.i,C.s)}
B b {B.s=m(B.i,b.s)}
C c {C.s=n(C.i,c.s)}
S {M1.i=1} M1 {A.i=M1.s} A {S.s=k(M1.s,A.s)}
A {M2.i=f(A.i)} M2 {B.i=M2.s} B
{M3.i=g(A.i,M2.s,B.s)} M3 {C.i=M3.s} C {A.s= h(A.i,
M2.s,B.s, M3.s,C.s)}
B b {B.s=m(B.i,b.s)}
C c {C.s=n(C.i,c.s)}
M1 {M1.s=M1.i}
M2 {M2.s=M2.i}
M3 {M3.s=M3.i}
335
S {M1.i=1} M1 {A.i=M1.s} A{S.s=k(M1.s,A.s)}
A {M2.i=f(A.i)} M2 {B.i=M2.s} B {M3.i=g(A.i,M2.s,B.s)} M3
{C.i=M3.s} C {A.s= h(A.i, M2.s,B.s, M3.s,C.s)}
B b {B.s=m(B.i,b.s)}
C c {C.s=n(C.i,c.s)}
M1 {M1.s= M1.i}
M2 {M2.s=M2.i}
M3 {M3.s=M3.i}
336
S M1 A { s[ntop]=k(s[top-1],s[top]) }
M1 { s[ntop]=1 }
A M2 B M3 C { s[ntop]=h(s[top-4],s[top-3],s[top-2],
s[top-1],s[top]) }
M2 { s[ntop]=f(s[top]) }
M3 { s[ntop]=g(s[top-2],s[top-1],s[top])}
B b { s[ntop]=m(s[top-1],s[top]) }
C c { s[ntop]=n(s[top-1],s[top]) } 337
S
S.s=k(1,h(..))
A.i=1
A
A.s=h(1,f(1),m(..),g(..),n(..))
B.i=f(1) C.i=g(1,f(1),m(..))
B C
B.s=m(f(1),b.s) C.s=n(g(..),c.s)
b c
338
stack input s-attribute stack
bc$
M1 bc$ 1
M1 M2 bc$ 1 f(1)
M1 M2 b c$ 1 f(1) b.s
M1 M2 B c$ 1 f(1) m(f(1),b.s)
M1 M2 B M3 c$ 1 f(1) m(f(1),b.s) g(1,f(1),m(f(1),b.s))
M1 M2 B M3 c $ 1 f(1) m(f(1),b.s) g(1,f(1),m(f(1),b.s)) c.s
M1 M2 B M3 C $ 1 f(1) m(f(1),b.s) g(1,f(1),m(f(1),b.s))
n(g(..),c.s)
M1 A $ 1 h(f(1),m(..),g(..),n(..))
S $ k(1,h(..))
339
• All L-attributed definitions based on LR grammars cannot be evaluated
during bottom-up parsing.
S M1 L
L M2 L1 1 But since L will be reduced first by the
bottom-up
L { print(s[top]) } parser, the translator cannot know the number of
1s.
M1 { s[ntop]=0 }
M2 { s[ntop]=s[top]+1 }
340
• The modified grammar cannot be LR grammar anymore.
L Lb L M Lb
L a L a NOT LR-grammar
M
S’ .L, $
L . M L b, $
L . a, $
M .,a shift/reduce conflict
341
• Intermediate codes are machine independent codes, but
they are close to machine instructions.
342
– postfix notation can be used as an intermediate language.
– three-address code (Quadraples) can be used as an
intermediate language
• we will use quadraples to discuss intermediate code
generation
• quadraples are close to machine instructions, but they are
not actual machine instructions.
– some programming languages have well defined intermediate
languages.
• java – java virtual machine
• prolog – warren abstract machine
• In fact, there are byte-code emulators to execute
instructions in these intermediate languages.
343
• A quadraple is:
x := y op z
where x, y and z are names, constants or compiler-generated
temporaries; op is any operator.
344
Binary Operator: op y,z,result or result := y op z
where op is a binary arithmetic or logical operator. This binary
operator is applied to y and z, and the result of the operation is
stored in result.
Ex: add a,b,c
gt a,b,c
addr a,b,c
addi a,b,c
345
Move Operator: mov y,,result or result := y
where the content of y is copied into result.
Ex: mov a,,c
movi a,,c
movr a,,c
346
Conditional Jumps: jmprelop y,z,L or if y
relop z goto L
We will jump to the three-address code with the label L if the result of y
relop z is true, and the execution continues from that statement. If the
result is false, the execution continues from the statement following this
conditional jump statement.
347
Our relational operator can also be a unary operator.
jmpnz y,,L1 // jump to L1 if y is not zero
jmpz y,,L1 // jump to L1 if y is zero
jmpt y,,L1 // jump to L1 if y is true
jmpf y,,L1 // jump to L1 if y is false
348
Procedure Parameters: param x,, or param x
Procedure Calls: call p,n, or call p,n
where x is an actual parameter, we invoke the procedure p
with n parameters.
Ex: param x1,,
param x2,,
p(x1,...,xn)
param xn,,
call p,n,
f(x+1,y) add x,1,t1
param t1,,
param y,,
349
call f,2,
Indexed Assignments:
move y[i],,x or x := y[i]
move x,,y[i] or y[i] := x
350
S id := E S.code = E.code || gen(‘mov’ E.place ‘,,’ id.place)
E E1 + E2 E.place = newtemp();
E.code = E1.code || E2.code || gen(‘add’ E1.place ‘,’ E2.place ‘,’
E.place)
E E1 * E2 E.place = newtemp();
E.code = E1.code || E2.code || gen(‘mult’ E1.place ‘,’ E2.place ‘,’
E.place)
E - E1 E.place = newtemp();
E.code = E1.code || gen(‘uminus’ E1.place ‘,,’ E.place)
E ( E1 ) E.place = E1.place;
E.code = E1.code
E id E.place = id.place;
E.code = null
351
S while E do S1 S.begin = newlabel();
S.after = newlabel();
S.code = gen(S.begin “:”) || E.code ||
gen(‘jmpf’ E.place ‘,,’ S.after) || S1.code ||
gen(‘jmp’ ‘,,’ S.begin) ||
gen(S.after ‘:”)
S if E then S1 else S2 S.else = newlabel();
S.after = newlabel();
S.code = E.code ||
gen(‘jmpf’ E.place ‘,,’ S.else) || S1.code ||
gen(‘jmp’ ‘,,’ S.after) ||
gen(S.else ‘:”) || S2.code ||
gen(S.after ‘:”)
352
S id := E
{ p= lookup(id.name);
if (p is not nil) then emit(‘mov’ E.place ‘,,’ p)
E E1 + E2 else error(“undefined-variable”) }
{ E.place = newtemp();
E E1 * E2 emit(‘add’ E1.place ‘,’ E2.place ‘,’ E.place) }
{ E.place = newtemp();
E - E1 { E.place
=emit(‘mult’
newtemp();E1.place ‘,’ E2.place ‘,’ E.place) }
emit(‘uminus’ E1.place ‘,,’ E.place) }
E ( E1 ) { E.place = E1.place; }
E id { p= lookup(id.name);
if (p is not nil) then E.place = id.place
else error(“undefined-variable”) }
353
S id := { E.inloc = S.inloc } E
{ p = lookup(id.name);
if (p is not nil) then { emit(E.outloc ‘mov’ E.place ‘,,’ p);
S.outloc=E.outloc+1 }
else { error(“undefined-variable”); S.outloc=E.outloc
}}
355
E { E1.inloc = E.inloc } E1 and { E2.inloc = E1.outloc } E2
{ E.place = newtemp(); emit(E2.outloc ‘and’ E1.place ‘,’
E2.place ‘,’ E.place); E.outloc=E2.outloc+1 }
E { E1.inloc = E.inloc } E1 or { E2.inloc = E1.outloc } E2
{ E.place = newtemp(); emit(E2.outloc ‘and’ E1.place ‘,’
E2.place ‘,’ E.place); E.outloc=E2.outloc+1 }
E not { E1.inloc = E.inloc } E1
{ E.place = newtemp(); emit(E1.outloc ‘not’ E1.place ‘,,’
E.place); E.outloc=E1.outloc+1 }
E { E1.inloc = E.inloc } E1 relop { E2.inloc = E1.outloc } E2
{ E.place = newtemp();
emit(E2.outloc relop.code E1.place ‘,’ E2.place ‘,’
E.place); E.outloc=E2.outloc+1 }
356
S while { E.inloc = S.inloc } E do
{ emit(E.outloc ‘jmpf’ E.place ‘,,’ ‘NOTKNOWN’);
S1.inloc=E.outloc+1; } S1
{ emit(S1.outloc ‘jmp’ ‘,,’ S.inloc);
S.outloc=S1.outloc+1;
backpatch(E.outloc,S.outloc); }
357
S if { E.inloc = S.inloc } E then
{ emit(E.outloc ‘jmpf’ E.place ‘,,’‘NOTKNOWN’);
S1.inloc=E.outloc+1; } S1 else
{ emit(S1.outloc ‘jmp’ ‘,,’ ‘NOTKNOWN’);
S2.inloc=S1.outloc+1;
backpatch(E.outloc,S2.inloc); } S2
{ S.outloc=S2.outloc;
backpatch(S1.outloc,S.outloc); }
358
x:=1; 01: mov 1,,x
y:=x+10; 02: add x,10,t1
while (x<y) { 03: mov t1,,y
x:=x+1; 04: lt x,y,t2
if (x%2==1) then y:=y+1; 05: jmpf t2,,17
else y:=y-2; 06: add x,1,t3
} 07: mov t3,,x
359
08: mod x,2,t4
09: eq t4,1,t5
10: jmpf t5,,14
11: add y,1,t6
12: mov t6,,y
13: jmp ,,16
14: sub y,2,t7
15: mov t7,,y
16: jmp ,,4
17:
360
• Elements of arrays can be accessed quickly if the elements
are stored in a block of consecutive locations.
A one-dimensional array A:
… …
363
• The location of A[i1,i2] is
baseA+ ((i1-low1)*n2+i2-low2)*width
baseA is the location of the array A.
low1 is the index of the first row
low2 is the index of the first column
n2 is the number of elements in each row
width is the width of each array element
• Again, this formula can be re-written as
((i1*n2)+i2)*width + (baseA-((low1*n1)+low2)*width)
L id | id [ Elist ]
Elist Elist , E | E
L id | Elist ]
Elist Elist , E | id [ E
366
S L := E { if (L.offset is null) emit(‘mov’ E.place ‘,,’ L.place)
else emit(‘mov’ E.place ‘,,’ L.place ‘[‘ L.offset ‘]’) }
E E1 + E2 { E.place = newtemp();
emit(‘add’ E1.place ‘,’ E2.place ‘,’ E.place) }
E ( E1 ) { E.place = E1.place; }
367
L id { L.place = id.place; L.offset = null; }
L Elist ]
{ L.place = newtemp(); L.offset = newtemp();
emit(‘mov’ c(Elist.array) ‘,,’ L.place);
emit(‘mult’ Elist.place ‘,’ width(Elist.array) ‘,’L.offset)
}
Elist Elist1 , E
{ Elist.array = Elist1.array ; Elist.place = newtemp();
Elist.ndim = Elist1.ndim + 1;
emit(‘mult’ Elist1.place ‘,’ limit(Elist.array,Elist.ndim)
‘,’ Elist.place);
emit(‘add’ Elist.place ‘,’ E.place ‘,’Elist.place); }
Elist id [ E
{Elist.array = id.place ; Elist.place = E.place; Elist.ndim
= 1; }
368
• A one-dimensional double array A : 5..100
n1=95 width=8 (double) low1=5
369
• A two-dimensional int array A : 1..10x1..20
n1=10 n2=20 width=4 (integers) low1=1 low2=1
mult y,20,t1
add t1,z,t1
mov c,,t2 // where c=baseA-
(1*20+1)*4
mult t1,4,t3
mov t2[t3],,t4
mov t4,,x
370
• A three-dimensional int array A : 0..9x0..19x0..29
n1=10 n2=20 n3=30 width=4 (integers) low1=0 low2=0
low3=0
371
P MD
M€ { offset=0 }
D D ;D
D id : T { enter(id.name,T.type,offset);
offset=offset+T.width }
T int { T.type=int; T.width=4 }
T real { T.type=real; T.width=8 }
T array[num] of T1 { T.type=array(num.val,T1.type);
T.width=num.val*T1.width }
T ↑ T1 { T.type=pointer(T1.type); T.width=4 }
373
addwidth(symtable,width) – puts the total width of all entries
in the symbol table into the header of that table.
374
P M D { addwidth(top(tblptr),top(offset)); pop(tblptr);
pop(offset) }
N € { t=mktable(top(tblptr)); push(t,tblptr);
push(0,offset) }
375
376
• Translating source program into an “intermediate
language.”
– Simple
– CPU Independent,
– …yet, close in spirit to machine language.
• Or, depending on the application other intermediate
languages may be used, but in general, we opt for simple,
well structured intermediate forms.
• (and this completes the “Front-End” of Compilation).
Benefits
1. Retargeting is facilitated
2. Machine independent Code Optimization can be
applied. 377
Intermediate codes are machine independent codes, but they are close to
machine instructions.
The given program in a source language is converted to an equivalent program
in an intermediate language by the intermediate code generator.
Intermediate language can be many different languages, and the designer of
the compiler decides this intermediate language.
378
• Graphical Representations.
– Consider the assignment a:=b*-c+b*-c:
assign
assign
+
a + a
*
* *
b uminus uminus
uminus b
c c
b c
379
PRODUCTION Semantic Rule
S id := E { S.nptr = mknode (‘assign’, mkleaf(id, id.entry),
E.nptr) }
E E1 + E2
E E1 * E2 {E.nptr = mknode(‘+’, E1.nptr,E2.nptr) }
E - E1 {E.nptr = mknode(‘*’, E1.nptr,E2.nptr) }
E ( E1 ) {E.nptr = mknode(‘uminus’,E1.nptr) }
E id {E.nptr = E1.nptr }
380
• Statements of general form x:=y op z
• As a result, x:=y + z * w
should be represented as
t1:=z * w
t2:=y + t1
x:=t2
381
• Observe that given the syntax-tree or the dag of the
graphical representation we can easily derive a three
address code for assignments as above.
382
t1:= - c t1:=- c
t2:= b * t1 t2:= b *
t3:= - c t1
t4:= b * t3 t5:= t2 +
t5:= 2 + t4 t2
a:= t5 a:= t5
383
Assignment Statement: x:=y op z
Assignment Statement: x:=op z
Copy Statement: x:=z
Unconditional Jump: goto L
Conditional Jump: if x relop y goto L
Stack Operations: Push/pop
More Advanced:
Procedure:
param x1
param x2
…
param xn
call p,n
384
Index Assignments:
x:=y[i]
x[i]:=y
Address and Pointer Assignments:
x:=&y
x:=*y
*x:=y
385
• First deal with assignments.
• Use attributes
– E.place: the name that will hold the value of E
• Identifier will be assumed to already have the place
attribute defined.
– E.code:hold the three address code statements that
evaluate E (this is the `translation’ attribute).
• Use function newtemp that returns a new temporary
variable that we can use.
• Use function gen to generate a single three address
statement given the necessary information (variable names
and operations).
386
PRODUCTION Semantic Rule
S id := E { S.code = E.code||gen(id.place ‘=’ E.place ‘;’) }
E E1 + E2 {E.place= newtemp ;
E.code = E1.code || E2.code ||
|| gen(E.place‘:=’E1.place‘+’E2.place) }
E E1 * E2 {E.place= newtemp ;
E.code = E1.code || E2.code ||
|| gen(E.place‘=’E1.place‘*’E2.place) }
E - E1 {E.place= newtemp ;
E.code = E1.code ||
|| gen(E.place ‘=’ ‘uminus’ E1.place) }
E ( E1 ) {E.place= E1.place ; E.code = E1.code}
E id {E.place = id.entry ; E.code = ‘’ }
388
S.code= gen(S.begin ‘:’)
|| E.code
|| gen(‘if’ E.place ‘=’ ‘0’ ‘goto’ S.after)
|| S1.code
|| gen(‘goto’ S.begin)
|| gen(S.after ‘:’)
389
• Quadruples
op arg1 arg2 result
t1:=- c
t2:=b * t1 (0) uminu c t1
t3:=- c s
t4:=b * t3
t5:=t2 + t4 (1) * b t1 t2
a:=t5 uminu c
(2)
s
(3) * b t3 t4
(4) + t2 t4 t5
(5) := t5 a
Temporary names must be entered into the symbol table as they are created.
390
op arg1 arg2
• Triples (0) uminu c
t1:=- c s
t2:=b * t1 (1) * b (0)
t3:=- c uminu c
(2)
t4:=b * t3 s
t5:=t2 + t4 (3) * b (2)
a:=t5
(4) + (1) (3)
(5) assign a (4)
Temporary names are not entered into the symbol table.
391
• e.g. ternary operations like
x[i]:=y x:=y[i]
• require two or more entries. e.g.
op arg1 arg2
(0) []= x i
(1) assign (0) y
op arg1 arg2
(0) [ ]= y i
(1) assign x (0)
392
• Indirect Triples
op op arg1 arg2
393
P procedure id ‘;’ block ‘;’
Semantic Rule
begin = newlabel;
Enter into symbol-table in the entry of the procedure name
the begin label.
P.code = gen(begin ‘:’) || block.code ||
gen(‘pop’ return_address) || gen(“goto return_address”)
S call id
Semantic Rule
Look up symbol table to find procedure name. Find its begin
label called proc_begin
return = newlabel;
S.code = gen(‘push’return); gen(goto proc_begin) || 394
Using a global variable offset
395
• For each procedure we should create a symbol table.
mktable(previous) – create a new symbol table where
previous is the parent symbol table of this new symbol
table
enter(symtable,name,type,offset) – create a new entry for a
variable in the given symbol table.
enterproc(symtable,name,newsymbtable) – create a new
entry for the procedure in the symbol table of its parent.
addwidth(symtable,width) – puts the total width of all entries
in the symbol table into the header of that table.
• We will have two stacks:
– tblptr – to hold the pointers to the symbol tables
– offset – to hold the current offsets in the symbol tables
in tblptr stack. 396
Consider the grammar fraction:
P D
D D ; D | id : T | proc id ; D ; S
397
(a translation scheme)
P MD { addwidth(top(tblptr), top(offset));
pop(tblptr); pop(offset) }
M { t:=mktable(null); push(t, tblptr);
push(0,
offset)}
D D1 ; D2 ...
D proc id ; N D ; S { t:=top(tblpr);
addwidth(t,top(offset));
pop(tblptr); pop(offset);
enterproc(top(tblptr), id.name, t)}
N {t:=mktable(top(tblptr)); push(t,tblptr);
398
D id : T {enter(top(tblptr), id.name, T.type, top(offset);
top(offset):=top(offset) + T.width
399
Type Checking
. 400
Abstract Decorated
Syntax Abstract
Tree Syntax Tree
Token
Parser Static Intermediate
Intermedi
Strea Checke Code
ate Code
m r Generator
401
• Problem: Verify that a type of a construct matches that
expected by its context.
• Examples:
– mod requires integer operands (PASCAL)
– * (dereferencing) – applied to a pointer
– a[i] – indexing applied to an array
– f(a1, a2, …, an) – function applied to correct arguments.
• Information gathered by a type checker:
– Needed during code generation.
402
• A collection of rules for assigning type expressions to the
various parts of a program.
Type Constructors
Basic Types
Arrays
Boolean Character
Records
Real Integer
Sets
Enumera Sub-ranges Pointers
tions Functions
Void Error
404
Variables Names
cell = record
-> ->
x
x pointr x pointr x x
struct cell {
Tree DAG
int info;
struct cell * next;
(char x char)-> pointer (integer) };
405
Type -> int | float | char | …
| void
| error
| name Basic Types
| variable
| array( size, Type)
| record( (name, Type)*)
| pointer( Type)
Structured
| tuple((Type)*)
Types
| arrow(Type, Type)
406
Program -> Declaration; Statement
Declaration -> Declaration; Declaration
| id: Type
Statement -> Statement; Statement
| id := Expression
| if Expression then Statement
| while Expression do Statement
Expression -> literal | num | id
| Expression mod Expression
| E[E] | E ↑ | E (E)
407
E -> int_const { E.type = int }
E -> float_const { E.type = float }
E -> id { E.type = sym_lookup(id.entry, type) }
E -> E1 + E2 {E.type = if E1.type {int, float} | E2.type
{int, float}
then error
else if E1.type == E2.type == int
then int
else float }
408
E -> E1 [E2] {E.type = if E1.type = array(S, T) &
E2.type = int then T else error}
E -> *E1 {E.type = if E1.type = pointer(T) then T else error}
E -> &E1 {E.type = pointer(E1.tye)}
E -> E1 (E2) {E.type = if (E1.type = arrow(S, T) &
E2.type = S, then T else err}
E -> (E1, E2) {E.type = tuple(E1.type, E2.type)}
409
S -> id := E {S.type := if id.type = E.type then void else error}
410
Problem: When in E1.type = E2.type?
– We need a precise definition for type equivalence
– Interaction between type equivalence and type
representation
412
function stequiv(s, t): boolean
{
if (s & t are of the same basic type) return true;
if (s = array(s1, s2) & t = array(t1, t2))
return equal(s1, t1) & stequiv(s2, t2);
if (s = tuple(s1, s2) & t = tuple(t1, t2))
return stequiv(s1, t1) & stequiv(s2, t2);
if (s = arrow(s1, s2) & t = arrow(t1, t2))
return stequiv(s1, t1) & stequiv(s2, t2);
if (s = pointer(s1) & t = pointer(t1))
return stequiv(s1, t1);
}
413
Where: Linked Lists, Trees, etc.
How: records containing pointers to similar records
Example: type link = ↑ cell;
cell = record info: int; next = link end
Representation:
x x
x x x x
• Example:
– struct cell {int info; struct cell * next;}
415
• Overloaded Symbol: one that has different meanings
depending on its context
416
function “*” (i, j: integer) return complex;
function “*” (x, y: complex) return complex;
* Has the following types:
arrow(tuple(integer, integer), integer)
arrow(tuple(integer, integer), complex)
arrow(tuple(complex, complex), complex)
int i, j;
k = i * j;
417
E’ -> E {E’.types = E. types
E.unique = if E’.types = {t} then t else
error}
E -> id {E.types = lookup(id.entry)}
E -> E1(E2) {E.types = {s’ | s E2.types and S->s’
E1.types}
t = E.unique
S = {s | s E2.types and S->t E1.types}
E2.unique = if S ={s} the S else error
E1.unique = if S = {s} the S->t else error
418
• Defn: a piece of code (functions, operators) that can be
executed with arguments of different types.
• Example HL:
fun length(lptr) = if null (lptr) then 0
else length(+l(lptr)) + 1
419
P -> D ; E
D -> D ; D | id : Q
Q -> α. Q | T
T -> arrow (T, T) | tuple (T, T)
| unary (T) | (T)
| basic
|α
E -> E (E) | E, E | id
420
• Why: variables representing type expressions allow us to
talk about unknown types.
– Use Greek alphabets α, β, γ …
• Application: check consistent usage of identifiers in a
language that does not require identifiers to be declared
before usage.
– A type variable represents the type of an undeclared
identifier.
• Type Inference Problem: Determine the type of a language
constant from the way it is used.
– We have to deal with expressions containing variables.
421
Type link ↑ cell;
Procedure mlist (lptr: link; procedure p);
{ while lptr <> null { p(lptr); lptr := lptr ↑ .next} }
Hence: p: link -> void
Function deref (p){ return p ↑; }
P: β, β = pointer(α)
Hence deref: α. pointer(α) -> α
422
apply: α0
deref: α. pointer(α) -> α
q: pointer (pointer (integer))
deref (deref( (q)) deref0: pointer (α0 ) -> α0 apply: αi
423
• Distinct occurrences of a p.f. in the same expression need
not have arguments of the same type.
– deref ( deref (q))
– Replace α with fresh variable and remove (αi, αo)
424
• Substitution: a mapping from type variables to type expressions.
Function subst (t: type Expr): type Expr { S
if (t is a basic type) return t;
if (t is a basic variable) return S(t); --identify if t S
if (t is t1 -> t2) return subst(t1) -> subst (t2); }
425
E -> E1 (E2) { p := mkleaf(newtypevar); unify
(E1.type, mknode(‘-
>’, E2.type,p);
E.type = p}
E -> E1, E2 {E.type := mknode(‘x’, E1.type, E2.type); }
E -> id { E.type := fresh (id.type) }
426
Given: derefo (derefi (q)) q = pointer (pointer (int))
Bottom Up: fresh (α. Pointer(α) -> α)
-> : 3
pointer :
derefo derefi q 2
α :1
-> : 3 -> : 6 pointer :
9
pointer : pointer : pointer :
2 5 8
αo : 1 αi : 4 integer : 7
n-> : 6
-> : 3 m-> : 6
pointer : β:8
pointer : pointer : 5
2 5 pointer :
αo : 1 αi : 4 8 427
integer : 7
SYMBOL TABLE
Def : Symbol table is a data structure used by compiler to keep
track of semantics of variable .
L-value and r – value : the l and r prefixes come from left and right
side assignment .
Ex:
a := I + 1
l-value r-value
• Variable names
• Constants
• Procedure names
• Function names
• Literal constants and strings
• Compiler generated temporaries
• Labels in source languages
Compiler uses following types of information from symbol table
1.Data type
2.Name
3.Declaring procedures
4.Offset in storage
5.If structure are record then pointer to structure variable .
6.For parameters, whether parameter passing is
by value or reference .
7.Number and type of arguments passed to the function .
8.Base address .
• There are two types of name representation .
• Fixed length names :
• A fixed space for each name is allocated in symbol table .
name attribute
C A L C U L A T E
S U m
b
• Variable length
name
Starting length attribute
index
0 10
10 4
14 2
16 2
• Data structure for symbol table :
1 . Linear list
2 . Arrays
The pointer variable is maintained at the end of all stored records .
Name 1 Info 1
Name2 Info 2
Name 3 Info 3
. .
. .
. .
Name n Info n
Available
(start of empty
slot
• Self organization list :
• This symbol table implementation is using linked list .
• We search the records in the order pointed by the link of link
field .
Name 1 Info 1
Name 2 Info 2
first
Name 3 Info 3
Name 4 Info 4
• Ex:
• Int m,n,p;
• Int compute(int a,int b,intc)
• {
• T=a+b*c;
• Return(t);
• }
• Main()
• {
• Int k;
• K=compute(10,20,30)
• }
• Binary tree structure organization
k int
a int m int
n int
b int
c int p int
j
avg
avg
. .
. .
• Hash tables :
• The advantages of hash table is quick search is possible .
• The disadvantage is that hashing is complicated to implement .
Some extra space is required . Obtaining scope of variables is
very difficult .
• HEAPALLOCATION:
Return value
Actual parameters
Control link(dynamic link)
Access link (static link)
Saved machine status
Local variables
Temporaries
• Temporary values : These values are needed during the evaluation of
expressions .
• Local variables : the local data is a data that is local to the execution of
procedure is stored in this field of activation record .
• Saved machine registers : the status of machine just before the
procedure is called .this field contains the machine registers and
program counter .
• Control link : this field is optional .it points the activation record of the
calling procedure . This link is also called dynamic link .
• Access link : this field is optional . It refers the non local data in other
activation record . This field is also called static link field .
• Actual parameters : this contains the information about the actual
parameters .
• Return values : this field is used to store the result of a function call .
Control link Act record for A
Pointer to x .
Pointer to y
Array x
Array y
Array of A
Control link
Act record for B
Top_sp
Base_ptr
Return value
Act rec for proc. Dynamic link
A
Saved registers offset
parameters
Locals : a Access to local data
top
Act rec for
proc.A
Return value Base_ptr
Dynamic link
Saved registers
Act rec for Offset
parameters
proc.B
Local : b Access to local data
top
access
Used by block Used by non block
Handling non local data
structured stuctured languages
languages
Static scope or
lexical scope Dynamic scope
a: a: i,b:
Loop, proc
Profile calls, addr Reg usage,
calculation instruction
and choice,
optimize improvem peephole opt
(user) ent (compiler)
(compiler)
• Organization of an optimizing compiler
Control
Data flow
flow Transformation
analysis
analysis
Code optimizer
Peephole optimization
Local Optimization
Global Optimization
Inter-procedural
Intra-procedural
Loop Optimization
• The target machine: machine dependent factors can be
parameterized to compiler for fine tuning
• Machine Architecture
– Cache Size and type
– Cache/Memory transfer rate
• Avoid redundancy: something already computed need
not be computed again
• Smaller code: less work for CPU, cache, and memory!
• Less jumps: jumps interfere with code pre-fetch
• Code locality: codes executed close together in time is
generated close together in memory – increase locality of
reference
• Extract more information about code: More info –
better code generation
• Redundancy elimination = determining that two computations
are equivalent and eliminating one.
– Value numbering
• Associates symbolic values to computations and
identifies expressions that have the same value
• Two ways:
– Constant folding
– Constant propagation
• Constant folding: Evaluation of an expression with constant
operands to replace the expression with single value
• Example:
area := (22.0/7.0) * r ** 2
area := 3.14286 * r ** 2
• Constant Propagation: Replace a variable with constant
which has been assigned to it earlier.
• Example:
pi := 3.14286
area = pi * r ** 2
area = 3.14286 * r ** 2
• What does it mean?
– Given an assignment x = c, where c is a constant, replace
later uses of x with uses of c, provided there are no
intervening assignments to x.
• Similar to copy propagation
• Extra feature: It can analyze constant-value
conditionals to determine whether a branch should
be executed or not.
• When is it performed?
– Early in the optimization process.
• What is the result?
– Smaller code
– Fewer registers
• Identify common sub-expression present in different
expression, compute once, and use the result in all the
places.
– The definition of the variables involved should not
change
Example:
a := b * c temp := b * c
… a := temp
… …
x := b * c + 5 x := temp + 5
• Local common subexpression elimination
– Performed within basic blocks
– Algorithm sketch:
• Traverse BB from top to bottom
• Maintain table of expressions evaluated so far
– if any operand of the expression is redefined,
remove it from the table
• Modify applicable instructions as you go
– generate temporary variable, store the expression
in it and use the variable next time the expression
is encountered.
X=a+b T=a+b
…… X=t
…… …..
Y=a+b Y=t
t1 = a + b
c=a+b c = t1
d = m *n t2 = m * n
e=b+d d = t2
f=a+b t3 = b + d
g = -b e = t3
h=b+a f = t1
a =j+a g = -b
k = m *n h = t1 /* commutative */
j=b+d a = j +a
a = -b k = t2
if m * n go to L j = t3
a = -b
if t2 go to L
Example:
if (a<b) then if (a<b) then
z = x *2 temp = x * 2
z = temp
else else
y = 10 y = 10
temp = x * 2
g = x *2 g = temp;
Code Motion
Example:
temp = 5;
for i=1 to 10 do for i=1 to 10 do
… …
x = i *5 x = temp
… …
temp = temp + 5
end end
• Typical cases of strength reduction occurs in address
calculation of array references.
• Applies to integer expressions involving induction variables
(loop optimization)
• Dead Code are portion of the program which will not be
executed in any path of the program.
– Can be removed
• Examples:
– No control flows into a basic block
– A variable is dead at a point -> its value is not used
anywhere in the program
– An assignment is dead -> assignment assigns a value to a
dead variable
• Examples:
•i=j;
•…
•X=i+10
•The optimization can be performed by
•Eliminating the assignment statement
•i=j
.
This assignment statement is called dead
assignment .
• What does it mean?
– Given an assignment x = y, replace later uses of x with
uses of y, provided there are no intervening assignments
to x or y.
• When is it performed?
– At any level, but usually early in the optimization
process.
Example:
x[i] = a; x[i] = a;
sum = x[i] + a; sum = a + a;
i := i + 1
ADD i, #1 INC i
Optimization of Basic Blocks
4 i
t1 := 4 * i
t3 := 4 * i
t2 := t1 + t3 if (i <= 20)goto L1
+ t2 <= (L1)
* t1, t3
i 20
4 i
• I/p: Basic block, B
• O/p: A DAG for B containing the following information:
1) A label for each node
2) For leaves the labels are ids or consts
3) For interior nodes the labels are operators
4) For each node a list of attached ids (possible empty list,
no consts)
• Data structure and functions:
– Node:
1) Label: label of the node
2) Left: pointer to the left child node
3) Right: pointer to the right child node
4) List: list of additional labels (empty for leaves)
– Node (id): returns the most recent node created for id.
Else return undef
– Create(id,l,r): create a node with label id with l as left
child and r as right child. l and r are optional params.
• Method:
For each 3AC, A in B
A if of the following forms:
1. x := y op z
2. x := op y
3. x := y
1. if ((ny = node(y)) == undef)
ny = Create (y);
if (A == type 1)
and ((nz = node(z)) == undef)
nz = Create(z);
2. If (A == type 1)
Find a node labelled ‘op’ with left and right as ny and nz
respectively [determination of common sub-
expression]
If (not found) n = Create (op, ny, nz);
If (A == type 2)
Find a node labelled ‘op’ with a single child as ny
If (not found) n = Create (op, ny);
If (A == type 3) n = Node (y);
3. Remove x from Node(x).list
Add x in n.list
Node(x) = n;
t1 := 4 * i
* t1
4 i
t1 := 4 * i
t2 := a [ t1 ]
[] t2
* t1
a 4 i
t1 := 4 * i
t2 := a [ t1]
t3 := 4 * i
[] t2
* t1, t3
a 4 i
t1 := 4 * i
t2 := a [ t1 ]
t3 := 4 * i
t4 := b [ t3 ]
t4 [] [] t2
* t1, t3
b a 4 i
t1 := 4 * i
t2 := a [ t1 ]
t3 := 4 * i + t5
t4 := b [ t3 ]
t5 := t2 + t4 t4 [] [] t2
* t1, t3
b a 4 i
t1 := 4 * i
t2 := a [ t1 ]
t3 := 4 * i + t5,i
t4 := b [ t3 ]
t5 := t2 + t4 t4 [] [] t2
i := t5
* t1, t3
b a 4 i
• Observations:
– A leaf node for the initial value of an id
– A node n for each statement s
– The children of node n are the last definition (prior to s)
of the operands of n
• Common sub-expression elimination: by construction of DAG
– Note: for common sub-expression elimination, we are
actually targeting for expressions that compute the same
value.
a := b +c
b := b –d Common expressions
But do not generate the
c := c +d
same result
e := b +c
• DAG representation identifies expressions that yield
the same result
+ e
a := b + c
b := b – d
c := c + d
+ a - b + c
e := b + c
b0 c0 d0
• Dead code elimination: Code generation from DAG
eliminates dead code.
c +
a := b + c
a := b + c
b := a – d × b,d -
d := a - d
d := a – d
c := d + c
c := d + c a +
d0
b is not live
b0 c0
• Most important set of optimizations
– Programs are likely to spend more time in loops
• Presumption: Loop has been identified
• Optimizations:
– Loop invariant code removal
– Induction variable strength reduction
– Induction variable reduction
• Dominators:
A node d of a flow graph G dominates a node n, if every
path in G from the initial node to n goes through d.
Corollaries:
Every node dominates itself.
The initial node dominates all nodes in G.
The entry node of a loop dominates all nodes in the loop.
• Each node n has a unique immediate dominator m, which is
the last dominator of n on any path in G from the initial
node to n.
(d ≠ n) && (d dom n) → d dom m
• Dominator tree (T):
A representation of dominator information of flow graph
G.
• The root node of T is the initial node of G
• A node d in T dominates all node in its sub-tree
1 1
2 3
2 3
4
4
5 6
5 6 7
7
8 9
8 9
• Loop fission: break a loop into multiple loops over the same
index range but each taking only a part of the loop's body.
r5 = r4 - 3 r5 = r4 - 3
r4 = r4 + 1 r4 = r4 + 1
new_reg += new_inc
r7 = r4 *r9
r7 = new_reg
r6 = r4 << 2 r6 = r4 << 2
• Remove unnecessary basic induction variables from the loop
by substituting uses with another basic induction variable.
• Rules:
– Find two basic induction variables, x and y
– x and y in the same family
• Incremented at the same place
– Increments are equal
– Initial values are equal
– x is not live at exit of loop
– For each BB where x is defined, there is no use of x
between the first and the last definition of y
r1 = 0 r2 = 0
r2 = 0
r1 = r1 - 1 r2 = r2 - 1
r2 = r2 -1
r9 = r2 + r4 r7 = r1 * r9 r9 = r2 + r4 r7 = r2 * r9
r4 = *(r1) r4 = *(r2)
*r2 = r7 *r7 = r2
• Variants:
1. Trivial: induction variable that are never used except to
Complexity of elimination
r4 := r2 + 8 r4 := r1 + rx
r3 := r1 + 4 r3 := r1 = 4
. .
. .
r1 := r1 + 4 r1 := r1 + 4
r2 := r2 + 4
• Replicate the body of a loop (N-1) times, resulting in total N
copies.
– Enable overlap of operations from different iterations
– Increase potential of instruction level parallelism (ILP)
• Variants:
– Unroll multiple of known trip counts
– Unroll with remainder loop
– While loop unroll
Optimization
c:= 1 + 3 c:= 4
555
• IMPORTANT!
– Data flow analysis should never tell us that a
transformation is safe when in fact it is not.
– When doing data flow analysis we must be
• Conservative
– Do not consider information that may not
preserve the behavior of the program
• Aggressive
– Try to collect information that is as exact as
possible, so we can get the greatest benefit from
our optimizations.
556
• Global:
– Performed on the flow graph
– Goal = to collect information at the beginning and end
of each basic block
• Iterative:
– Construct data flow equations that describe how
information flows through each basic block and solve
them by iteratively converging on a solution.
557
• Components of data flow equations
– Sets containing collected information
• in set: information coming into the BB from outside
(following flow of data)
• gen set: information generated/collected within the
BB
• kill set: information that, due to action within the BB,
will affect what has been collected outside the BB
• out set: information leaving the BB
– Functions (operations on these sets)
• Transfer functions describe how information changes
as it flows through a basic block
• Meet functions describe how information from
multiple paths is combined. 558
• Algorithm sketch
– Typically, a bit vector is used to store the information.
• For example, in reaching definitions, each bit
position corresponds to one definition.
– We use an iterative fixed-point algorithm.
– Depending on the nature of the problem we are solving,
we may need to traverse each basic block in a forward
(top-down) or backward direction.
• The order in which we "visit" each BB is not
important in terms of algorithm correctness, but is
important in terms of efficiency.
– In & Out sets should be initialized in a conservative and
aggressive way.
559
• Reaching definitions
– For each use of a variable, find all definitions that reach
it.
• Upward exposed uses
– For each definition of a variable, find all uses that it
reaches.
• Live variables
– For a point p and a variable v, determine whether v is
live at p.
• Available expressions
– Find all expressions whose value is available at some
point p.
560
• A typical data flow equation:
S: statement
out[S] gen[S] (in[S] kill[S])
in[S]: Information goes into S
kill[S]: Information get killed by S
gen[S]: New information generated by S
out[S]: Information goes out from S
561
• The notion of gen and kill depends on the desired
information.
• In some cases, in may be defined in terms of out - equation
is solved as analysis traverses in the backward direction.
• Data flow analysis follows control flow graph.
– Equations are set at the level of basic blocks, or even for
a statement
562
• Point within a basic block:
– A location between two consecutive statements.
– A location before the first statement of the basic block.
– A location after the last statement of the basic block.
• Path: A path from a point p1 to pn is a sequence of points
p1,
p2, … pn such that for each i : 1 ≤ i ≤ n,
– pi is a point immediately preceding a statement and pi+1 is
the point immediately following that statement in the
same block, or
– pi is the last point of some block and pi+1 is first point in
the successor block.
563
d1: i := m – 1 B1
d2: j := n
d3: a := u1
Path:
p3 d4: i := i + 1 B2 p1, p2, p3, p4,
p4 p5, p6 … p n
p5 d5: j := j - 1
p6
B3
B4
p1 pn
d6: a := u2 B5 B6
p2
564
• Definition of a variable x is a statement that assigns or may
assign a value to x.
– Unambiguous Definition: The statements that certainly
assigns a value to x
• Assignments to x
• Read a value from I/O device to x
– Ambiguous Definition: Statements that may assign a
value to x
• Call to a procedure with x as parameter (call by ref)
• Call to a procedure which can access x (x being in the
scope of the procedure)
• x is an alias for some other variable (aliasing)
• Assignment through a pointer that could refer x
565
• A definition d reaches a point p
– if there is a path from the point immediately following d
to p and
– d is not killed along the path (i.e. there is not redefinition
of the same variable in the path)
• A definition of a variable is killed between two points when
there is another definition of that variable along the path.
566
d1: i := m – 1
d2: j := n B1
d3: a := u1 Definition of i (d1)
reaches p1
p1 d4: i := i + 1 B2 Killed as d4, does not
p2 reach p2.
d6: a := u2 B5 B6
567
• Non-Conservative view: A definition might reach a point
even if it might not.
– Only unambiguous definition kills a earlier definition
– All edges of flow graph are assumed to be traversed.
if (a == b) then a = 2
else if (a == b) then a = 4
The definition “a=4” is not reachable.
568
• Structured programs have well defined loop constructs – the
resultant flow graph is always reducible.
– Without loss of generality we only consider while-do and
if-then-else control constructs
S → id := E│S ; S
│ if E then S else S │ do S while E
E → id + id │ id
The non-terminals represent regions.
569
• Region: A graph G’= (N’,E’) which is portion of the control
flow graph G.
– The set of nodes N’ is in G’ such that
• N’ includes a header h
• h dominates all node in N’
– The set of edges E’ is in G’ such that
• All edges a → b such that a,b are in N’
570
• Region consisting of a statement S:
– Control can flow to only one block outside the region
• Loop is a special case of a region that is strongly connected
and includes all its back edges.
• Dummy blocks with no statements are used as technical
convenience (indicated as open circles)
571
S1
S → S1 ; S 2
S2
572
if E goto S1
S → if E then S1 else S2
S1 S2
573
S1
S → do S1 while E
if E goto S1
574
• Each region (or NT) has four attributes:
– gen[S]: Set of definitions generated by the block S.
If a definition d is in gen[S], then d reaches the end
of block S.
– kill[S]: Set of definitions killed by block S.
If d is in kill[S], d never reaches the end of block S.
Every path from the beginning of S to the end S
must have a definition for a (where a is defined
by d).
575
– in[S]: The set of definition those are live at the entry
point of block S.
– out[S]: The set of definition those are live at the exit
point of block S.
• The data flow equations are inductive or syntax
directed.
– gen and kill are synthesized attributes.
– in is an inherited attribute.
576
• gen[S] concerns with a single basic block. It is the set of
definitions in S that reaches the end of S.
• In contrast out[S] is the set of definitions (possibly defined
in some other block) live at the end of S considering all
paths through S.
577
gen[S] {d}
kill[S] Da {d}
S d: a := b + c
578
gen[S ] gen[S2 ] (gen[S1 ] kill[S2 ])
kill[S ] kill[S2 ] (kill[S1 ] gen[S2 ])
S1
S
in[S1 ] in[S ]
in[S2 ] out[S1 ] S2
out[S ] out[S2 ]
579
gen[S] gen[S1 ] gen[S2 ]
kill[S] kill[S1 ] kill[S2 ]
S S1 S2
in[S1 ] in[S ]
in[S2 ] in[S ]
out[S ] out[S1 ] out[S2 ]
580
gen[S] gen[S1 ]
kill[S] kill[S1 ]
S S1
581
• The attributes are computed for each region. The equations
can be solved in two phases:
– gen and kill can be computed in a single pass of a basic
block.
– in and out are computed iteratively.
• Initial condition for in for the whole program is
• In can be computed top- down
• Finally out is computed
582
• Due to back edge, in[S] cannot be used as
in [S1]
• in[S1] and out[S1] are interdependent.
• The equation is solved iteratively.
• The general equations for in and out:
584
• How are the gen and kill sets defined?
– gen[B] = {definitions that appear in B and reach the
end of B}
– kill[B] = {all definitions that never reach the end of B}
• What is the direction of the analysis?
– forward
– out[B] = gen[B] (in[B] - kill[B])
585
• What is the confluence operator?
– union
– in[B] = out[P], over the predecessors P of B
• How do we initialize?
– start small
• Why? Because we want the resulting set to be as
small as possible
– for each block B initialize out[B] = gen[B]
586
for each basic block BB do
gen(BB) = ; kill(BB) = ;
for each statement (d: x := y op z) in sequential order in BB, do
kill(BB) = kill(BB) U G[x];
G[x] = d;
endfor
gen(BB) = U G[x]: for all id x
endfor
587
for all basic blocks BB in(BB) =
for all basic blocks BB out(BB) = gen(BB)
change = true
while (change) do
change = false
for each basic block BB, do
old_out = out(BB)
in(BB) = U(out(Y)) for all predecessors Y
of BB
out(BB) = gen(BB) + (in(BB) – kill(BB))
if (old_out != out(BB)) then change = true
endfor
endfor 588
• Liveness: For each point p in a program and each variable
y, determine whether y can be used before being redefined,
starting at p.
• Attributes
– use = set of variable used in the BB prior to its
definition
– def = set of variables defined in BB prior to any use of
the variable
– in = set of variables that are live at the entry point of a
BB
– out = set of variables that are live at the exit point of a
BB
589
• Data flow equations:
in[B] use[B] (out[B] def [B])
out[B] in[S]
S succ( B)
– 1st Equation: a var is live, coming in the block, if either
• it is used before redefinition in B
or
• it is live coming out of B and is not redefined in B
– 2nd Equation: a var is live coming out of B, iff it is live
coming in to one of its successors.
590
r2, r3, r4, r5 are all live as they
r1 = r2 + r3 are consumed later, r6 is dead
r6 = r4 – r5 as it is redefined later
r4 is dead, as it is redefined.
r4 = 4 So is r6. r2, r3, r5 are live
r6 = 8
r6 = r2 + r3
r7 = r4 – r5 What does this mean?
r6 = r4 – r5 is useless,
it produces a dead
value !!
591
Get rid of it!
for each basic block BB do
def(BB) = ; use(B B) = ;
for each statement (x := y op z) in sequential order, do
for each operand y, do
if (y not in def(BB))
use(BB) = use(BB) U {y};
endfor
def(BB) = def(BB) U {x};
endfor
def is the union of all the LHS’s
use is all the ids used before defined
592
for all basic blocks BB
in(BB) = ;
change = true;
while (change) do
change = false
for each basic block BB do
old_in = in(BB);
out(BB) = U{in(Y): for all successors Y of BB}
in(X) = use(X) U (out(X) – def(X))
if (old_in != in(X)) then change = true
endfor
endfor
593
• Convenient way to access/use reaching definition
information.
• Def-Use chains (DU chains)
– Given a def, what are all the possible consumers of the
definition produced
• Use-Def chains (UD chains)
– Given a use, what are all the possible producers of the
definition consumed
594
1: r1 = MEM[r2+0]
2: r2 = r2 + 1 DU Chain of r1:
3: r3 = r1 * r4 (1) -> 3,4
(4) ->5
DU Chain of r3:
(3) -> 11
4: r1 = r1 + 5 7: r7 = r6 (5) -> 11
5: r3 = r5 – r1 8: r2 = 0 (12) ->
6: r7 = r3 * 2 9: r7 = r7 + 1
UD Chain of r1:
10: r8 = r7 + 5 (12) -> 11
11: r1 = r3 – r8
12: r3 = r1 * 2 UD Chain of r7:
(10) -> 6,9 595
• Liveness and Reaching definitions are basically the same
thing!
– All dataflow is basically the same with a few parameters
• Meaning of gen/kill (use/def)
• Backward / Forward
• All paths / some paths (must/may)
– So far, we have looked at may analysis algorithms
– How do you adjust to do must algorithms?
• Dataflow can be slow
– How to implement it efficiently?
– How to represent the info?
596
• Transfer function
– How information is changed by BB
out[BB] = gen[BB] + (in[BB] – kill[BB]) forward
analysis
in[BB] = gen[BB] + (out[BB] – kill[BB]) backward
analysis
• Meet/Confluence function
– How information from multiple paths is combined
in[BB] = U out[P] : P is pred of BB forward analysis
out[BB] = U in[P] : P is succ of BB backward
analysis
597
change = true;
while (change)
change = false;
for each BB
apply meet function
apply transfer function
if any changes change = true;
598
for each basic block BB, do
gen[BB]
kill[BB]
endfor
kill[BB] kill[BB] {y}
endfor
599
endfor
• Upward exposed defs
– in = gen + (out – kill)
– out = U(in(succ)) • Downward exposed defs
– Walk ops reverse order – in = U(out(pred))
• gen += {dest} kill – out = gen + (in - kill)
+= {dest}
– Walk in forward order
• gen += {dest}; kill
• Downward exposed uses += {dest};
– in = U(out(pred))
– out = gen + (in - kill)
– Walk in forward order
• gen += {src}; kill -=
{src};
• gen -= {dest}; kill
+= {dest};
600
• Up to this point
– Any path problems (maybe relations)
• Definition reaches along some path
• Some sequence of branches in which def reaches
• Lots of defs of the same variable may reach a
point
– Use of Union operator in meet function
• All-path: Definition guaranteed to reach
– Regardless of sequence of branches taken, def
reaches
– Can always count on this
– Only 1 def can be guaranteed to reach
– Availability (as opposed to reaching)
• Available definitions
• Available expressions (could also have reaching
601
expressions, but not that useful)
1: r1 = r2 + r3 1,2 reach
2: r6 = r4 – r5 1,2 available
1,2 reach 3: r4 = 4
1,2 available 4: r6 = 8
1,3,4 reach
1,3,4 available
5: r6 = r2 + r3
6: r7 = r4 – r5 1,2,3,4 reach
1 available
602
• A definition d is available at a point p if along all paths
from d to p, d is not killed
• Remember, a definition of a variable is killed between 2
points when there is another definition of that variable
along the path
– r1 = r2 + r3 kills previous definitions of r1
• Algorithm:
– Forward dataflow analysis as propagation occurs from
defs downwards
– Use the Intersect function as the meet operator to
guarantee the all-path requirement
– gen/kill/in/out similar to reaching defs
• Initialization of in/out is the tricky part
603
for each basic block BB do
gen(BB) = ; kill(BB) = ;
for each statement(d: x := y opz) in sequential order in BB, do
kill(BB) = kill(BB) U G[x];
G[x] = d;
endfor
gen(BB) = U G[x]: for all id x
endfor
604
U = universal set of all definitions in the prog
in(0) = 0; out(0) = gen(0)
for each basic block BB, (BB != 0), do
in(BB) = 0; out(BB) = U – kill(BB)
change = true
while (change) do
change = false
for each basic block BB, do
old_out = out(BB)
in(BB) = out(Y) : for all predecessors Y of BB
out(BB) = GEN(X) + (IN(X) – KILL(X))
if (old_out != out(X)) then change = true
endfor
endfor
605
• An expression is a RHS of an operation
– Ex: in “r2 = r3 + r4” “r3 + r4” is an expression
• An expression e is available at a point p if along all paths
from e to p, e is not killed.
• An expression is killed between two points when one of its
source operands are redefined
– Ex: “r1 = r2 + r3” kills all expressions involving r1
• Algorithm:
– Forward dataflow analysis
– Use the Intersect function as the meet operator to
guarantee the all-path requirement
– Looks exactly like adefs, except gen/kill/in/out are the
RHS’s of operations rather than the LHS’s
606
• Input: A flow graph with e_kill[B] and e_gen[B]
• Output: in[B] and out[B]
• Method:
foreach basic block B
in[B1] := ; out[B1] := e_gen[B1];
608
• Requirements – Efficiency!
– Large amount of information to store
– Fast access/manipulation
• Bitvectors
– General strategy used by most compilers
– Bit positions represent defs (rdefs)
– Efficient set operations: union/intersect/isone
– Used for gen, kill, in, out for each BB
609
• Classes of optimization
1. Classical (machine independent)
• Reducing operation count (redundancy
elimination)
• Simplifying operations
2. Machine specific
• Peephole optimizations
• Take advantage of specialized hardware features
3. Instruction Level Parallelism (ILP) enhancing
• Increasing parallelism
• Possibly increase instructions
610
• Operation-level – One operation in isolation
– Constant folding, strength reduction
– Dead code elimination (global, but 1 op at a time)
• Local – Pairs of operations in same BB
– May or may not use dataflow analysis
• Global – Again pairs of operations
– Pairs of operations in different BBs
• Loop – Body of a loop
611
• Simplify operation based on values of target operand
– Constant propagation creates opportunities for this
• All constant operands
– Evaluate the op, replace with a move
• r1 = 3 * 4 r1 = 12
• r1 = 3 / 0 ??? Don’t evaluate excepting ops !,
what about FP?
– Evaluate conditional branch, replace with BRU or noop
• if (1 < 2) goto BB2 goto BB2
• if (1 > 2) goto BB2 convert to a noop Dead
code
• Algebraic identities
– r1 = r2 + 0, r2 – 0, r2 | 0, r2 ^ 0, r2 << 0, r2 >> 0 r1 =
r2
– r1 = 0 * r2, 0 / r2, 0 & r2 r1 = 0
– r1 = r2 * 1, r2 / 1 r1 = r2 612
• Replace expensive ops with cheaper ones
– Constant propagation creates opportunities for this
• Power of 2 constants
– Mult by power of 2: r1 = r2 * 8 r1 = r2 << 3
– Div by power of 2: r1 = r2 / 4 r1 = r2 >> 2
– Rem by power of 2: r1 = r2 % 16 r1 = r2 & 15
• More exotic
– Replace multiply by constant by sequence of shift and
adds/subs
• r1 = r2 * 6
– r100 = r2 << 2; r101 = r2 << 1; r1 = r100 + r101
• r1 = r2 * 7
– r100 = r2 << 3; r1 = r100 – r2
613
• Remove statement d: x := y op z whose result is never
consumed.
• Rules:
– DU chain for d is empty
– y and z are not live at d
614
• Forward propagation of moves/assignment of the
form d: rx := L where L is literal
615
• Forward propagation of RHS of assignment or mov’s.
r1 := r2 r1 := r2
. .
. .
. .
r4 := r1 + 1 r4 := r2 + 1
– Reduce chain of dependency
– Possibly create dead code
616
• Rules:
Statement dS is source of copy propagation
Statement dT is target of copy propagation
– dS is a mov statement
– src(dS) is a register
– dT uses dest(dS)
– dS is available definition at dT
– src(dS) is a available expression at dT
617
• Backward propagation of LHS of an assignment.
dT: r1 := r2 + r3 r4 := r2 + r3
r5 := r1 + r6 r5 := r4 + r6
dS: r4 := r1 Dead Code
• Rules:
– dT and dS are in the same basic block
– dest(dT) is register
– dest(dT) is not live in out[B]
– dest(dS) is a register
– dS uses dest(dT)
– dest(dS) not used between dT and dS
– dest(dS) not defined between d1 and dS
– There is no use of dest(dT) after the first definition of
dest(dS)
618
• Benefits:
– Reduced computation
– Generates mov statements,
which can get copy dS: r1 := r2 + r3
propagated
• Rules: dT: r4 := r2 + r3
– dS and dT has the same
expression
– src(dS) == src(dT) for all dS: r1 := r2 + r3
sources r100 := r1
– For all sources x, x is not
redefined between dS and dT
dT: r4 := r100
619
• Rules:
– dS and dT has the same expression
– src(dS) == src(dT) for all sources of dS and dT
– Expression of dS is available at dT
620
Mark initial BB visited entry
to_visit = initial BB
while (to_visit not empty) bb1 bb2
current = to_visit.pop()
for each successor block of current bb3 bb4
Mark successor as visited;
to_visit += successor
bb5
endfor
endwhile Which BB(s) can be deleted?
Eliminate all unvisited blocks
621
Code generation: Machine dependent code generation, object code
forms, generic code generation algorithm, register allocation and
assignment using DAG representation of Block
• The final phase in our 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 requirements traditionally imposed on a code generator
are severe. The output code must be correct and of high
quality, meaning that it should make effective use of the
resources of the target machine. Moreover, the code
generator itself should run efficiently.
While the details are dependent on the target language and
the operating system, issues such as memory management,
instruction selection, register allocation, and evaluation order
are inherent in almost all code generation problems .
• The input to the code generator consists of the
intermediate representation of the source program
produced by the front end, together with information in the
symbol table that is used to determine the run time
addresses of the data objects denoted by the names in the
intermediate representation.
• There are several choices for the intermediate
language, including: linear representations such as postfix
notation, three address representations such as quadruples,
virtual machine representations such as syntax trees and
dags.
• We assume that prior to code generation the front end has
scanned, parsed, and translated the source program into a
reasonably detailed intermediate representation, so the
values of names appearing in the intermediate language can
be represented by quantities that the target machine can
directly manipulate (bits, integers, reals, pointers, etc.). We
also assume that the necessary type checking has take place,
so type conversion operators have been inserted wherever
necessary and obvious semantic errors (e.g., attempting to
index an array by a floating point number) have already
been detected. The code generation phase can therefore
proceed on the assumption that its input is free of errors. In
some compilers, this kind of semantic checking is done
together with code generation .
• The output of the code generator is the target program. The
output may take on a variety of forms: absolute machine
language, relocatable machine language, or assembly
language.
• Producing an absolute machine language
program as output has the advantage that it can be placed in
a location in memory and immediately executed. A small
program can be compiled and executed quickly. A number of
“student-job” compilers, such as WATFIV and PL/C, produce
absolute code.
• Producing a relocatable machine language program as
output allows subprograms to be compiled
separately. A set of relocatable object modules can be
linked together and loaded for execution by a linking
loader. Although we must pay the added expense of
linking and loading if we produce relocatable object
modules, we gain a great deal of flexibility in being
able to compile subroutines separately and to call
other previously compiled programs from an object
module. If the target machine does not handle
relocation automatically, the compiler must provide
explicit relocation information to the loader to link
the separately compiled program segments
• Producing an assembly language program as output makes
the process of code generation somewhat easier .We can
generate symbolic instructions and use the macro facilities
of the assembler to help generate code .The price paid is the
assembly step after code generation.
• Because producing assembly code does not duplicate the
entire task of the assembler, this choice is another
reasonable alternative, especially for a machine with a small
memory, where a compiler must uses several passes.
Mapping names in the source program to addresses of
data
objects in run time memory is done cooperatively by the
front end and the code generator. We assume that a name
in a three-address statement refers to a symbol table
entry for the name.
If machine code is being generated, labels in three address
statements have to be converted to addresses of
instructions. This process is analogous to the “back
patching”. Suppose that labels refer to quadruple numbers
in a quadruple array. As we scan each quadruple in turn we
can deduce the location of the first machine instruction
generated for that quadruple, simply by maintaining a count
of the number of words used for the instructions generated
so far. This count can be kept in the quadruple array (in an
extra field), so if a reference such as j: goto i is encountered,
and i is less than j, the current quadruple number, we may
simply generate a jump instruction with the target address
equal to the machine location of the first instruction in the
code for quadruple i. If, however, the jump is forward, so i
exceeds j, we must store on a list for quadruple i the location
of the first machine instruction generated for quadruple j.
Then we process quadruple i, we fill in the proper machine
location for all instructions that are forward jumps to i.
• The nature of the instruction set of the target machine
determines the difficulty of instruction selection. The
uniformity and completeness of the instruction set are
important factors. If the target machine does not support
each data type in a uniform manner, then each exception to
the general rule requires special handling.
• Instruction speeds and machine idioms are other important
factors. If we do not care about the efficiency of the target
program, instruction selection is straightforward. For each
type of three- address statement we can design a code
skeleton that outlines the target code to be generated for
that construct.
• For example, every three address statement of the form x :=
y + z, where x, y, and z are statically allocated, can be
translated into the code sequence
•
• MOV y, R0 /* load y into register R0 */
• ADD z, R0 /* add z to R0 */
• MOV R0, x /* store R0 into x */
• Unfortunately, this kind of statement – by - statement code
generation often produces poor code. For example, the
sequence of statements
• a := b + c
• d := a + e
• would be translated into
• MOV b, R0
• ADD c, R0
• MOV R0, a
• MOV a, R0
• ADD e, R0
• MOV R0, d
• Here the fourth statement is redundant, and so is the third if
‘a’ is not subsequently used.
• The quality of the generated code is determined
by its speed and size.
• A target machine with a rich instruction set may provide
several ways of implementing a given operation. Since the
cost differences between different implementations may be
significant, a naive translation of the intermediate code may
lead to correct, but unacceptably inefficient target code.
• For example if the target machine has an “increment”
instruction (INC), then the three address statement a := a+1
may be implemented more efficiently by the single
instruction INC a, rather than by a more obvious sequence
that loads a into a register, add one to the register, and then
stores the result back into a.
• MOV a, R0
• ADD #1,R0
• MOV R0, a
Instruction speeds are needed to design good code
sequence but unfortunately, accurate timing information is
often difficult to obtain. Deciding which machine code
sequence is best for a given three address construct may
also require knowledge about the context in which that
construct appears.
Instructions involving register operands are usually shorter
and faster than those involving operands in memory.
Therefore, efficient utilization of register is particularly
important in generating good code. The use of registers is
often subdivided into two subproblems:
1. During register allocation, we select the set of variables
that will reside in registers at a point in the program.
2. During a subsequent register assignment phase, we pick
the specific register that a variable will reside in.
• Finding an optimal assignment of registers to variables is
difficult, even with single register values. Mathematically,
the problem is NP-complete. The problem is further
complicated because the hardware and/or the operating
system of the target machine may require that certain
register usage conventions be observed.
• Certain machines require register pairs (an even
and next odd numbered register) for some operands and
results. For example, in the IBM System/370 machines
integer multiplication and integer division involve register
pairs.
• The multiplication instruction is of the form
• M x, y
• where x, is the multiplicand, is the even register of an
even/odd register pair.
• The multiplicand value is taken from the odd register
pair. The multiplier y is a single register. The product
occupies the entire even/odd register pair.
• The division instruction is of the form
• D x, y
• where the 64-bit dividend occupies an
even/odd register pair whose even
register is x; y represents the divisor.
After division, the even register holds
the remainder and the odd register the
quotient.
• Now consider the two three address code
sequences
(a)and (b) in which the only difference is the operator in
the second statement. The shortest assembly sequence
for (a) and (b) are given in(c).
• Ri stands for register i. L, ST and A stand
• t := a + b t := a + b
• t := t * c t := t + c
• t := t / d t := t / d
(a) (b)