Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 130

ai a b f e i t $

a: i i N(r1) N(r) N(r2) f f


S S a S iCtSE

NFA Efore Sr1 r2 Unit IE


E
E
Introduction
a) Source code
C C b
The form in which a computer program is written by the programmer. Source code is
written in some formal programming language which can be compiled automatically
into object code or machine code or executed by an interpreter.

b) Machine code

The representation of a computer program which is actually read and interpreted by


the computer. A program in machine code consists of a sequence of machine
instructions

c) Object code

The machine code generated by a source code language processor such as an


assembler or compiler. A file of object code may be immediately executable or it may
require linking with other object code files, e.g. libraries, to produce a complete
executable program.

d) Macro

A name (possibly followed by a formal argument list) that is equated to a text or


symbolic expression to which it is to be expanded (possibly with the substitution of
actual arguments) by a macro expander

e) Mnemonic

A word or string which is intended to be easier to remember than the thing it stands
for. Most often used in "instruction mnemonic" which are so called because they are
easier to remember than the binary patterns they stand for.

f) Operation code (Or "op code")

The part or parts of a machine language instruction which determines what kind of
action the computer should take, e.g. add, jump, load, store.

g) Instruction set

The collection of machine language instructions that a particular processor


understands.

1
h) High-level language

A programming language which provides some level of abstraction above assembly


language. These normally use statements consisting of English-like keywords such as
"FOR", "PRINT" or "GOTO", where each statement corresponds to several machine
language instructions. It is much easier to program in a high-level language than in
assembly language though the efficiency of execution depends on how good the
compiler or interpreter is at optimising the program

i) Assembly language

A symbolic representation of the machine language of a specific processor. Assembly


language is converted to machine code by an assembler. Usually, each line of
assembly code produces one machine instruction, though the use of macros is
common.

j) Interpreter

A program which executes other programs.

Interpreting code is slower than running the compiled code because the interpreter
must analyse each statement in the program each time it is executed and then perform
the desired action whereas the compiled code just performs the action. This run-time
analysis is known as "interpretive overhead". Access to variables is also slower in an
interpreter because the mapping of identifiers to storage locations must be done
repeatedly at run time rather than at compile time.

k) Compiler

A program that converts another program from some source language (or
programming language) to machine language (object code). Some compilers output
assembly language which is then converted to machine language by a separate
assembler.

l) Peephole optimisation

A kind of code optimisation that considers only a few adjacent instructions at a time
and looks for certain combinations which can be replaced with more efficient
sequences. E.g.

1. ADD R0, #1
2. ADD R0, #1

(add one to register R0) could be replaced by


3. ADD R0, #2

2
m) Register allocation

The phase of a compiler that determines which values will be placed in registers.
Register allocation may be combined with register assignment

n) C preprocessor

The standard macro-expansion utility run as the first phase of the C compiler, cc. Cpp
interprets lines beginning with "#" such as

1. #define BUFFER_SIZE 256

as a textual assignment giving the symbol BUFFER_SIZE a value "256".preprocessor

o) Execution

The process of carrying out the instructions in a computer program by a computer

p) Grammar

A formal definition of the syntactic structure of a language (see syntax), normally


given in terms of production rules which specify the order of constituents and their
sub-constituents in a sentence (a well-formed string in the language). Each rule has a
left-hand side symbol naming a syntactic category (e.g. "noun-phrase" for a natural
language grammar) and a right-hand side which is a sequence of zero or more
symbols. Each symbol may be either a terminal symbol or a non-terminal symbol. A
terminal symbol corresponds to one "lexeme" - a part of the sentence with no internal
syntactic structure (e.g. an identifier or an operator in a computer language). A non-
terminal symbol is the left-hand side of some rule.

q) Instruction mnemonic

A word or acronym used in assembly language to represent a binary machine


instruction operation code. Different processors have different instruction set and
therefore use a different set of mnemonics to represent them.

E.g. ADD, B (branch), BLT (branch if less than), SVC, MOVE, LDR (load register).

r) Lexical analysis

The first stage of processing a language. The stream of characters making up the
source program or other input is read one at a time and grouped into lexemes (or
"tokens") - word-like pieces such as keywords, identifiers, literals and punctutation.
The lexemes are then passed to the parser

3
s) Lexical analyser (Or "scanner")

The initial input stage of a language processor (e.g. a compiler), the part that performs
lexical analysis.

t) Parser

An algorithm or program to determine the syntactic structure of a sentence or string


of symbols in some language. A parser normally takes as input a sequence of tokens
output by a lexical analyser. It may produce some kind of abstract syntax tree as
output. One of the best known parser generators is yacc.

u) Parser generator

A program which takes a formal description of a grammar (e.g. in BNF) and outputs
source code for a parser which will recognise valid strings obeying that grammar and
perform associated actions. Unix's yacc is a well known example

v) Run time

1. The elapsed time to perform a computation on a particular computer.

2. The amount of time a processor actually spent on a particular process and


not on other processes or overhead (see time-sharing).

The period of time during which a program is being executed, as opposed to compile-
time or load time.

w) Token

A basic, grammatically indivisible unit of a language such as a keyword, operator or


identifier.

The Importance of Compilers

A compiler does not only “translate".


It determines whether its input is grammatical and meaningful.
Usually it also improves the input program in some signicant way.
The optimising compiler is a very intricate piece of software, and its correctness is of vast
importance.
Compiler front-end technology is important for many diferent software tools, where
structured text input has to be handled.

4
Understanding the operation of a compiler gives better insight into the structure of
programming languages.
The concept of syntax-directed processing is also of wider importance.

COMPILERS

 A compiler is a program takes a program written in a source language and


translates it into an equivalent program in a target language.

Source program COMPILER Target program


Normally the equivalent program in machine code – relocatable object file)
( Normally a program written in
a high-level programming language)

Error message
Other Applications

 In addition to the development of a compiler, the techniques used in compiler


design can be applicable to many problems in computer science.
 Techniques used in a lexical analyzer can be used in text editors, information
retrieval system, and pattern recognition programs.
 Techniques used in a parser can be used in a query processing system such as
SQL.
 Many software having a complex front-end may need techniques used in
compiler design.
 A symbolic equation solver which takes an equation as input. That program
should parse the given input equation.
 Most of the techniques used in compiler design can be used in Natural Language
Processing (NLP) systems.

Major Parts of Compilers

 There are two major parts of a compiler: Analysis and Synthesis


 In analysis phase, an intermediate representation is created from the given source
program.
 Lexical Analyzer, Syntax Analyzer and Semantic Analyzer are the parts of this
phase.
 In synthesis phase, the equivalent target program is created from this intermediate
representation.
 Intermediate Code Generator, Code Generator, and Code Optimizer are the parts
of this phase.

5
6
 Phases of A Compiler

Source program

Lexical analysis

Syntax analysis

Semantic analysis

Intermediate code generation

Code optimisation

Code generation

Target program

Phases of compiler

7
 Each phase transforms the source program from one representation into another
representation.
 They communicate with error handlers.
 They communicate with the symbol table.

Lexical Analyzer

 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:
o newval identifier
o := assignment operator
o oldval identifier
o + add operator
o a number

 Puts information about identifiers into the symbol table.


 Regular expressions are used to describe tokens (lexical constructs).
 A (Deterministic) Finite State Automaton can be used in the implementation of a
lexical analyzer.

Syntax Analyzer

 A Syntax Analyzer creates the syntactic structure (generally a parse tree) of the
given program.
 A syntax analyzer is also called as a parser.
 A parse tree describes a syntactic structure

assigstmt

identifier := Expression

newval
Expression
+ Expression

number
identifier

oldval 12
8
Syntax Analyzer (CFG)

 The syntax of a language is specified by a context free grammar (CFG).


 The rules in a CFG are mostly recursive.
 A syntax analyzer checks whether a given program satisfies the rules implied by a
CFG or not.
 If it satisfies, the syntax analyzer creates a parse tree for the given program.

 •Ex: We use productions to specify a CFG

 assigstmt -> identifier := expression


 expression -> identifier
 expression -> number
 expression -> expression + expression

Syntax Analyzer versus Lexical Analyzer

 Which constructs of a program should be recognized by the lexical analyzer, and


which ones by the syntax analyzer?
 Both of them do similar things; But the lexical analyzer deals with simple non-
recursive constructs of the language.
 The syntax analyzer deals with recursive constructs of the language.
 The lexical analyzer simplifies the job of the syntax analyzer.
 The lexical analyzer recognizes the smallest meaningful units (tokens) in a source
program.
 The syntax analyzer works on the smallest meaningful units (tokens) in a source
program to recognize meaningful structures in our programming language.

Parsing Techniques

 Depending on how the parse tree is created, there are different parsing techniques.
 These parsing techniques are categorized into two groups:
 Top-Down Parsing, Bottom-Up Parsing

Top-Down Parsing:

 Construction of the parse tree starts at the root, and proceeds towards the leaves.
 Efficient top-down parsers can be easily constructed by hand.
 Recursive Predictive Parsing, Non-Recursive Predictive Parsing (LL Parsing).

9
Bottom-Up Parsing:

 Construction of the parse tree starts at the leaves, and proceeds towards the root.
 Normally efficient bottom-up parsers are created with the help of some software
tools.
 Bottom-up parsing is also known as shift-reduce parsing.
 Operator-Precedence Parsing – simple, restrictive, easy to implement
 LR Parsing – much general form of shift-reduce parsing, LR, SLR, LALR

Semantic Analyzer

 A semantic analyzer checks the source program for semantic errors and collects
the type information for the code generation.
 Type-checking is an important part of semantic analyzer.
 Normally semantic information cannot be represented by a context-free language
used in syntax analyzers.
 Context-free grammars used in the syntax analysis are integrated with attributes
(semantic rules)
 the result is a syntax-directed translation,
 Attribute grammars
 Ex:
 newval := oldval + 12
 The type of the identifier newval must match with type of the expression
(oldval+12)

Intermediate Code Generation

 A compiler may produce an explicit intermediate codes representing the source


program.
 These intermediate codes are generally machine (architecture independent). But
the level of intermediate codes is close to the level of machine codes.
 Ex:
newval := oldval * fact + 1
id1 := id2 * id3 + 1
MULT id2,id3,temp1 Intermediates Codes (Quadraples)
ADD temp1,#1,temp2
MOV temp2,,id1
Code Optimizer (for Intermediate Code Generator)

10
 The code optimizer optimizes the code produced by the intermediate code
generator in the terms of time and space.

 Ex:
MULT id2,id3,temp1
ADD temp1,#1,id1

Code Generator

 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)

MOVE id2,R1
MULT id3,R1
ADD #1,R1
MOVE R1,id1
Some software tool for analysis
1. Structure Editors:

A structure editor takes as input a sequence of commands to build a source


program. The structure editor not only performs the text creation and modification
functions of an ordinary text editor, but it also analyzes the program text, putting an
appropriate hierarchical structure on the source program. Thus the structure editor can
perform additional tasks that are useful in the preparation of programs.

2. Pretty printers:

A pretty printer analyzes a program and prints it in such a way that the
structure of the program becomes clearly visible.

3. Static Checkers:
A static checker reads a program , analyzes it, and attempts to discover
potential bugs without running the program.

4. Interpreters:
Instead of producing a target program as translation an interpreter performs the
operations implied by the source program for an assignment statement. Interpreter are
frequently used to execute command languages , since each operator executed in a
command language is usually an invocation of a complex routine such as an editor or
compiler.

11
Analysis portion is similar to Conventional Compiler in

1. Text formatters:
A text formatter takes input that is a stream of characters ,most of which is
text to be typeset, but some of which includes commands to indicate paragraphs , figures,
or mathematical structures like subscripts and superscripts.

2. Silicon Compilers:
A Silicon compiler has a source language that is similar or identical to a conventional
programming language. However, the variables of the language represent , not locations
in memory, but, logical signals(0 or 1) or groups of signals in a switching circuit. The
output is a circuit design in an appropriate language.

3. Query interpreters:
A query interpreter translates a predicate containing relational and Boolean
operators into commands to search a database for records satisfying that predicate.

Compiler construction tools


Compiler writing systems are often called as compiler- compiler, compiler generators, or
translator writing systems.
Lexical analyzers for all the languages are same, except for the particular keywords.
Some general tools have been created for the automatic design of specific compiler
components. These tools use specialized languages for specifying and implementing the
component. The following are some useful compiler construction tools.

1. Parser generators
2. Scanner generators
3. Syntax directed translation engines
4. Automatic code generators
5. Data flow engines

1. Parser generator

Parser generators produce syntax analyzer from input that is based on the context free
grammar. Syntax analysis consumed a large fraction of the running time of a compiler.
Also syntax analysis consumed a large fraction of the intellectual effect of writing a
compiler. Many parser generators utilize powerful parsing algorithms that are too
complex to be carried out by hand.

2. Scanner generators

Scanner generators automatically generate lexical analyzers, normally from a


specification based on the regular expression. The result of the lexical analyzer is based
on the finite automata

12
3. Syntax directed translation engines

Syntax directed translation engines produce collections of routines that walk the
parse tree and produces intermediate code. Translations are associated with each node of
the parse tree. Each translation is defined in terms of translations at its neighbor nodes of
the tree.

4. Automatic code generators

Automatic code generators take a collection of rules that define the translation of
each operation of the intermediate language into the machine language fore the target
machine. The rules should have sufficient details to handle the different possible access
methods for data.
E.g. variables may be in the registers, or location in memory or allocated a
position on a stack.
The intermediate code statement are replaced by the template that represent sequence of
the machine instruction in such a way that the assumption about the storage of variable
match from template to template. This technique is called template matching

5. Data flow engine

Data flow engine helps in data flow analysis. Data flow analysis is needed to
perform good code optimisation. It also helps us to know how values are transmitted
from one part of a program to other part. The user has to supply detail relation ship
between intermediate code statements. The information is gathered and the data analysis
is done.

Token specification
Alphabet :
 a finite set of symbols (ASCII characters)

String :
 Finite sequence of symbols on an alphabet
 Sentence and word are also used in terms of string
  is the empty string
 |s| is the length of string s.

Language:
 sets of strings over some fixed alphabet
  the empty set is a language.
 {} the set containing empty string is a language
 The set of well-wormed C programs is a language
 The set of all possible identifiers is a language.

13
Operators on Strings:

 Concatenation: xy represents the concatenation of strings x and y.


 s  = s,
 
 s =s
 (Exponentiation) sn = s s s .. s ( n times) s0 = 

Parts of string:

 prefix of s : a string abtained by removing zero or more trailing symbols of the


string s; eg. Com is a prefix of Computer
 suffix of s : a string abtained by removing zero or more leading symbols of the
string s; eg. puter is a suffix of Computer
 sub string of s: a string obtained by deleting the prefix and suffix from the string
s. eg put in computer
 proper prefix, suffix or substring of s: any non empty string x that is,
respectively , a prefix , suffix, or substring of s suct that sx.

Operations on Languages

 •Concatenation:
o L1L2 = { s1s2 | s1  L1 and s2  L2 }
 •
Union
o L1  L2 = { s | s  L1 or s  L2 }
 •Exponentiation:
o L0 = {} L1 = L L2 = LL
 •
 Kleene Closure
o –
 L* = zero or more occurance
 •Positive Closure

o L+ = one or more occurance

Example

 L1 = {a,b,c,d} L2 = {1,2}
 L1L2 = {a1,a2,b1,b2,c1,c2,d1,d2}

14
 L1 È L2 = {a,b,c,d,1,2}
 L13 = all strings with length three (using a,b,c,d}
 L1* = all strings using letters a,b,c,d and empty string
 L1+ = doesn’t include the empty string

Regular Expressions

 We use regular expressions to describe tokens of a programming language.


 A regular expression is built up of simpler regular expressions (using defining
rules)
 Each regular expression denotes a language.
 A language denoted by a regular expression is called as a regular set.

Regular Expressions (Rules)

 Regular expressions over alphabet S

Reg. Expr Language it denotes


 {}
a S {a}
(r1) | (r2) L(r1) L(r2)
(r1) (r2) L(r1) L(r2)
(r)* (L(r))*
(r) L(r)


(r)+ = (r)(r)*
 (r)? = (r) | 

 We may remove parentheses by using precedence rules.

o * highest
o concatenation next
o | lowest
ab*|c means (a(b)*)|(c)

Examples:

 S = {0,1}
 0|1 => {0,1}

15
 (0|1)(0|1) => {00,01,10,11}
 0* => { ,0,00,000,0000,....}
 (0|1)* => all strings with 0 and 1, including the empty string

Regular Definitions

 To write regular expression for some languages can be difficult, because their
regular expressions can be quite complex. In those cases, we may use regular
definitions.
 We can give names to regular expressions, and we can use these names as
symbols to define other regular expressions.

 A regular definition is a sequence of the definitions of the form:


d1  r1 where di is a distinct name and
d2  r2 ri is a regular expression over symbols in
.  S{d1,d2,...,di-1}

dn  rn
basic symbols previously defined names

Ex:Identifiers in Pascal

 letter  A | B | ... | Z | a | b | ... | z


 digit  0 | 1 | ... | 9
 id  letter (letter | digit ) *

 If we try to write the regular expression representing identifiers without using


regular definitions, that regular expression will be complex.
o (A|...|Z|a|...|z) ( (A|...|Z|a|...|z) | (0|...|9) ) *
 •

 Ex: Unsigned numbers in Pascal

 digit  0 | 1 | ... | 9
 digits  digit +
 opt-fraction  ( . digits ) ?
 opt-exponent  ( E (+|-)? digits ) ?
 unsigned-num digits opt-fraction opt-exponent

16
Recognition machine

Finite Automata

 A recognizer for a language is a program that takes a string x, and answers “yes”
if x is a sentence of that language, and “no” otherwise.

x Yes or no
Recogniser

•We call the recognizer of the tokens as a finite automaton.


 A finite automaton can be: deterministic(DFA) or non-deterministic (NFA)
 This means that we may use a deterministic or non-deterministic automaton as a
lexical analyzer.
 Both deterministic and non-deterministic finite automaton recognize regular sets.

Which one? (deterministic or non-deterministic finite automaton)


 deterministic – faster recognizer, but it may take more space
 non-deterministic – slower, but it may take less space
 Deterministic automatons are widely used lexical analyzers.
 First, we define regular expressions for tokens; Then we convert them into a DFA
to get a lexical analyzer for our tokens.

–Algorithm1: Regular Expression  NFA  DFA


(two steps: first to NFA, then to DFA)

–Algorithm2: Regular Expression  DFA


(directly convert a regular expression into a DFA)

Non-Deterministic Finite Automaton (NFA)

 A non-deterministic finite automaton (NFA) is a mathematical model that consists


of:
o Q - a set of states
o  - a set of input symbols (alphabet)
o move – a transition function move to map state-symbol pairs to sets of
states.
o q0 - a start (initial) state

17
o F – a set of accepting states (final states)

 - transitions are allowed in NFAs. In other words, we can move from one state to
another one without consuming any symbol.
 A NFA accepts a string x, if and only if there is a path from the starting state to
one of accepting states such that edge labels along this path spell out x.

NFA (Example)

start 0 1

a b

Five tuples

0 is the start state q0


{2} is the set of final states F
 = {a,b}
Q = {0,1,2}
Transition Function  :

a b
0 {0,1} {0}
1 _ {2}
2 _ _

The language recognized by this NFA is (a|b) * a b

Deterministic Finite Automaton (DFA)

 A deterministic finite automaton (DFA) is a mathematical model that consists of:


o Q - a set of states

18
o  - a set of input symbols (alphabet)
o move – a transition function is from pair of state-symbol to state (not set
of states).
o q0 - a start (initial) state
o F – a set of accepting states (final states

 A Deterministic Finite Automaton (DFA) is a special form of a NFA.

 no state has - transition


 for each symbol a and state s, there is at most one labeled edge a leaving s.

b a

a b
start 0 1

The language recognized by this DFA is also (a|b) * a b

Implementing a DFA

 Let us assume that the end of a string is marked with a special symbol (say eos).
The algorithm for recognition will be as follows: (an efficient implementation)


s  q0 { start from the initial state }
c  nextchar { get the next character from the input string }
while (c != eos) do { do until the en dof the string }
begin
s  move(s,c) { transition function }
c  nextchar
end
if (s in F) then { if s is an accepting state }
return “yes”
else
return “no”

19
Implementing a NFA

S  e-closure({q0}) { set all of states can be accessible from q0 by -transitions }


c  nextchar
while (c != eos) {
begin
s  e-closure(move(Q,c)){ set of all states can be accessible from a state in Q
c  nextchar by a transition on c }
end
if (QF != ) then { if Q contains an accepting state }
return “yes”
else
return “no”
This algorithm is not efficient.

Converting A Regular Expression into A NFA (Thomson’s Construction)

 This is one way to convert a regular expression into a NFA.


 There can be other ways (much efficient) for the conversion.
 Thomson’s Construction is simple and systematic method.
 It guarantees that the resulting NFA will have exactly one final state, and one start
state.
 Construction starts from simplest parts (alphabet symbols).
 To create a NFA for a complex regular expression, NFAs of its sub-expressions
are combined to create its NFA,

To recognize an empty string 


To recognize a symbol a in the alphabet 

i f

If N(r1) and N(r2) are NFAs for regular expressions r1 and r2

20
For regular expression r1 | r2

N(r1)

i f

N(r2)

NFA for r1 | r2

For regular expression r1 r2

Final state of N(r2) become final state of N(r1r2)

For regular expression r*

NFA for r*

21
(Example - (a|b) * a )

b
b:

(a | b)
b

(a|b) *
b

a
(a|b) * a a

Converting a NFA into a DFA (subset construction)

put -closure({q0}) as an unmarked state into the set of DFA (DS)

22
while (there is one unmarked Q1 in DS) do
begin
mark S1 set of states to which there is a
transition on a from a state s in Q1
for each input symbol a do
begin
Q2  -closure(move(Q1,a))
if (Q2 is not in DS) then
add Q2 into DS as an unmarked state
transfunc[Q1,a]  Q2

end
end
•a state Q in DS is an accepting state of DFA if a state in S is an accepting state of NFA
•the start state of DFA is -closure({q0})

- closure({q0}) is the set of all states can be accessible from q0 by -transition

Converting a NFA into a DFA (Example)


.

2 a 3
0 1 6 7 a 8

4 b 5

Q0 = -closure({0}) = {0,1,2,4,7} Q0 into DS as an unmarked state


 mark Q0
-closure(move(Q0,a)) = -closure({3,8}) = {1,2,3,4,6,7,8} = Q1 Q1 into DS
-closure(move(S0,b)) = -closure({5}) = {1,2,4,5,6,7} = Q2 Q2 into DS
transfunc[Q0,a]  Q1 transfunc[Q0,b]  S2

 mark Q1
-closure(move(Q1,a)) = -closure({3,8}) = {1,2,3,4,6,7,8} = Q1
-closure(move(S1,b)) = -closure({5}) = {1,2,4,5,6,7} = Q2
transfunc[Q1,a]  S1 transfunc[Q1,b]  S2

23
 mark Q2
-closure(move(Q2,a)) = -closure({3,8}) = {1,2,3,4,6,7,8} = Q1
-closure(move(Q2,b)) = -closure({5}) = {1,2,4,5,6,7} = Q2
transfunc[Q2,a]  Q1 transfunc[Q2,b]  Q2

Q0 is the start state of DFA since 0 is a member of Q0={0,1,2,4,7}


Q1 is an accepting state of DFA since 8 is a member of Q1 = {1,2,3,4,6,7,8}

S1

S0 b a

S2

b
Converting Regular Expressions Directly to DFAs

 We may convert a regular expression into a DFA (without creating a NFA first).
 First we augment the given regular expression by concatenating it with a special
symbol #.
 r  (r)# augmented regular expression
 Then, we create a syntax tree for this augmented regular expression.
 In this syntax tree, all alphabet symbols (plus # and the empty string) in the
augmented regular expression will be on the leaves, and all inner nodes will be
the operators in that augmented regular expression.
 Then each alphabet symbol (plus #) will be numbered (position numbers).
Syntax tree of (a|b) * a #
(a|b) * a  (a|b) * a # augmented regular expression

• each symbol is numbered (positions)


• each symbol is at a leave

# • inner nodes are operators


4
* a
3
24
|
a b
1 2

followpos

Then we define the function followpos for the positions (positions


assigned to leaves).

followpos(i) -- is the set of positions which can follow


the position i in the strings generated by
the augmented regular expression.

For example, ( a | b) * a #
1 2 3 4
followpos(1) = {1,2,3}
followpos(2) = {1,2,3}
followpos(3) = {4}
followpos(4) = {}

firstpos, lastpos, nullable

 •o evaluate followpos, we need three more functions to be defined for the nodes
(not just for leaves) of the syntax tree.

•firstpos(n)

 the set of the positions of the first symbols of strings generated by the sub-
expression rooted by n.

•lastpos(n)

 the set of the positions of the last symbols of strings generated by the sub-
expression rooted by n.

•nullable(n)

 true if the empty string is a member of strings generated by the sub-expression


rooted by n false otherwise

25
How to evaluate firstpos, lastpos, nullable

n nullable(n) firstpos(n) lastpos(n)


leaf labeled  true  
leaf labeled with false {i} {i}
position i
| nullable(c1) or firstpos(c1) lastpos(c1) 
c1 c2 nullable(c2) firstpos(c2) lastpos(c2)

 nullable(c1) and if (nullable(c1)) if (nullable(c2))


nullable(c2) firstpos(c1)  lastpos(c1) 
c1 c2
firstpos(c2) lastpos(c2)
else firstpos(c1) else lastpos(c2)

* true firstpos(c1) lastpos(c1)


c1

How to evaluate followpos

•Two-rules define the function followpos:

 If n is concatenation-node with left child c1 and right child c2, and i is a position
in lastpos(c1), then all positions in firstpos(c2) are in followpos(i).
 If n is a star-node, and i is a position in lastpos(n), then all positions in
firstpos(n) are in followpos(i).
 If firstpos and lastpos have been computed for each node, followpos of each
position can be computed by making one depth-first traversal of the syntax tree.

Example -- ( a | b) * a #

{1,2,3} {4} green – firstpos


blue – lastpos

{1,2,3} {3} {4} # {4}


26
4
{1,2} {1,2} {3} {3}
* a
{1,2} | {1,2}

{1} a {1} {2} b {2}


1 2

Then we can calculate


followpos

followpos(1) = {1,2,3}
followpos(2) = {1,2,3}
followpos(3) = {4}
followpos(4) = {}

After we calculate follow positions, we are ready to create DFA for the regular
expression

Algorithm (RE  DFA)

Create the syntax tree of (r) #


Calculate the functions: followpos, firstpos, lastpos, nullable
Put firstpos(root) into the states of DFA as an unmarked state.
while (there is an unmarked state S in the states of DFA) do
mark S
for each input symbol a do

let s1,...,sn are positions in S and symbols in those positions are a


S’  followpos(s1)  ...  followpos(sn)
move(S,a)  S’

if (S’ is not empty and not in the states of DFA)


put S’ into the states of DFA as an unmarked state
.
the start state of DFA is firstpos(root)
the accepting states of DFA are all states containing the position of #

Example -- ( a | b) * a #

1 2 34

followpos(1)={1,2,3}
followpos(2)={1,2,3}
followpos(3)={4}
followpos(4)={}

27
S1=firstpos(root)={1,2,3}
 mark S1
a: followpos(1)  followpos(3)={1,2,3,4}=S2 move(S1,a)=S2
b: followpos(2)={1,2,3}=S1 move(S1,b)=S1
 mark S2
a: followpos(1)  followpos(3)={1,2,3,4}=S2 move(S2,a)=S2
b: followpos(2)={1,2,3}=S1 move(S2,b)=S1

start state: S1 b a
accepting states: {S2}
a
S1 S2

Example -- ( a | ) b c* #
1 2 3 4

followpos(1)={2}
followpos(2)={3,4}
followpos(3)={3,4}
followpos(4)={}
S1=firstpos(root)={1,2}
 mark S1
a: followpos(1)={2}=S2 move(S1,a)=S2
b: followpos(2)={3,4}=S3 move(S1,b)=S3
 mark S2
b: followpos(2)={3,4}=S3 move(S2,b)=S3
 mark S3
c: followpos(3)={3,4}=S3 move(S3,c)=S3

start state: S1
accepting states: {S3} S2
a
b
S1
b
S3 c

28
Minimizing Number of States of a DFA

 partition the set of states into two groups:


 G1 : set of accepting states
 G2 : set of non-accepting states

 •For each new group G


– partition G into subgroups such that states s1 and s2 are in the same
group iff
– for all input symbols a, states s1 and s2 have transitions to states in the
same group.

 Start state of the minimized DFA is the group containing the start state of the
original DFA.
 Accepting states of the minimized DFA are the groups containing the accepting
states of the original DFA.

Minimizing DFA – Example

a G1 = {2}
– G2 = {1,3}

2 G2 cannot be partitioned because


a move(1,a)=2 move(1,b)=3
move(3,a)=2 move(2,b)=3
1 b a
b
3

b b a
So, the minimized DFA (with minimum states)

{1,3} a {2}

29
Some Other Issues in Lexical Analyzer

 The lexical analyzer has to recognize the longest possible string.


 Ex: identifier newval -- n ne new newv newva newval

 •What is the end of a token? Is there any character which marks the end of a
token?
 It is normally not defined.
 If the number of characters in a token is fixed, in that case no problem: + -
 But <  < or <> (in Pascal)
 The end of an identifier : the characters cannot be in an identifier can mark the
end of token.
 We may need a lookhead
 In Prolog:
 p :- X is 1. p :- X is 1.5.
 The dot followed by a white space character can mark the end of a number.
But if that is not the case, the dot must be treated as a part of the number

 Skipping comments
 Normally we don’t return a comment as a token.
 We skip a comment, and return the next token (which is not a comment) to the
parser.
 So, the comments are only processed by the lexical analyzer, and the don’t
complicate the syntax of the language.

 Symbol table interface


 symbol table holds information about tokens (at least lexeme of identifiers)
 how to implement the symbol table, and what kind of operations.
 •hash table – open addressing, chaining
 •putting into the hash table, finding the position of a token from its
lexeme.

 Positions of the tokens in the file (for the error handling).

Error Recovery Techniques


Panic-Mode Error Recovery
 Skipping the input symbols until a synchronizing token is found.

Phrase-Level Error Recovery

30
 Each empty entry in the parsing table is filled with a pointer to a specific error
routine to take care that error case.

Error-Productions

 If we have a good idea of the common errors that might be encountered, we can
augment the grammar with productions that generate erroneous constructs.
 When an error production is used by the parser, we can generate appropriate error
diagnostics.
 Since it is almost impossible to know all the errors that can be made by the
programmers, this method is not practical.

Global-Correction

 Ideally, we we would like a compiler to make as few change as possible in


processing incorrect inputs.
 We have to globally analyze the input to find the error.
 This is an expensive method, and it is not in practice.

Panic-Mode Error Recovery in LL(1) Parsing

In panic-mode error recovery, we skip all the input symbols until a synchronizing token
is found.

What is the synchronizing token?


 All the terminal-symbols in the follow set of a non-terminal can be used as a
synchronizing token set for that non-terminal.

So, a simple panic-mode error recovery for the LL(1) parsing:
 All the empty entries are marked as synch to indicate that the parser will skip all
the input symbols until a symbol in the follow set of the non-terminal A which on
the top of the stack. Then the parser will pop that non-terminal A from the stack.
The parsing continues from that state.
 To handle unmatched terminal symbols, the parser pops that unmatched terminal
symbol from the stack and it issues an error message saying that that unmatched
terminal is inserted.

Phrase-Level Error Recovery


Each empty entry in the parsing table is filled with a pointer to a special error routine
which will take care that error case.
These error routines may:
 change, insert, or delete input symbols.
 issue appropriate error messages

31
 pop items from the stack.
We should be careful when we design these error routines, because we may put the parser
into an infinite loop.

A typical lexical analyzer generator

A language for specifying lexical analyzers

Lex is a tool widely used to specify lexical analyzers.

Lex source pgm lex.l


LEX Lex.yy.c
COMPILER

lex.yy.c
C COMPILER a.out

Input stream
a.out Sequence of token

Lex specification

A Lex program consists of three parts


Declarations
%%
translation
%%
auxicillary procedures

Declaration

 Variables
 Manifest constants: an identifier that is declared to represent a constant
 Regular definition:

32
 To write regular expression for some languages can be difficult,
because their regular expressions can be quite complex. In those
cases, we may use regular definitions.
 We can give names to regular expressions, and we can use these
names as symbols to define other regular expressions.

 •A regular definition is a sequence of the definitions of the form:


 d1  r1 where di is a distinct name and

 d2  r2 where ri is a regular expression over


symbols in  {d1,d2,...,di-1}
 dn  rn

Translation rules

The translation rules of the Lex program are statements of the form

p1 {action1}
p2 {action2}
.. …
pn {actionn}

Where pi is a regular expression and


Each actioni} is program fragment describing what action the lexical analyzer should
take when pattern matches the lexeme

Auxicillary procedures:

Needed by the action


Compiled separately and loaded with the lexical analyzer.

Example

%{
/* Definition of manifest constants LT, LE, EQ,NE,GT,GE,IF,THEN, ELSE, ID,
NUMBER, RELOP*/
}%
/*regular definition*/
delim { \t\n}
ws {delim}+
letter {A-Za-z}
digit {0-9}
id {letter}({letter}|{digit})*
number {digit}+(\.{digit}+)?(E[+\-]?{digit}+)?

33
%%
{ws} {/*no action and noreturn*/}
if {return (IF);}
then {return (THEN);}
else {return (ELSE);}
{id} {yyval=install_id(); return (ID);}
{number} {yyval=install_num (); return (NUMBER);}
“<” {yyval = LT; return (RELOP);}
“<=” {yyval = LE; return (RELOP);}
“=” {yyval = EQ; return (RELOP);}
“<>” {yyval = NE; return (RELOP);}
“>” {yyval = GT; return (RELOP);}
“>=” {yyval = GE; return (RELOP);}
%%
INSTALL_ID()
{
/* Procedure to install the lexemes, whose first character is pointed to by yytext and
whose length is yyleng, into the symbol table and return a pointer there to */
}
install_num()
{
Similar procedure to install a lexeme that is a number */
}

Design of a lexical analyzer generator


The specification of the lexical analyzer of the form

p1 {action1}
p2 {action2}
.. …
pn {actionn}

where pi ia a regular expression and


Each actioni} is program fragment describing what action the lexical analyzer should
take when pattern matches the lexeme

Our problem is to construct a recognizer that looks for the lexemes in the input buffer. If
more than one matches, the recognizer is to choose the longest lexeme matched.

Lex specification
LEX COMPILER Transition table

34
Lexeme Input buffer

FA
Simulator

Transition
table

Schematic lexical analyzer

A finite automaton is natural model around which to build a lexical analyzer


There are two pointer to input buffer (beginning and forward pointer)
The output of Lex compiler is transition table
The lexical analyzer consists of a finite automaton simulator that uses this tranition table
to look for the regular expression patterns in the input buffer.

Pattern matching based on NFA’s


Construct the transitition table of a NFA N for the composite pattern p1|p2|..|pn
create NFA N(pi) for each pi .
Add a new state S0.
Link S0 to the start state of each pattern N(pi) with a  transitition.

N(p1)

S0 N(p2)
……….

N(pn)

35
 NFA recognizes the longest prefix of the input string that is matched by a pattern
 Simulate the NFA using the algorithm
 Construct the sequence of sets of state that the combined NFA can be in after
seeing each input symbol
 We must continue to simulate NFA until it reaches termination.

Example

a {}

abb {}
a*b+ {}

a
1 2

a b b
3 4 5 6

b
7 8

a b

NFA for a, abb, a*b+

36
a
1 2

a b b
3 4 5 6
0

b
7 8

a b

Nfa recognizing three different patterns

Processing string aab


a a b
0137 247 7 8

p1 p3

Matched pattern: a, , a*b+


Longest matched pattern: , a*b+

DFA simulator for lexical analyzer can do this.

37
Syntax Analyzer

 Syntax Analyzer creates the syntactic structure of the given source program.
 This syntactic structure is mostly a parse tree.
 Syntax Analyzer is also known as parser.
 The syntax of a programming is described by a context-free grammar (CFG). We
will use BNF (Backus-Naur Form) notation in the description of CFGs.
 The syntax analyzer (parser) checks whether a given source program satisfies the
rules implied by a context-free grammar or not.
 If it satisfies, the parser creates the parse tree of that program.
 Otherwise the parser gives the error messages.

A context-free grammar

gives a precise syntactic specification of a programming language.


the design of the grammar is an initial phase of the design of a compiler.
–a grammar can be directly converted into a parser by some tools.

Parser
 Parser works on a stream of tokens.

 The smallest item is a token.


token parse
Source
Lexical
Analyzer Parser
program tree
Get next token

We categorize the parsers into two groups:

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

•Both top-down and bottom-up parsers scan the input from left to right (one symbol at a
time).
•Efficient top-down and bottom-up parsers can be implemented only for sub-classes of
context-free grammars.

38
 LL for top-down parsing
 LR for bottom-up parsing

Context-Free Grammars

Inherently recursive structures of a programming language are defined by a context-free


grammar.

In a context-free grammar,
we have:
 A finite set of terminals (in our case, this will be the set of tokens)
 A finite set of non-terminals (syntactic-variables)
 A finite set of productions rules in the following form
 Aa where A is a non-terminal and a is a string of terminals and non-
terminals (including the empty string)
 A start symbol (one of the non-terminal symbol)


•Example:
E E+E | E–E | E*E | E/E | -E
E (E)
E  id

Derivations

E  E+E
•E+E derives from E
–we can replace E by E+E
–to able to do this, we have to have a production rule EE+E in our grammar.

E  E+E  id+E  id+id
•A sequence of replacements of non-terminal symbols is called a derivation of id+id
from E.

•In general a derivation step is


A  
if there is a production rule A in our grammar where and  are
arbitrary strings of terminal and non-terminal symbols

1  2  ...  n (n derives from 1 or 1 derives n )

 : derives in one step

39
*
 : derives in zero or more steps
+
 : derives in one or more steps

CFG – Terminology
 L(G) is the language of G (the language generated by G) which is a set of
sentences.
 A sentence of L(G) is a string of terminal symbols of G.
 If S is the start symbol of G then w is a sentence of L(G) iff S   where  is
a string of terminals of G.
 If G is a context-free grammar, L(G) is a context-free language.
 Two grammars are equivalent if they produce the same language.

 S
- If  contains non-terminals, it is called as a sentential form of G.
- If  does not contain non-terminals, it is called as a sentence of G.

Derivation Example
E  -E  -(E)  -(E+E)  -(id+E)  -(id+id)
OR
E  -E  -(E)  -(E+E)  -(E+id)  -(id+id)

 •At each derivation step, we can choose any of the non-terminal in the sentential
form of G for the replacement.

 If we always choose the left-most non-terminal in each derivation step, this


derivation is called as left-most derivation.
 If we always choose the right-most non-terminal in each derivation step, this
derivation is called as right-most derivation.

Left-Most and Right-Most Derivations


Left-Most Derivation
E  -E  -(E)  -(E+E)  -(id+E)  -(id+id)
lm lm lm lm lm

Right-Most Derivation

40
E  -E  -(E)  -(E+E)  -(E+id)  -(id+id)
rm rm rm rm rm

•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.

Parse Tree

 Inner nodes of a parse tree are non-terminal symbols.


 The leaves of a parse tree are terminal symbols.
 A parse tree can be seen as a graphical representation of a derivation

E -E E E
-(E)
- E - E

( E )

E
E
-(E+E) - E
- E -(id+E)
id + E
( E )
( E )
E + E
E + E
id

E
- E 41

-(id+id)
E + E

id id

id + id

Ambiguity
A grammar produces more than one parse tree for a sentence is called as an ambiguous
grammar.

E  E+E  id+E  id+E*E E


 id+id*E  id+id*id
E + E

id E * E

id id
E  E*E  E+E*E  id+E*E
 id+id*E  id+id*id
E

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

We should eliminate the ambiguity in the grammar during the design phase of the
compiler.

42
An ambiguous grammar should be written to eliminate the ambiguity.
We have to prefer one of the parse trees of a sentence (generated by an ambiguous
grammar) to disambiguate that grammar to restrict to this choice.

stmt  if expr then stmt |


if expr then stmt else stmt | otherstmts

if E1 then if E2 then S1 else S2

stmt
if expr then stmt else stmt
E1 if expr then stmt S2

E2 S1

stmt
if expr then stmt
E1 if expr then stmt else stmt

E2 S1 S2

2
43
We prefer the second parse tree (else matches with closest if).
So, we have to disambiguate our grammar to reflect this choice.

The unambiguous grammar will be:

stmt  matchedstmt | unmatchedstmt


matchedstmt  if expr then matchedstmt else matchedstmt | otherstmts
unmatchedstmt  if expr then stmt |
if expr then matchedstmt else unmatchedstmt

Ambiguity – Operator Precedence


Ambiguous grammars (because of ambiguous operators) can be disambiguated according
to the precedence and associativity rules.

E  E+E | E*E | E^E | id | (E)


disambiguate the grammar

precedence: ^ (right to left)


* (left to right)
+ (left to right)
E  E+T | T
T  T*F | F
F  G^F | G
G  id | (E)

Left Recursion
 A grammar is left recursive if it has a non-terminal A such that there is a
derivation.
o +

o A  A for some string 

 Top-down parsing techniques cannot handle left-recursive grammars.


 So, we have to convert our left-recursive grammar into an equivalent grammar
which is not left-recursive.
 The left-recursion may appear in a single step of the derivation (immediate left-
recursion), or may appear in more than one step of the derivation.

Immediate Left-Recursion

44
AA|  where  does not start with A
 eliminate immediate left recursion
A   A’
A’   A’ |  an equivalent grammar

In general,

A A 1 | ... | A m | 1 | ... | n where 1 ... n do not start with A


 eliminate immediate left recursion
A   A’ | ... |  A’
1 n
A  1 A | ... | m A’ | 
’ ’ an equivalent grammar

Immediate Left-Recursion – Example


E  E+T | T
T  T*F | F
F  id | (E)

 eliminate immediate left recursion

E  T E’
E’  +T E’ | 
T  F T’
T’  *F T’ | 
F  id | (E)

Left-Recursion – Problem

 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

45
So, we have to eliminate all left-recursions from our grammar

Eliminate Left-Recursion – Algorithm


Arrange non-terminals in some order: A1 ... An
- for i from 1 to n do {
- for j from 1 to i-1 do {
replace each production
Ai  Aj 
by
Ai  1  | ... | k 
where Aj  1 | ... | k
}
- eliminate immediate left-recursions among Ai productions
}

Eliminate Left-Recursion – Example


S  Aa | b
A  Ac | Sd | f
- Order of non-terminals: S, A
for S:
- we do not enter the inner loop.
- there is no immediate left recursion in S.
for A:
- Replace A  Sd with A  Aad | bd
So, we will have A  Ac | Aad | bd | f
- Eliminate the immediate left-recursion in A
A  bdA’ | fA’
A’  cA’ | adA’ | 
So, the resulting equivalent grammar which is not left-recursive is:
S  Aa | b
A  bdA’ | fA’
A’  cA’ | adA’ | 

Eliminate Left-Recursion – Example2


S  Aa | b
A  Ac | Sd | f
- Order of non-terminals: A, S
for A:

46
- 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’ | 

Left-Factoring
 A predictive parser (a top-down parser without backtracking) insists that the
grammar must be left-factored.

 grammar  a new equivalent grammar suitable for predictive parsing

 stmt  if expr then stmt else stmt |


 if expr then stmt

 •when we see if, we cannot now which production rule to choose to re-write
stmt in the derivation.

 In general,
 •

 A  1 | 2 where  is non-empty and the first symbols of 1 and 2 (if they
have one)are different.
 when processing a we cannot know whether expand
 A to 1 or A to 2

 But, if we re-write the grammar as follows



A  A’

47

A’   |  so, we can immediately expand A to A’
1 2

Left-Factoring -- Algorithm

 For each non-terminal A with two or more alternatives (production rules) with a
common non-empty prefix, let say
 •

 A  1 | ... | n | 1 | ... | m

 convert it into

 A  A’ | 1 | ... | m
A  1 | ... | n
 ’

Left-Factoring – Example1

A  abB | aB | cdg | cdeB | cdfB



A  aA’ | cdg | cdeB | cdfB
A’bB | B

A  aA’ | cdA’’
A’  bB | B
A’’  g | eB | fB

Left-Factoring – Example2
A  ad | a | ab | abc | b

A  aA’ | b
A’  d | e | b | bc

48

A  aA’ | b
A’  d | e | bA’’
A’’  e | c

Non-Context Free Language Constructs

 There are some language constructions in the programming languages which are
not context-free. This means that, we cannot write a context-free grammar for
these constructions.

 L1 = { c |  is in (a|b)*} is not context-free
  declaring an identifier and checking whether it is declared or not later.
We cannot do this with a context-free language. We need semantic analyzer
(which is not context-free).

 L2 = {anbmcndm | n1 and m1 } is not context-free


 declaring two functions (one with n parameters, the other one with


m parameters), and then calling them with actual parameters.

Top-Down Parsing
The parse tree is created top to bottom.
Top-down parser

–Recursive-Descent Parsing

 Backtracking is needed (If a choice of a production rule does not work, we


backtrack to try other alternatives.)
 It is a general parsing technique, but not widely used.
 Not efficient

–Predictive Parsing
 no backtracking
 efficient
 needs a special form of grammars (LL(1) grammars).

49
 Recursive Predictive Parsing is a special form of Recursive Descent parsing
without backtracking.
 Non-Recursive (Table Driven) Predictive Parser is also known as LL(1) parser.

Recursive-Descent Parsing (uses Backtracking)

S  aBc
B  bc | b
input: abc

Predictive Parser

a grammar   a grammar suitable for predictive


eliminate left parsing (a LL(1) grammar)
left recursion factor no %100 guarantee.
When re-writing a non-terminal in a derivation step, a predictive parser can uniquely
choose a production rule by just looking the current symbol in the input string.

A  1 | ... | n input: ... a .......

current token

stmt  if ...... |
while ...... |
begin ...... |
for .....

50
 When we are trying to write the non-terminal stmt, if the current token is if we
have to choose first production rule.
 When we are trying to write the non-terminal stmt, we can uniquely choose the
production rule by just looking the current token.
 We eliminate the left recursion in the grammar, and left factor it. But it may not
be suitable for predictive parsing (not LL(1) grammar).

Recursive Predictive Parsing


Each non-terminal corresponds to a procedure.
Ex: A  aBb (This is only the production rule for A)
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 | e

 If all other productions fail, we should apply an -production. For example, if the
current token is not a or b, we may apply the -production.
 Most correct choice: We should apply an -production for a non-terminal A when
the current token is in the follow set of A (which terminals can follow A in the
sentential forms).

Recursive Predictive Parsing (Example)

A  aBe | cBd | C
B  bB | e

51
Cf

proc C
{
match the current token with f, and move to the next token;
}
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 e, and move to the next token;
c: - match the current token with c, and move to the next token;
- call B;
- match the current token with d, and move to the next token;
f: - call C
}
}

proc B
{
case of the current token
{
b: - match the current token with b, and move to the next token;
- call B;
e,d: do nothing
}

f- first set of C
e,d – follow set of B

Non-Recursive Predictive Parsing -- LL(1) Parser


 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

52
LL(1) Parser

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.

LL(1) Parser – Parser Actions

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.


 1. If X and a are $
 parser halts (successful completion)
 2.
 If X and a are the same terminal symbol (different from $)
 parser pops X from the stack, and moves the next symbol in the input
buffer.
 3. If X is a non-terminal
 parser looks at the parsing table entry M[X,a]. If M[X,a] holds a
production rule XY1Y2...Yk, it pops X from the stack and pushes
Yk,Yk-1,...,Y1 into the stack. The parser also outputs the production rule
XY1Y2...Yk to represent a step of the derivation.
 4. none of the above
 error
o all empty entries in the parsing table are errors.
o If X is a terminal symbol different from a, this is also an error case.

53
LL(1) Parser – Example1
S  aBa
B  bB | 

stack input output


$S abba$ S  aBa
$aBa abba$
$aB bba$ B  bB
$aBb bba$
$aB ba$ B  bB
$aBb ba$
$aB a$ Be
$a a$
$ $ accept, successful completion

a b $
S S  aBa
B B B  bB
LL(1) Parsing Table

Outputs: S  aBa B  bB B bB B

Derivation(left-most): SaBaabBaabbBaabba

S
parse tree

a B a

b B

b B
LL(1) Parser – Example2

E  TE’
E’  +TE’ | 
T  FT’

54
T’  *FT’ | 
F  (E) | id

id + * ( ) $
E E TE’ E TE’

E E’ E’
’ E’ +TE’
T T FT’
T FT’
T T’ T’ T’
T’ *FT’
’F
F id F (E)

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

Constructing LL(1) Parsing Tables

•Two functions are used in the construction of LL(1) parsing tables:


–FIRST FOLLOW

55
FIRST() is a set of the terminal symbols which occur as first symbols in strings
derived from  where  is any string of grammar symbols.
 if  derives to , then  is also in FIRST() .

FOLLOW(A) is the set of the terminals which occur immediately after (follow) the
non-terminal A in the strings derived from the starting symbol.
 a terminal a is in FOLLOW(A) if S  Aa
 $ is in FOLLOW(A) if S  A

Compute FIRST for Any String X

If X is a terminal symbol
 FIRST(X)={X}

If X is a non-terminal symbol and X   is a production rule


  is in FIRST(X).

If X is a non-terminal symbol and X  Y1Y2..Yn is a production rule


 if a terminal a in FIRST(Yi) and  is in all FIRST(Yj) for j=1,...,i-1
then a is in FIRST(X).
 if  is in all FIRST(Yj) for j=1,...,n then  is in FIRST(X).
If X is 
 FIRST(X)={}

If X is Y1Y2..Yn
 if a terminal a in FIRST(Yi) and  is in all FIRST(Yj) for j=1,...,i-1
then a is in FIRST(X).
if  is in all FIRST(Yj) for j=1,...,n then  is in FIRST(X).

FIRST Example
E  TE’
E’  +TE’ | 

56
T  FT’
T’  *FT’ | 
F  (E) | id

FIRST(F) = {(,id} FIRST(TE’) = {(,id}


FIRST(T’) = {*, } FIRST(+TE’ ) = {+}
FIRST(T) = {(,id} FIRST() = {}
FIRST(E’) = {+, } FIRST(FT’) = {(,id}
FIRST(E) = {(,id} FIRST(*FT’) = {*}
FIRST() = {}
FIRST((E)) = {(}
FIRST(id) = {id}

Compute FOLLOW (for non-terminals)

If S is the start symbol


 $ is in FOLLOW(S)

if A  B is a production rule


 everything in FIRST() is FOLLOW(B) except 

If ( A  B is a production rule )or ( A  B is a production rule and  is in


FIRST(b) )
 everything in FOLLOW(A) is in FOLLOW(B).

We apply these rules until nothing more can be added to any follow set.

FOLLOW Example
E  TE’
E’  +TE’ | 
T  FT’
T’  *FT’ | 
F  (E) | id

FOLLOW(E) = { $, ) }
FOLLOW(E’) = { $, ) }
FOLLOW(T) = { +, ), $ }
FOLLOW(T’) = { +, ), $ }
FOLLOW(F) = {+, *, ), $ }

Constructing LL(1) Parsing Table -- Algorithm

57
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,$]
All other undefined entries of the parsing table are error entries.

Constructing LL(1) Parsing Table – Example


E  TE’ FIRST(TE’)={(,id}  E  TE’ into M[E,(] and M[E,id]
E’ +TE’ FIRST(+TE’ )={+}  E’  +TE’ into M[E’,+]

E’   FIRST()={}  none

but since  in FIRST()


and FOLLOW(E’)={$,)}  E’   into M[E’,$] and M[E’,)]

T  FT’ FIRST(FT’)={(,id}  T  FT’ into M[T,(] and M[T,id]


T’  *FT’ FIRST(*FT’ )={*}  T’  *FT’ into M[T’,*]

T’   FIRST()={}  none
but since  in FIRST()
and FOLLOW(T’)={$,),+}  T  e into M[T’,$], M[T’,)]

and M[T’,+]
F  (E) FIRST((E) )={(}  F  (E) into M[F,(]
F  id FIRST(id)={id}  F  id into M[F,id]

LL(1) Grammars

A grammar whose parsing table has no multiply-defined entries is said to be LL(1)


grammar.

 (1)
 one input symbol used as a look-head symbol do determine parser action
 L left most derivation
 L input scanned from left to right

58
The parsing table of a grammar may contain more than one production rule. In this case,
we say that it is not a LL(1) grammar.

A Grammar which is not LL(1)

SiCtSE | a FOLLOW(S) = { $,e }


EeS |  FOLLOW(E) = { $,e }
Cb FOLLOW(C) = { t }

FIRST(iCtSE) = {i}
FIRST(a) = {a}
FIRST(eS) = {e}
FIRST() = {}
FIRST(b) = {b}

two production rules for M[E,e]


Problem  ambiguity

What do we have to do it if the resulting parsing table contains multiply defined entries?

 If we didn’t eliminate left recursion, eliminate the left recursion in the grammar.
 If the grammar is not left factored, we have to left factor the grammar.
 If its (new grammar’s) parsing table still contains multiply defined entries, that
grammar is ambiguous or it is inherently not a LL(1) grammar.

A left recursive grammar cannot be a LL(1) grammar.

 A  A | 
 any terminal that appears in FIRST() also appears FIRST(A) because
A  .
 If  is , any terminal that appears in FIRST() also appears in
FIRST(A) and FOLLOW(A).

59
A grammar is not left factored, it cannot be a LL(1) grammar
 A  1 | 2
 any terminal that appears in FIRST(1) also appears in
FIRST(2).
An ambiguous grammar cannot be a LL(1) grammar.

Properties of LL(1) Grammars

A grammar G is LL(1) if and only if the following conditions hold for two distinctive
production rules A   and A  

 Both  and  cannot derive strings starting with same


terminals.
 At most one of  and  can derive to .

 3.If  can derive to , then  cannot derive to any string


starting with a terminal in FOLLOW(A).

Error Recovery in Predictive Parsing

•An error may occur in the predictive parsing (LL(1) parsing)


 if the terminal symbol on the top of stack does not match with the
current input symbol.
 if the top of stack is a non-terminal A, the current input symbol is a, and
the parsing table entry M[A,a] is empty.
•What should the parser do in an error case?
 The parser should be able to give an error message (as much as possible
meaningful error message).
 It should be recover from that error case, and it should be able to
continue the parsing with the rest of the input.

Bottom-Up Parsing

60
A bottom-up parser creates the parse tree of the given input starting from leaves
towards the root.
A bottom-up parser tries to find the right-most derivation of the given input in the reverse
order.
S  ...  w (the right-most derivation of w)
 (the bottom-up parser finds the right-most derivation in the
reverse order)
•Bottom-up parsing is also known as shift-reduce parsing because its two main actions
are shift and reduce.
–At each shift action, the current symbol in the input string is pushed to a stack.
–At each reduction step, the symbols at the top of the stack (this symbol sequence is the
right side of a production) will replaced by the non-terminal at the left side of that
production.
–There are also two more actions: accept and error.

Shift-Reduce Parsing
A shift-reduce parser tries to reduce the given input string into the starting symbol.

a string  the starting symbol


reduced to

At each reduction step, a substring of the input matching to the right side of a production
rule is replaced by the non-terminal at the left side of that production rule.

If the substring is chosen correctly, the right most derivation of that string is created in
the reverse order.

Rightmost Derivation: S


Shift-Reduce Parser finds:   ...  S

Shift-Reduce Parsing -- Example

S  aABb input string: aaabb


A  aA | a aaAbb
B  bB | b aAbb  reduction
aABb
S
S  aABb  aAbb  aaAbb  aaabb
rm rm rm rm

Right Sentential Forms

61
How do we know which substring to be replaced at each reduction step?

Handle
Informally, a handle of a string is a substring that matches the right side of a production
rule.
–But not every substring matches the right side of a production rule is handle

A handle of a right sentential form  ( ) is


a production rule A  and a position of 
where the string  may be found and replaced by A to produce
the previous right-sentential form in a rightmost derivation of .

S  A  

If the grammar is unambiguous, then every right-sentential form of the grammar has
exactly one handle.
We will see that  is a string of terminals.

Handle Pruning

A right-most derivation in reverse can be obtained by handle-pruning.

S=0  1  2  ...  n-1  n= input string)

Start from n, find a handle Ann in n, and replace n in by An to get n-1.
Then find a handle An-1n-1 in n-1, and replace n-1 in by An-1 to get n-2.
Repeat this, until we reach S.

A Shift-Reduce Parser
E  E+T | T Right-Most Derivation of id+id*id
T  T*F | F E  E+T  E+T*F  E+T*id  E+F*id
F  (E) | id  E+id*id  T+id*id  F+id*id  id+id*id

Right-Most Sentential Form Reducing Production

id+id*id F  id
F+id*id TF
T+id*id ET
E+id*id F  id
E+F*id TF
E+T*id F  id
E+T*F T  T*F

62
E+T E  E+T

E
Handles are red and underlined in the right-sentential forms.

A Stack Implementation of A Shift-Reduce Parser


There are four possible actions of a shift-parser action:

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.

Initial stack just contains only the end-marker $.


The end of the input string is marked by the end-marker $.

A Stack Implementation of A Shift-Reduce Parser

Stack Input Action


$ id+id*id$ shift
$id +id*id$ reduce by F  id
$F +id*id$ reduce by T  F
$T +id*id$ reduce by E  T
$E +id*id$ shift
$E+ id*id$ shift
$E+id *id$ reduce by F  id
$E+F *id$ reduce by T  F
$E+T *id$ shift
$E+T* id$ shift
$E+T*id $ reduce by F  id
$E+T*F $ reduce by T  T*F
$E+T $ reduce by E  E+T
$E $ accept

63
Parse Tree

Conflicts during Shift-Reduce Parsing


There are context-free grammars for which shift-reduce parsers cannot be used.

Stack contents and the next input symbol may not decide action:

–shift/reduce conflict: Whether make a shift operation or a reduction.


–reduce/reduce conflict: The parser cannot decide which of several reductions to make.

If a shift-reduce parser cannot be used for a grammar, that grammar is called as non-
LR(k) grammar.

left to right right-most k lookhead
scanning derivation
•An ambiguous grammar can never be a LR grammar

Shift-Reduce Parsers

1.Operator-Precedence Parser

64
– simple, but only a small class of grammars.



2.LR-Parsers
–covers wide range of grammars.
SLR – simple LR parser
LR – most general LR parser
LALR – intermediate LR parser (lookhead LR parser)
–SLR, LR and LALR work same, only their parsing tables are different.

CFG G
LR

LALR

SLR

Operator-Precedence 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:
–e at the right side
–two adjacent non-terminals at the right side.

•Ex:
EAB EEOE EE+E |
Aa Eid E*E |
Bb O®+|*|/ E/E | id
not operator grammar not operator grammar operator grammar

Precedence Relations
In operator-precedence parsing, we define three disjoint precedence relations between
certain pairs of terminals.

65
a <. b b has higher precedence than a
a =· b b has same precedence as a
a .> b b has lower precedence than a

•The determination of correct precedence relations between terminals are based on the
traditional notions of associativity and precedence of operators. (Unary minus causes a
problem).

Using Operator-Precedence Relations

•The intention of the precedence relations is to find the handle of a right-sentential form,

<. with marking the left end,


=· appearing in the interior of the handle, and
.> marking the right hand.

•In our input string $a1a2...an$, we insert the precedence relation between the pairs of
terminals (the precedence relation holds between the terminals in that pair).

E E+E | E-E | E*E | E/E | E^E | (E) | -E | id

The partial operator-precedence table for this grammar

•Then the input string id+id*id with the precedence relations inserted will be:

$ <. id .> + <. id .> * <. id .> $

id + * $
id .> .> .>

+ <. .> <. . >


* <. .> .> .>

$ <. <. <.

To Find The Handles


1.Scan the string from left end until the first .> is encountered.
2.Then scan backwards (to the left) over any =· until a <. is encountered.
3.The handle contains everything to left of the first .> and to the right of the <. is
encountered.

$ <. id .> + <. id .> * <. id .> $ E  id $ id + id * id $


$ <. + <. id .> * <. id .> $ E  id $ E + id * id $
$ <. + <. * <. id .> $ E  id $ E + E * id $
$ <. + <. * .> $ E  E*E $ E + E * .E $

66
$ <. + .> $ E  E+E $E+E$
$$ $E$

Operator-Precedence Parsing Algorithm


The input string is w$, the initial stack is $ and a table holds precedence relations
between certain terminals
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();
}

Operator-Precedence Parsing Algorithm – Example


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

67
$+* $ * .> $ reduce E  E*E
$+ $ + .> $ reduce E  E+E
$ $ accept

How to Create Operator-Precedence Relations


 We use associativity and precedence relations among operators.
 .If operator O1 has higher precedence than operator O2,
o O1 .> O2 and O2 <. O1

 .If operator O1 and operator O2 have equal precedence,


o they are left-associative  O1 .> O2 and O2 .> O1
o they are right-associative  O1 <. O2 and O2 <. O1

 .For all operators


o O, O <. id, id .> O, O <. (, (<. O, O .> ), ) .> O, O .> $, and $
<. O

 .Also, let
o (=·) $ <. ( id .> ) ) .> $
o ( <. ( $ <. id id .> $ ) .> )
o ( <. id

Operator-Precedence Relations

+ - * / ^ id ( ) $
+ .> .> <. <. <. <. <. .> .>
- .> .> <. <. <. <. <. .> .>
* .> .> .> .> <. <. <. .> .>
/ .> .> .> .> <. <. <. .> .>68
^ .> .> .> .> <. <. <. .> .>
.> .> .> .> .> .> .>
( <. <. <. <. <. <. <. =·
) .> .> .> .> .> .> .>
$ <. <. <. <. <. <. <.

Handling Unary Minus


 Operator-Precedence parsing cannot handle the unary minus when we also the
binary minus in our grammar.
 The best approach to solve this problem, let the lexical analyzer handle this
problem.
 The lexical analyzer will return two different operators for the unary minus and
the binary minus.
 The lexical analyzer will need a lookhead to distinguish the binary minus from the
unary minus.
 •Then, we make
 O <. unary-minus for any operator
 unary-minus > O . if unary-minus has higher precedence
than O
 unary-minus <. O if unary-minus has lower (or equal)
precedence than O

Precedence Functions

 Compilers using operator precedence parsers do not need to store the table of
precedence relations.
 The table can be encoded by two precedence functions f and g that map terminal
symbols to integers.
 For symbols a and b.
 f(a) < g(b) whenever a <. b
 f(a) = g(b) whenever a =· b

69
 f(a) > g(b) whenever a .> b

Disadvantages of Operator Precedence Parsing


Disadvantages:

 It cannot handle the unary minus (the lexical analyzer should handle the unary
minus).
 Small class of grammars.
Advantages:

 simple
 powerful enough for expressions in programming languages

Error Recovery in Operator-Precedence Parsing

Error Cases:
 No relation holds between the terminal on the top of stack and the next input
symbol.
 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.
 .Decides the popped handle “looks like” which right hand side. And tries to
recover from that situation

LR Parsers
 The most powerful shift-reduce parsing (yet efficient) is:
 LR(k) parsing.
 L left to right Scanning
 R right-most Derivation
 (k) k lookhead (k is omitted  it is 1)

 LR parsing is attractive because:


 LR parsing is most general non-backtracking shift-reduce parsing, yet it is still
efficient.

70
 The class of grammars that can be parsed using LR methods is a proper superset
of the class of grammars that can be parsed with predictive parsers.
LL(1)-Grammars  LR(1)-Grammars
 An LR-parser can detect a syntactic error as soon as it is possible to do so a left-
to-right scan of the input.

LR-Parsers

 covers wide range of grammars.


 SLR – simple LR parser
 LR – most general LR parser
 LALR – intermediate LR parser (look-head LR parser)
 SLR, LR and LALR work same (they used the same algorithm), only their parsing
tables are different.

LR Parsing Algorithm

71
A Configuration of LR Parsing Algorithm
A configuration of a LR parsing is:
( So X1 S1 ... Xm Sm, ai ai+1 ... an $ )

Stack Rest of Input

Sm and ai decides the parser action by consulting the parsing action table. (Initial Stack
contains just So )

A configuration of a LR parsing represents the right sentential form:


X1 ... Xm ai ai+1 ... an $

Actions of A LR-Parser
1.shift s -- shifts the next input symbol and the state s onto the stack
( So X1 S1 ... Xm Sm, ai ai+1 ... an $ )  ( So X1 S1 ... Xm Sm ai s, ai+1 ... an $ )

2.reduce A  (or rn where n is a production number)


–pop 2|| (=r) items from the stack;
–then push A and s where s=goto[sm-r,A]

( So X1 S1 ... Xm Sm, ai ai+1 ... an $ ) è ( So X1 S1 ... Xm-r Sm-r A s, ai ... an $ )

–Output is the reducing production reduce A


.
3.Accept – Parsing successfully completed

4.Error -- Parser detected an error (an empty entry in the action table)

Reduce Action
pop 2|| (=r) items from the stack; let us assume that  = Y1Y2...Yr then push A and s
where s=goto[sm-r,A]

( So X1 S1 ... Xm-r Sm-r Y1 Sm-r ...Yr Sm, ai ai+1 ... an $ )

72
 ( So X1 S1 ... Xm-r Sm-r A s, ai ... an $ )

In fact, Y1Y2...Yr is a handle.


X1 ... Xm-r A ai ... an $  X1 ... Xm Y1...Yr ai ai+1 ... an $

(SLR) Parsing Tables for Expression Grammar

1) E  E+T
2) ET
3) T  T*F
4) TF
5) F  (E)
6) F  id

ACTION TABLE GOTO TABLE

state id + * ( ) $ E T F
0 s5 s4 1 2 3
1 s6 acc
2 r2 s7 r2 r2
3 r4 r4 r4 r4
4 s5 s4 8 2 3
5 r6 r6 r6 r6
6 s5 s4 9 3
7 s5 s4 10
8 s6 s1
9 r1 s7 1
r1 r1
73
10 r3 r3 r3 r3
11 r5 r5 r5 r5
Actions of A (S)LR-Parser -- Example

stack input action output


0 id*id+id$ shift 5
0id5 *id+id$ reduce by Fid Fid
0F3 *id+id$ reduce by TF TF
0T2 *id+id$ shift 7
0T2*7 id+id$ shift 5
0T2*7id5 +id$ reduce by Fid Fid
0T2*7F10 +id$ reduce by TT*F TT*F
0T2 +id$ reduce by ET ET
0E1 +id$ shift 6
0E1+6 id$ shift 5
0E1+6id5 $ reduce by Fid Fid
0E1+6F3 $ reduce by TF TF
0E1+6T9 $ reduce by EE+T EE+T
0E1 $ accept

Constructing SLR Parsing Tables – LR(0) Item


An LR(0) item of a grammar G is a production of G a dot at the some position of the
right side.
Ex: A  aBb Possible LR(0) Items: A  .aBb
(four different possibility) A  a.Bb
A  aB.b
A  aBb.
Sets of LR(0) items will be the states of action and goto table of the SLR parser.
A collection of sets of LR(0) items (the canonical LR(0) collection) is the basis for
constructing SLR parsers.
Augmented Grammar:
G’ is G with a new production rule S’S where S’ is the new starting symbol.

The Closure Operation

74
If I is a set of LR(0) items for a grammar G, then closure(I) is the set of LR(0) items
constructed from I by the two rules:
 Initially, every LR(0) item in I is added to closure(I).
 If A  .B is in closure(I) and B is a production rule of G; then B. will
be in the closure(I).
We will apply this rule until no more new LR(0) items can be added to closure(I).

The Closure Operation -- Example

E’  E .
closure({E’  E}) =

E  E+T { E’  E . kernel items

ET E  E+T.
T  T*F E T .
TF T  T*F.
F  (E) T F .
F  id F  (E).
.
F  id }

Goto Operation

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,

75
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  T*F, T  . F,

F. . (E), F  id }

.
goto(I,id) = { F  id }

Construction of The Canonical LR(0) Collection

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

goto function is a DFA on the sets in C.

The Canonical LR(0) Collection – Example


I0: E’  .E I1: E’  E. I6: E ® E+.T I9: E  E+T.

76
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)
F .id I3: T  F. I7: T  T*.F I11: F  (E).
F  .(E)
I4: F  (.E) F  .id
E  .E+T
E  .T I8: F  (E.)
T .T*F E  E.+T
T  .F
F  .(E)
F .id
I5: F  id.

Transition Diagram (DFA) of Goto Function

E T
I0 I1 I6
F
I9 * to I7

( to I3
T to I4
I2 I7 id
to I5
F
I3 *
F
I8 I10
I4
to I2 ( to I4
77
I5 to I3 id
( to I5
) I11
id T
id
F + to I6
(

Constructing SLR Parsing Table


(of an augumented grammar G’)

 Construct the canonical collection of sets of LR(0) items for G’.


o C{I0,...,In}
 .Create the parsing action table as follows
o If a is a terminal, A.a in Ii and goto(Ii,a)=Ij then action[i,a] is shift
j.
o •If A. is in Ii , then action[i,a] is reduce A  for all a in
FOLLOW(A) where AS’.
o •If S’S. is in Ii , then action[i,$] is accept.
o •If any conflicting actions generated by these rules, the grammar is not
SLR(1).
 .Create the parsing goto table
o for all non-terminals A, if goto(Ii,A)=Ij then goto[i,A]=j
 All entries not defined by (2) and (3) are errors.
 .Initial state of the parser contains S’.S

Parsing Tables of Expression Grammar

Action table Goto table


state id + * ( ) $ E T F
0 s5 s4 1 2 3
1 s6 ac
2 r2 s7 r2 c
r2 78
3 r4 r4 r4 r4
5 r6 r6 r6 r6
6 s5 s4 9 3
7 s5 s4 1
8 s6 s1 0
9 r1 s7 1
r1 r1
10 r3 r3 r3 r3
11 r5 r5 r5 r5

SLR(1) Grammar
An LR parser using SLR(1) parsing tables for a grammar G is called as the SLR(1) parser
for G.
If a grammar G has an SLR(1) parsing table, it is called SLR(1) grammar (or SLR
grammar in short).
Every SLR grammar is unambiguous, but every unambiguous grammar is not a SLR
grammar.

shift/reduce and reduce/reduce conflicts

If a state does not know whether it will make a shift operation or reduction for a terminal,
we say that there is a shift/reduce conflict.

If a state does not know whether it will make a reduction operation using the production
rule i or j for a terminal, we say that there is a reduce/reduce conflict.

If the SLR parsing table of a grammar G has a conflict, we say that that grammar is not
SLR grammar

Conflict Example
S  L=R I0: S’ .S I1:S’  S. I6:S  L=.R I9: S  L=R.
SR S  .L=R R  .L
L *R S  .R I2:S  L.=R L .*R

79
L  id L  .*R R  L. L  .id
RL L  .id
R  .L I3:S  R.

I4:L  *.R I7:L  *R.


Problem R  .L
FOLLOW(R)={=,$} L .*R I8:R  L.
= shift 6 L  .id
reduce by R  L
shift/reduce conflict I5: L  id.

Conflict Example2

S  AaAb I0: S’  .S
S  BbBa S  .AaAb
A S  .BbBa
B A.

80
B.

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

Constructing Canonical LR(1) Parsing Tables


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)

In some situations, A cannot be followed by the terminal a in a right-sentential form
when  and the state i are on the top stack. This means that making reduction in this
case is not correct.
S  AaAb SAaAbAabab SBbBaBbaba
S  BbBa
A Aab   ab Bba   ba
B AaAb  Aa  b BbBa  Bb  a

LR(1) Item

To avoid some of invalid reductions, the states need to carry more information.
Extra information is put into a state by including a terminal symbol as a second
component in an item.
A LR(1) item is:

.
A   ,a where a is the look-head of the LR(1) item
(a is a terminal or end-marker.)

When  ( in the LR(1) item A  .,a ) is not empty, the look-head does not have any
affect.
When  is empty (A  .,a ), we do the reduction by A only if the next input
symbol is a (not for any terminal in FOLLOW(A)).

81
A state will contain A  .,a1 where {a1,...,an} Í FOLLOW(A)
...
A  .,an

Canonical Collection of Sets of LR(1) Items

The construction of the canonical collection of the sets of LR(1) items are similar to the
construction of the canonical collection of the sets of LR(0) items, except that closure
and goto operations work a little bit different.

closure(I) is: ( where I is a set of LR(1) items)


–every LR(1) item in I is in closure(I)
.
–if A B,a in closure(I) and B is a production rule of G; then
B.,b will be in the closure(I) for each terminal b in FIRST(a) .

goto operation

•If I is a set of LR(1) items and X is a grammar symbol (terminal or non-terminal), then
goto(I,X) is defined as follows:
–If A  .X,a in I then every item in closure({A  X.,a}) will be in
goto(I,X).

Construction of The Canonical LR(1) Collection


Algorithm:
C is { closure({S’.S,$}) }
repeat the followings until no more set of LR(1) 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

goto function is a DFA on the sets in C.

82
A Short Notation for The Sets of LR(1) Items

A set of LR(1) items containing the following items


A  .,a1
...
A  .,an
can be written as

A  .,a1/a2/.../an

Canonical LR(1) Collection -- Example

S  AaAb I0: S’  .S ,$ s I1: S’ S. ,$


S BbBa S .AaAb ,$ A
A S  .BbBa ,$ I2: S  A.aAb ,$ a to I4
B A  . ,a B
B  . ,b I3: S  B.bBa ,$ b to I5
I4: S Aa.Ab ,$ A I6: S  AaA.b ,$ a I8: S  AaAb. ,$
A  . ,b
I5: S  Bb.Ba ,$ B I7: S  BbB.a ,$ b I9: S  BbBa. ,$
B  . ,a

83
Construction of LR(1) Parsing Tables

 1.Construct the canonical collection of sets of LR(1) items for G’.


C{I0,...,In}
 2.
 Create the parsing action table as follows
o If a is a terminal, A.a,b in Ii and goto(Ii,a)=Ij then action[i,a] is
shift j.
o If A.,a is in Ii , then action[i,a] is reduce Aa® where AS’.
o If S’S.,$ is in Ii , then action[i,$] is accept.
o If any conflicting actions generated by these rules, the grammar is not
LR(1).
 3.Create the parsing goto table
o for all non-terminals A, if goto(Ii,A)=Ij then goto[i,A]=j
 4.All entries not defined by (2) and (3) are errors.
 5.Initial state of the parser contains S’.S,$

LALR Parsing Tables

LALR stands for LookAhead LR.

LALR parsers are often used in practice because LALR parsing tables are smaller than
LR(1) parsing tables.
The number of states in SLR and LALR parsing tables for a grammar G are equal.
But LALR parsers recognize more grammars than SLR parsers.
yacc creates a LALR parser for the given grammar.
A state of LALR parser will be again a set of LR(1) items.

Creating LALR Parsing Tables


Canonical LR(1) Parser  LALR Parser
shrink # of states

This shrink process may introduce a reduce/reduce conflict in the resulting LALR parser
(so the grammar is NOT LALR)
But, this shrik process does not produce a shift/reduce conflict.

84
The Core of A Set of LR(1) Items

The core of a set of LR(1) items is the set of its first component.
Ex: S  L.=R,$  S  L.=R Core
R  L.,$ R  L.
We will find the states (sets of LR(1) items) in a canonical LR(1) parser with same cores.
Then we will merge them as a single state.
I1:L  id.,= A new state: I12: L  id.,=
 L  id.,$
I2:L  id.,$ have same core, merge them
We will do this for all states of a canonical LR(1) parser to get the states of the LALR
parser.
In fact, the number of the states of the LALR parser for a grammar will be equal to the
number of states of the SLR parser for that grammar

Creation of LALR Parsing Tables

Create the canonical LR(1) collection of the sets of LR(1) items for the given grammar.
Find each core; find all sets having that same core; replace those sets having same cores
with a single set which is their union.
C={I0,...,In}  C’={J1,...,Jm} where m  n
Create the parsing tables (action and goto tables) same as the construction of the parsing
tables of LR(1) parser.
–Note that: If J=I1  ...  Ik since I1,...,Ik have same cores
 cores of goto(I1,X),...,goto(I2,X) must be same.
–So, goto(J,X)=K where K is the union of all sets of items having same cores as
goto(I1,X).
If no conflict is introduced, the grammar is LALR(1) grammar. (We may only
introduce reduce/reduce conflicts; we cannot introduce a shift/reduce conflict)

Shift/Reduce Conflict

We say that we cannot introduce a shift/reduce conflict during the shrink process for the
creation of the states of a LALR parser.

85
Assume that we can introduce a shift/reduce conflict. In this case, a state of LALR parser
must have:

A   ,a. and B   a,b .


This means that a state of the canonical LR(1) parser must have:

.
A   ,a and .
B   a,c
But, this state has also a shift/reduce conflict. i.e. The original canonical LR(1)
parser has a conflict.
(Reason for this, the shift operation does not depend on lookaheads)

Reduce/Reduce Conflict

But, we may introduce a reduce/reduce conflict during the shrink process for the creation
of the states of a LALR parser.

.
I1 : A   ,a .
I2: A   ,b

.
B   ,b .
B   ,c


.
I12: A   ,a/b  reduce/reduce conflict

.
B  ,b/c

Using Ambiguous Grammars


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?

86
 –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

Error recovery

Error Recovery in LR Parsing


 An LR parser will detect an error when it consults the parsing action table and
finds an error entry. All empty entries in the action table are error entries.
 Errors are never detected by consulting the goto table.
 An LR parser will announce error as soon as there is no valid continuation for the
scanned portion of the input.
 A canonical LR parser (LR(1) parser) will never make even a single reduction
before announcing an error.
 The SLR and LALR parsers may make several reductions before announcing an
error.
 But, all LR parsers (LR(1), LALR and SLR parsers) will never shift an erroneous
input symbol onto the stack.

Panic Mode Error Recovery in LR Parsing

 Scan down the stack until a state s with a goto on a particular nonterminal A is
found. (Get rid of everything from the stack before this state s).
 Discard zero or more input symbols until a symbol a is found that can legitimately
follow A.
o –The symbol a is simply in FOLLOW(A), but this may not work for all
situations.
 The parser stacks the nonterminal A and the state goto[s,A], and it resumes the
normal parsing.
 This nonterminal A is normally is a basic programming block (there can be more
than one choice for A).
o –stmt, expr, block, ...

87
Phrase-Level Error Recovery in LR Parsing
 Each empty entry in the action table is marked with a specific error routine.
 An error routine reflects the error that the user most likely will make in that case.
 An error routine inserts the symbols into the stack or the input (or it deletes the
symbols from the stack and the input, or it can do both insertion and deletion).
o –missing operand
o –unbalanced right parenthesis

Intermediate languages

The front end translates a source program into an intermediate represntation from
which the back end generates the target code.

Intermediate code
Parser Static Intermedi Code
checker ate code generator
generator

Position of intermediate code

Advantage of machine independent intermediate form

 Retarget is facilitated. [a compiler for a different machine can be created by


attaching a back end for the new machine to an existing front end]
 A machineindepedent code optimiser can be applied to the intermediate
represntation

Intermediate languages

types of intermediate represntation

 Syntax trees

88
 Postfix notation
 Three address codes: [the semantic rules for generating three address code
from common programming languages]

Graphical representation

A syntax tree shows the hierarchical structure of a source program


A DAG gives the same information but in compact way because common sub
expression are identified.
Statements a:= b*-c +b*-c

89
postfix notation

linear represntation of a syntax tree

postfix for the Statements a:= b*-c +b*-c

a b c uminus * b c unminus * + assign

syntax tree for the assignment statement is produced by the syntax directed translation
Nonterminal S generates an assignment statement
+ and – are operators in the typical languages
operator associates and precedence are usual

syntax directed definition for the Statements a:= b*-c +b*-c

Production Semantic rule


S id:=E S.nptr := mknode (‘assign’, mkleaf(id, id.place),E.nptr)
EE1 + E2 E.nptr := mknode (‘+’ E1.nptr, E2.nptr)
EE1 * E2 E.nptr := mknode (‘*’ E1.nptr, E2.nptr)
E-E1 E.nptr := mknode (‘uminus’ E1.nptr)
E ( E1 ) E.nptr :=E1.nptr
Eid E.nptr := mkleaf(id,id.place)

Representation of syntax tree

 Each node as As a record

90
o Field – operator, pointers to children
 Node are allocated from an array

Each node as As a record

Node are allocated from an array

91
Three address codes

 Each statement has three address


o Two for operands
o One for result
 General form
o x:= y op z
o where x, y,z – name, constant , compiler generated temporaries
o op – operators
Examples
x+y*z
t1:= y * z
t2:=x + t1

Code for the syntax tree (Statements a:= b*-c +b*-c)

1. t1:= -c
2. t2:= b * t1
3. t3:= -c
4. t4:= b * t3
5. t5 := t2 + t4
6. a:= t5

Code for the DAG (Statements a:= b*-c +b*-c)


1. t1:= -c
2. t2:= b * t1
3. t5 := t2 + t2
4. a:= t5

Types of three address codes

92
o similar to assembly code
o statement can have symbolic labelsand there are statement for flow of
conrol.
o A symbolic label represnts the index of a three address statement in the
array holding the intermediate code.

Common three address statements

 Assignment statement of form x: = y op z


o Where op is binary or logical operation
 Assignment statement of form x:= op y
o op is a unary operation
o unary minus, logical negation, shift operators and conversion operators
 Copy statement of form x:=y
o The value y is assigned to x.
 The unconditional statement go to L
 Conditional jumps such as if x relop y goto L
o relop – relational operator
o {<,>.<>,=……}
 param x and call p, n for procedure calls and return y. y is optional
o param x1
o param x2
o param x3
o ……
o param xn
o call p, n
o call of the procedure p (x1,x2, x3..xn)
 Indexed statement of form x:=y[i] and x[i] := y
o the statement x[i] := y sets the contents of the location i units beyond x to
the value y.
o the statement x:=y[i]sets x to the value in the location I memory units
beyond location.
 Address and ponters assignments of the form x:=&y, x:=*y and *x=y
o x:=&y sets the value of x to be the location of y

Declarations
as new name is seen, the name is entered in the symbol tablewith the offset equal to the
current value of offset.

For each local name entry is created in symbol table along with other information
(type and the realive address of the storage for the name)

93
declaration in a procedure

the procedure enter (name, type, offset) creates an symbol table entry for name, gives it
type type and relative address offset in its data area.
attribute type represnts a type expression constructed from the basic types integer and real

P D {offset :=0}

D D ; D

D id : T { enter(id.name, T.type, offset);


Offset:=offset + T.width}
Tinteger { T.type := integer;
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 x T1.width }

TT1 { T.type := pointer (T1.type)


T.width:=4 }

Computing the types and relative address of the declared names

Keep track ofscope information

Local names of ecah procedure can be assigned relative address using the above approach
For nested procedures the processing of declaration is temperorily suspended.
P D
D D ; D | id : T | proc id; D ; S

S for statement
T for ypes are not shown
A new symbol table is created when a procedure declaration D  proc id D ; S is seen
and the entries for the declaration in D1 are created in the new table

The new table points back to the symbol table of the enclosing procedure.
The name represnted by id is local
Symbol tables of procedures are shown in fig

Procedure : sort, readarray, exchange, quicksort, partition

The symbol tables of the procedures readarray, exchange, quicksort points back to the
containing procedure sort.
Since partition is declared with in quicksort, its table points to that of quick sort

94
nil header
a
x
Readarray To readarray
exchange
quicksort To exchange

quicksort
exchange readarray

nil header nil header


i
partition

The semantic rules are defined in terms of the following operation

1. mktable(previous) creates a new symbol table and returns a pointer to the new
table
a. The argument previous points to the previously created symbol table

2. Enter (table, name, type, offset) creates an entry for name name in the symbol
table pointed by the table
3. Enter places type type and the relative address address offset in fields within the
entry.

4. Addwidth (table, width ) records the cumulative width of all the entries in table in
the header assiciated with this table.

5. Enterproc (table, name, newname) creates a new entry for the


procedure name in the symbol table pointed to the table. The nil header
argument newtable points to the symbol table for the procedure k
name. v
partition

Fields name in record


1. nil 2. header
The following production allows nonterminal T to generate
3. i 4.
records in addition to basic types , pointers and arrays
5. j 6.
T  record D end

95
The action to the translation scheme

T  record D end {T.type:=record(top(tblptr));


T.width:=top(offset);
Pop(tblptr);pop(offset)}
L {t:=mktable(nil);
Push(t, tblptr); push(0,offset)}

Setting up a symbol table for field name in a record

Flow control statement

Case statement

Our switch state syntax

Switch expression
Begin
Case value : statement
Case value : statement
Case value : statement
…………..
Case value : statement
default : statement
end

The translation of the switch is code to

1. evaluate the expression


2. find which value in the list of case is the same as the value of the expression.
a. If non of the value matches, then default
3. execute the statement associated with the value

syntax directed translation of the case statement

Switch expression
Begin
Case v1 : s1
Case v2 : s2
Case v3 : s3
…………..
Case vn-1 : sn-1
default : sn

96
end

Translation of case statement

Code to evaluate E into t


L1: code for s1
goto next
L2: code for s2
goto next

Ln-1: code for n-1


goto next
L n: code for s n
goto next
test: if t = V1 goto L1
if t = V2 goto L2
……
if t = V n-1 goto L n-1
goto L n
next:

Another Translation of case statement


Code to evaluate E into t
If t V1 goto L1
Code for S1
Goto next
L1: If t V2 goto L2
Code for S2
Goto next

L2: If t V3 goto L3


Code for S3
Goto next

Ln-2: If t V n-1 goto L n-1


Code for S n-1
Goto next
Ln-1: code for S n
Next:

Procedure calls

97
Calling sequence

The procedure is a programming construct


The procedure is imperative for a compiler to generate good code for procedure call and
returns
The run time routines handles procedure argument passing, calls, and returns

Grammar for simple procedure call.

1. S  call id (Elist)
2. Elist  Elist, E
3. Elist  E

Calling sequence

A sequence of action taken on entry to and exit from each procedure

When a procedure call occurs

1. When a procedure call occurs, space must be allocated for the activation
record of the called procedure.
2. The argument of the called procedure must be evaluated and made available to
the called procedure in a known place
3. Environment pointers must be established to enable the called procedure to
access data in the enclosing blocks
4. The state of the calling procedure must be Saved so it can resume execution
after the call.
5. Also the return address of the calling procedure is saved in a known place

6. The return address is the location of the the instruction that follows the call in
the calling procedure
7. Finally a jump to the beginning of the code for the called procedure must be
generated

When a procedure returns

98
When a function returns,
1. if the procedure is a function , the result must be stored in a known place .
2. The activatation record of the calling procedure must be restored.
3. A jump to the beginningof the code for the called procedure must be
generated

There is no exact division of the run-time tasks between the calling called procedure
Syntax directed translation

1. S  call id (Elist)
2. Elist  Elist, E
3. Elist  E

S  call id (Elist)
{ for each item p on queue do
emit (‘param’ p)
emit (‘call’ id.place)}

Elist  Elist, E
{ append E.place to the end of queue}

Elist  E
{ initilize the queue to contain only E.place}

1. We use param statements as place holders for the arguments


2. The called procedure is passed a pointer in the register to the first param
statements.
3. Pointers to any other arguments by using the proper offset from the base pointer.
4. If we do not want to mix the argument evaluating statement with the param
statement, we have to save the value of E.place, for each expression E in id
(E,E,E,..E)
5. If the parameters are passed to the called procedure by putting them on a stack, as
would normally be the case for the dynamically allocated data, there is no reason
not to mix evaluation and param statement
6. The data used to save this values is a queue

Code Generation

 A code generator takes an intermediate representation of a source program, and


produces an equivalent program as an output.
 The requirements on a code generator:
o –The output code must be correct.

99
o –The output code must be high quality.
o –It should make effective use of the resources of the target machine.
o –It should run efficiently.
 In theory, the problem of generating optimal code is undecidable.
 In practice, we use heuristic techniques to generate sub-optimal (good, but not
optimal) target code. The choice of the heuristic is important since a carefully
designed code generation algorithm can produce much better code than a naive
code generation algorithm.

Input to Code Generator

 The input of a code generator is the intermediate representation of a source


program (together with the information in the symbol table to figure out the
addresses of the symbols).
 The intermediate representation can be:
o –Three-address codes (quadraples).
o –Trees
o –Dags (Directed Acyclic Graphs)
o –or, other representations
 Code generator assumes that codes in the intermediate representation are free of
semantic errors and we have all the type conversion instructions in these codes.

Target Programs (Output of Code Generation)

 The output of the code generation is the target program.


 The target program can be in one of the following form:
o –absolute machine language
o –relocatable machine language
o –assembly language
o –virtual machine codes(Java..)
 If absolute machine language is used, the target program can be placed in a fixed
location, and immediately executed (WATFIV, PL/C).
 If relocatable machine language is used, we need a linker and loader to combine
the relocatable object files and load them. It is a flexible approach (C language)
 If assembly language is used, we need an assembler is need

Memory Management

 Implementation of static and stack allocation of data objects?

100
 How the names in the intermediate codes are converted into addresses in the
target code?
 The labels in the intermediate codes must be converted into the addresses of the
target machine instructions.
 A quadraple will match to more than one machine instruction. If that quadraple
has a label, this label will be the address of the first machine instruction
corresponding to that quadraple.

Instruction Selection

 The structure of the instruction set of the target machine determines the difficulty
of the instruction selection.
 –The uniformity and completeness of the instruction set are an important factors.
 Instruction speeds are also important.
 –If we do not care speed, the code generation is a straight forward job. We can
map each quadraple into a set of machine instructions. Naive code generation:
ADD y,z,x  MOV y, R0
ADD z, R0
MOV R0,x
 The quality of the generated code is determined by its speed and size.
 Instruction speeds are needed to design good code sequences.

Register Allocation

 Instructions involving register operands are usually shorter and faster than those
involving operands in memory.
 The efficient utilization of registers is important in generating good code
sequence.
 The use of registers is divided into two sub-problems:
o –Register Allocation – we select the set of registers that will be reside in
registers at a point in the program.
o –Register Assignment – we pick the specific register that a variable will
reside in.
 Finding an optimal assignment of registers is difficult.
o –In theory, the problem is NP-complete.
o –The problem is further complicated because some architectures may
require certain register-usage conventions such as address vs data
registers, even vs odd registers for certain instructions.

101
Choice of Evaluation Order

 The order of computations affect the efficiency of the target code.


 Some computation orders require less registers to hold intermediate results.
 Picking the best computation order is also another NP-complete problem.
 We will try to use the order used in the intermediate codes.
 But, the most important criterion for a code generator is that it should
produce correct code.
 We may use a less efficient code generator as long as it produces correct codes.
But we cannot use a code generator which is efficient but it does not produce
correct codes.

Target Machine

 To design a code generator, we should be familiar with the structure of the target
machine and its instruction set.
 nstead of a specific architecture, we will design our own simple target machine
for the code generation.
– We will decide the instruction set, but it will be closer actually machine
instructions.
– We will decide size and speeds of the instructions, and we will use them in
the creation of good code generators.
– Although we do not use an actual target machine, our discussions are also
applicable to actual target machines.

Our Target Machine

 Our target machine is a byte-addressable machine (each word is four-bytes).


 Our target machine has n general purpose registers – R0, R1,...,Rn-1
 Our target machine has two-address instructions of the form:
 op source,destination
 where op is an op-code, and source and destination are data fields.


ADD  add source to destination
SUB  subtract source from destination
MOV  move source to destination

Our Target Machine – Address Modes

102
 •The source and destination fields are not long enough to hold memory addresses.
Certain bit-patterns in these fields specify that words following the instruction
(the instruction is also one word) contain operand addresses (or constants).
 •Of course, there will be cost for having memory addresses and constants in
instructions.
 •We will use different addressing modes to get addresses of source and
destination.

MODE FORM ADDRESS ADDED COST


absolute M M 1
register R R 0
indexed c(R) c+contents(R) 1
indirect register *R contents(R) 0
indirect indexed *c(R) contents(c+contents(R)) 1
literal #c c 1

MOV R0,M  move contents of R0 into memory location M


MOV M,R0  move contents of memory location M into R0.
MOV 4(R0),M  move contents of 4+contents(R0) into
memory location M.
MOV *R0,R1  move contents of contents(R0) into R1.
MOV *4(R0),R1  move contents of 4+contents(R0) into R1.
MOV #13,R1  move the constant 13 into R1.

MOV R1,*4(R0)  move contents of R1 into the memory location


4+contents(R0) .

Our Target Machine – Instruction Costs


 The cost of an instruction is one plus the costs associated with the source and
destination address modes.
 This cost corresponds to the length (in words) of the instruction.
o –In real architectures, there are similar costs.
o –When we try to minimize the cost in time, we also minimize the cost in
space in our architecture (this may not be true in a real architecture).

MOV R0,a  cost is 2
MOV R0,R1  cost is 1
MOV 4(R0),b  cost is 3
MOV *R0,R1  cost is 1
MOV *4(R0),R1  cost is 2
MOV #13,R1  cost is 2

Run-Time Storage Organization

103
 Static Allocation -- the static allocation can be performed by just reserving
enough memory space for static data objects.
o –Static variables can be accessible by just using absolute memory address.
 Stack Allocation – the code generator should produce machine codes to allocate
the activation records (corresponding to intermediate codes).
o –Normally we will use a specific register to point (the beginning of) the
activation record, and we will use this register to access variables residing
in that activation record.
o We cannot know actual address of these stack variables until run-time.

Stack Allocation – Activation Record

Return address

SP
Return Value

Actual Parameters

Other Stuff

Local Variables

Temporaries
 All values in the activation record can be accessible from SP by a positive offset.

 And all these offsets are calculated at compile-time.

Possible Procedure Invocation

ADD #caller.recordsize,SP
MOV PARAM1,*8(SP) // save parameters
MOV PARAM2,*12(SP)
.
MOV PARAMn,*4+4n(SP)
. // saving other stuff
MOV #here+16,*SP // save return address
GOTO callee.codearea // jump to procedure
SUB #caller.recordsize,SP // return address

104
Possible Return from A Procedure Call
MOV RETVAL,*4(SP) // save the return value
GOTO *SP // return to caller

Run-Time Addresses
 Static Variables:
o static[12]  staticaddressblock+12

 if the beginning of the static address block is 100,


o MOV #0,,static[12]  MOV #0,112

 So, the static variables are absolute addresses and these absolute addresses are
evaluated at compile time (or load time).

Run-Time Addresses
 Stack Variables
o Stack variables are accesses using offsets from the beginning of the
activation records.

 local variable  *OFFSET(SP)

 non-local variable
o access links
o displays

Basic Blocks

A basic block is a sequence of consecutive statements (of intermediate codes –


quadraples) in which flow of control enters at the beginning and leaves at the end without
halt or possibility of branch (except at the end).

A basic block:
t1 := a * a
t2 := a * b
t3 := t1 – t2

Partition into Basic Blocks

105
Input: A sequence of three-address codes
Output: A list of basic blocks with each three-address statement in exactly one block.
Algorithm:
 1.Determine the list of leaders. The first statement of each basic block will be a
leader.
o The first statement is a leader
o Any statement that is the target of a jump instruction (conditional or
unconditional) is a leader.
o Any statement immediately following a jump instruction (conditional or
unconditional) is a leader.
 2.For each leader, its basic block consists of the leader and all statements up to
but not including the next leader or the end of the program.

Example Pascal Program

begin
prod := 0;
i := 1;
do begin
prod := prod + a[i] * b[i];
i := i + 1;
end
while i <= 20
end

Corresponding Quadraples

1: prod := 0
2: i := 1
3: t1 := 4*i
4: t2 := a[t1]
5: t3 := 4*i
6: t4 := b[t3]
7: t5 := t2*t4
8: t6 := prod+t5
9: prod := t6
10: t7 := i+1
11: i := t7
12: if i<=20 goto 3

106
Basic Blocks (Another Example)

x:=1; 01: mov 1,,x


y:=x+10; 02: add x,10,t1 leader
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
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:

Live Variables

 A three-address statement x := y op z is said


 to define x and
 to use (or reference) y and z

 A name in a basic block is said to be live at a given point if its value is used after
that point in the program (in that basic block or in another basic block).

 A basic block computes a set of expressions. These expressions are values of the
some names live on exit from the block.
 Two blocks are said to be equivalent if they compute the same set of expressions.

Transformations on Basic Blocks

107
 A number of transformations can be applied to a basic block without changing the
set of expressions computed by the block.
 Many of these transformations are useful for improving the quality of code that
will be generated from that block.
 These transformations are categorized into two groups:
o –Structure-Preserving Transformations
o –Algebraic Transformations.

Structure-Preserving Transformations

 The primary structure-preserving transformations are:


o –common sub-expression elimination
o –dead-code elimination
o –renaming of temporary variables
o –interchange of two independent adjacent statements.

Common Sub-expression Elimination

a := b+c a := b+c
b := a-d b := a-d
c := b+c  c := b+c
d := a-d d := b
a-d in 2 and 4 statements are common sub-expressions.

But, b+c in 1 and 3 are not common sub-expressions because the values of b in
those statements are different.

Dead-Code Elimination

 We say that x is dead at a certain point, if it is not used after that point in the
block (or in the following blocks).

108
 If x is dead at the point of the statement x := y op z, this statement can be safely
eliminated without changing the meaning of the block

Renaming Temporary Variables

Without changing the meaning of a block, we may rename temporary variables in that
block.
The new block with renamed variables is equivalent to the original block.

t1 := a+b  t2 := a+b
t2 := t1*c t1 := t2*c

Interchange of Statements
If two adjacent statements are independent they can be interchanged without affecting the
value of the basic block.

t1 := a+b t2 := x*y
t2 := x*y  t1 := a+b

Algebraic Transformations
x := x+0 eliminate this statement
x := y+0  x := y
x := x+1  INC ,,X
x := y**2  x := y*y

Flow Graphs

 We can add the flow-of-control information to the set of basic blocks making up a
program by constructing a directed graph called a flow graph.
 There is a directed edge from block B1 to block B2 if B2 immediately follows B1
in some sequence; that is if:
o –there is a conditional or unconditional jump from the last statement of B1
to the first statement of B2, or
o –B2 immediately follow B1 in the order of the program, and B1 does not
end with an unconditional jump.
 We say that B2 is successor of B1, and B1 is predecessor of B2.

109
Flow Graphs - Example

prod := 0
i := 1

t1 := 4*i
t2 := a[t1]
t3 := 4*i
t4 := b[t3]
t5 := t2*t4
t6 := prod*t5
prod := t6
t7 := i+1
i := t7
if i<=20 goto 3

Representation of Basic Blocks

 We may different data structures to represent basic blocks.


o –We may use linked lists of quadraples.
o –Instead of jumping to the location of the first quadraple of a basic block
(leader), we may assume that jump instructions jumps to the block. Thus,
if the structure of that basic block is changed because of the optimization,
we will not be affected.
 if i<10 goto B2
 An edge of the flow graph from block B1 to B2 does not specify the conditions
under which control flows from B1 to B2.
o –This information can be recovered from the jump instruction at the end of
the block.

110
Loops
 What is a loop in a flow graph?
 A loop in a flow graph is:
o 1.All nodes in a a loop is strongly connected. In other words, from any
node in a loop to any other node there is a path of length one or more, and
all nodes in that path are in that loop.
o 2.The collection of nodes in a loop has a unique entry. In other words, the
only way to reach a node in a loop from an outside node is to first go
through that unique entry.
 A loop that contain no other loop is called inner loop.
 We will assume that we can find all loops in a given flow graph.

Next Uses
 We say that a quadraple (x := y op z) uses names y and z.
 For each name in a quadraple, we would like to know the next use of that name.
 We also would like to know which names are live after the block; ie, they will be
used after this block.
o –Without a global live-analysis, we cannot determine which names will be
live after a block.
o –For simplicity, we may assume that all variables and some of temporaries
are live after each block.
 We use next use and live information about names to determine which names will
be in registers.

Computation of Next Uses


 We scan a block backward to collect next use information about names.
 For each quadraple, i: x := y op z
o 1.Attach i to the next use and liveness lists of x, y and z.
o 2.Mark x as “not live” and “no next use”
o 3.Mark y and z as “live” and “next use”
 Thus, we collect the usage information about all the names in a basic block.

Storage for Temporaries

111
If two temporaries are not live at the some time, we can pack these temporaries into a
same location.
We can use the next use information to pack temporaries.

t1 := a*a t1 := a*a
t2 := a*b t2 := a*b
t3 := 2*t2  t2 := 2*t2
t4 := t1+t3 t1 := t1+t2
t5 := b*b t2 := b*b
t6 := t4+t5 t1 := t1+t2

Simple Code Generator

 For simplicity, we will assume that for each intermediate code operator we have a
corresponding target code operator.
 We will also assume that computed results can be left in registers as long as
possible.
o –If the register is needed for another computation, the value in the register
must be stored.
o –Before we leave a basic block, everything must be stored in memory
locations.
 We will try to produce reasonable code for a given basic block.
 The code-generation algorithm will use descriptors keep track of register contents
and addresses for names.

Register Descriptors
 A register descriptor keeps track of what is currently in each register.
 It will be consulted when a new register is needed by code-generation algorithm.
 We assume that all registers are initially empty before we enter into a basic block.
This is not true if the registers are assigned across blocks.
 At a certain time, each register descriptor will hold zero or more names.

R1 is empty
MOV a,R1
R1 holds a
MOV R1,b
R1 holds both a and b

Address Descriptors

112
 An address descriptor keeps track of the locations where the current value of a
name can be found at run-time.
 The location can be a register, a stack location or a memory location (in static
area). The location can be a set of these.
 This information can be stored in the symbol table.

a is in the memory
MOV a,R1
a is in R1 and in the memory
MOV R1,b
b is in R1 and in the memory

A Code Generation Algorithm

 This code generation algorithm takes a basic block of three-address codes, and
produces machines codes in our target architecture.
 For each three-address code we perform certain actions.
 We assume that we have getreg routine to determine the location of the result of
the operation.

Code Generation Actions for x := y op z


 1.Invoke getreg function determine the location Lx of the result (x).
 2.Consult the address descriptor of y to determine the location Ly for y. Prefer a
register for Ly if y is in both in a register and in a memory location. If y is not
already in Lx, generate the instruction MOV Ly,Lx
 3.Generate the instruction OP Lz,Lx where Lz is the location of z. If z is in both
in a register and in memory, prefer the register. Update the address descriptor of x
to indicate that x is in Lx. If Lx is a register update its descriptor to indicate that it
contains x. Remove x from all other register descriptors.
 4.If y and z has no next uses, are not live on the exit from block, and they are in
registers, change the register descriptors so that they do not contain y or z
respectively.
 •At the end of block, if a name is live after block and it is not in memory, we
generate a move instruction for this name.

Function getreg for Lx (x := y op z)

113
 1.If y is in a register that does not hold no other names, and y is not live and has
no next use after x:=y op z, then return the register of y as Lx. Update the address
descriptor of y to indicate that y is no longer in Lx.
 2.If (1) fails, return an empty register for Lx if there is one.
 3.If (2) fails  if x has a next use in the block (or OP is a special instruction
needs a register), find a suitable occupied register and empty that register.
 4.If x is not used in the block, or no suitable register can be found, select the
memory location of x as Lx.
 5.A more sophisticated getreg function can be designed.

Code Generation Example

REG. DESC. ADDR. DESC.


Registers are empty
t:=a-b MOV a,R0 R0 contains a a in R0
SUB b,R0 R0 contains t t in R0
u:=a-c MOV a,R1 R1 contains a a in R0
SUB c,R1 R1 contains u u in R1
v:=t+u ADD R1,R0 R0 contains v v in R0
d:=v+u ADD R1,R0 R0 contains d d in R0
MOV R0,d d in R0 and memory

Handling Indexing Operations in Quadraples

a := b[i] We have to move i into a register first (if it is not in a register).

 i in a register  MOV b(Ri),a

 i in static data area  MOV Mi,R


 MOV b(R),a

 i in the stack  MOV Si(ARReg),R


 MOV b(R),a

Handling Pointer Operations in Quadraples

114
a := *p We have to move p into a register first (if it is not in a register).

 p in a register  MOV *Mp,a

 p in static data area  MOV Mp,R


o MOV *R,a

 p in the stack  MOV Si(ARReg),R


o MOV *R,a

Handling Conditional Statements

 Most of the machines uses a set of condition codes to indicate whether the last
quantity computed or loaded into a register is negative,zero, or positive.
 A compare CMP can be used to set these condition codes without evaluating the
value.
 Then a conditional jump instruction (based on these conditional codes) can be
used for designated condition: < = > <= >= 

if a<b goto l1  CMP a,b a:=b+c  MOV b,R0


CJ< l1 if a<0 goto l2 ADD c,R0
MOV R0,a
CJ< l2

Register Allocation

 Just holding values in registers during a single block may not produce good
results.
 We need some other techniques for register allocation:
o –Putting specific values into fix registers – activation record pointer, stack
pointer, ...
o –global analysis (across the blocks) of the variables to decide which
variables will be in registers. Frequently used variables are kept in the
registers. A frequently used variable in an inner loop should be in a
register.
o –Or, in some programming languages (such as C), programmer can use
register declarations to keep certain variables in registers.

Usage Counts

115
 Keeping a variable x in a register for the duration of a loop L è save
one unit of cost for each reference to x if x is in a register.
 If a variable x has subsequent usages in next blocks, it should stay in a register.
 So, if x stays in the register:
o –we save one unit every time x is referenced prior to any definition of x.
o –we save two units because we avoid a store of x at the end of a block.
 Benefit (approximate) of keeping x in the register is:

∑ (use( x , B)+2∗live( x , B ))
bocksBinL

 where use(x,B) is the number of times x is used in B prior to any definition of x.


 live(x,B) is 1 if x is live on exit from B, and is assigned a value in B; otherwise
live(x,B) is 0

Usage Counts – Example

bcdf

a:=b+c
d:=d-b B1
e:=a+f

acdef acdf
acde
b:=d+f
e:=a-c B3
f:=a-d B2

cdef cdef bcdef

b:=d+c B4

bcdef
bcdef - live

116
Usage Counts – Example

 benefit-a-inreg = 0+2*1 + 1+2*0 + 1+2*0 + 0+2*0 = 4


o use(a,B1)+2*(live,B1)+ use(a,B2)+2*(live,B2)+ use(a,B3)+2*(live,B3)+
use(a,B4)+2*(live,B4)
 benefit-b-inreg = 2+2*0 + 0+2*0 + 0+2*1 + 0+2*1 = 6

So, benefits:
a: 4 Thus, b and d must be in registers. If we can only use
b: 6 two registers.
c: 3 If we have a third one we can put one of a, e, f into register.
d: 6 For example, a into a register.
e: 4
f: 4

Code Sequence – if a,b,d in registers (globally)


MOV b,R1
MOV d,R2

MOV R1,R0
ADD c,R0
SUB R1,R2
MOV R0,R3
ADD f,R3
MOV R3,e

MOV R2,R2
ADD f,R1
MOV R0,R3 MOV R0,R3
SUB R2,R3 SUB c,R3
MOV R3,f MOV R3,e

MOV R2,R1
ADD c,R1 MOV R1,b
MOV R2,d

MOV R1,b
MOV R2,d

Register Allocation by Graph Coloring

117
 •First, for each symbol, we assign a symbolic register (we assume that we have
symbolic registers as much as we need).
 •A register-interference graph is created in which the nodes are symbolic
registers, and there is an edge between two nodes if the names in both nodes are
live at the same time.
 •Then, we have N machine registers
o –we apply N-coloring problem to this register-inference graph to find a
solution. If two nodes have an edge, they cannot have a same color (i.e.
they cannot be assigned to same machine registers because they are live at
the same time).
o –Since graph-coloring problem is NP-complete, we use certain heuristics
to get approximations to find the solutions

Register Allocation by Graph Coloring –Example

r1:=1
x:=1 s1:=1 r2:=2
y:=2 s2:=2 r3:=r1+r2
w:=x+y s3:=s1+s2 r3:=r1+1
z:=x+1 s4:=s1+1 r1:=r1*r2
u:=x*y s5:=s1*s2 r2:=r3*2
t:=z*2 s6:=s4*2

DAG Representation of Basic Blocks

 •Directed Acyclic Graphs (dags) can be useful data structures for implementing
transformations on basic blocks.
 •Using dags

118
o –we can easily determine common sub-expressions
o –We can determine which names are evaluated outside of the block, but
used in the block.
 •First, we will construct a dag for a basic block.
 •Then, we apply transformations on this dag.
 •Later, we will produce target code from a dag.

A dag for A Basic Block


 A dag for a basic block is:
o –Leaves are labeled with unique identifiers(names, constants). If the value
of a variable is changed in a basic block we use subscripts to distinguish
two different value of that name.
o –Interior nodes are labeled by an operator symbol
o –Interior nodes optionally may also have a sequence of names as labels.
 So, for each basic block we can create a dag for that basic block.

Three-Address Codes for A Basic Block


1: t1 := 4*i
2: t2 := a[t1]
3: t3 := 4*i
4: t4 := b[t3]
5: t5 := t2*t4
6: t6 := prod+t5
7: prod := t6
8: t7 := i+1
9: i := t7
10: if i<=20 goto 1

119
Corresponding DAG

Construction of DAGs
 •We can systematically create a corresponding dag for a given basic block.
 •Each name is associated with a node of the dag. Initially, all names are undefined
(i.e. they are not associated with nodes of the dag).
 •For each three-address code x := y op z
o –Find node(y). If node(y) is undefined, create a leaf node labeled y and let
node(y) to be this node.
o –Find node(z). If node(z) is undefined, create a leaf node labeled y and let
node(z) to be this node.
o –If there is a node with op, node(y) as its left child, and node(z) as its right
child  this is node is also treated as node(x).
o –Otherwise, create node(x) with op, node(y) as its left child, and node(z)
as its right child.

Applications of DAGs
 We automatically detect common sub-expressions.
 We can determine which identifiers whose values are used in the block. (the
identifier at leaves).
 We can create simplified quadraples for a block using its dag.
o –taking advantage of common sub-expressions
o –without performing unnecessary move instructions.

120
 In general, the interior nodes of a the dag can be evaluated in any order that is a
topological sort of the dag.
o –In topological sort, a node is not evaluated until its all children are
evaluated.
o –So, a different evaluation order may correspond to a better code
sequence.

Simplified Basic Block

1: t1 := 4*i
t1 := 4 * i 2: t2 := a[t1]
t2 := a [t1] 3: t3 := 4*i
t4: = b[t1] 4: t4 := b[t3]
t5 := t2 * t4  5: t5 := t2*t4
6: t6 := prod+t5
prod := prod + t5 7: prod := t6
i := i + 1 8: t7 := i+1
if i<=20 goto (1) 9: i := t7
10: if i<=20 goto 1

Some Problems

we have to careful when using arrays, pointers.

x := a[i] x:=a[i] unsafe optimization when


a[j]:=y z:=x i is equal to j.
z:=a[i] a[j]:= y

In these cases, we should not treat a[i] as common sub-expression.

Peephole Optimization
 Peephole Optimization is a method to improve performance of the target program
by examining a short sequence of target instructions (called peephole), and
replacing these instructions shorter and faster instructions.
o –peephole optimization can be applicable to both intermediate codes and
target codes.
o –the peephole can be in a basic block (sometimes can be across blocks).
o –we may need multiple passes to get best improvement in the target code.

121
o –we will look at certain program transformations which can be seen as
peephole optimization.

Redundant Instruction Elimination


MOV R0,a  MOV R0,a
MOV a,R0
•We can eliminate the second instruction, if there is no jump instruction jumping to that
instruction.

Unreachable Code
We may remove unreachable codes.
#define debug 0
.
.
if (debug==1) { print debugging info }

This is an unreachable code sequence. So we can eliminate it.

Flow-of-Control Optimizations
goto L1 goto L2
. 
L1: goto L2 L1: goto L2
----------------------------------------------
if a<b goto L1 if a<b goto L2
. 
L1: goto L2 L1: goto L2
-----------------------------------------------
goto L1 if a<b goto L2
.  goto L3
L1: if a<b goto L2 .
L3: L3:

Other Peephole Optimizations


 Algebraic Simplifications:
o –x := x+0
o –x := x*1
o –... more
 Reduction in Strength
o –x := y**2  x := y*y

122
o –x := y*2  x := lshift(y,1)
 Specific Machine Instructions
o –The target machine may specific instructions to implement specific
operations.
o –auto increment, auto decrement, ...

Generating Code From DAGs

 We can directly generate target code from a dag of a basic block.


 In fact, if the dag of the basic block is a tree, we can generate the optimal code
(optimal size in our target machine)
 We will look at an algorithm which produces optimal codes from trees.
 We will use the fewest number of temporaries.
 We may change the structure of the tree to get optimal code.
 The structure of the tree effects the generated code

Rearranging the Order


(a+b)-(e-(c+d))

t1 := a+b - t4
t2 := c+d
t3 := e-t2 + t1 - t3
t4 := t1-t3
a b e + t2

c d

Target Codes

only t4 is live on exit from the block, and we have only registers R0 and R1
t1:=a+b t2:=c+d t3:=e-t2 t4:=t1-t3 t2:=c+d t3:=e-t2 t1:=a+b t4:=t1-t3

MOV a,R0 MOV c,R0


ADD b,R0 ADD d,R0
MOV c,R1 MOV e,R1
ADD d,R1 SUB R0,R1
MOV R0,t1 MOV a,R0
MOV e,R0 ADD b,R0
SUB R1,R0 SUB R1,R0
MOV t1,R1 MOV R0,t4

123
SUB R0,R1
MOV R1,t4 Revised code sequence

Heuristic Ordering of DAGs


 The reordering may improve the generated target code.
o –In the previous example, we got an improvement because t4 is
immediately evaluated after t1 which is the left operand of t4.
 In selecting an ordering for the nodes of a dag, we are only constrained to
preserve the edge relationships of the dag.
 We can use a heuristic ordering algorithm, which attempts as far as possible to
make the evaluation of a node immediately follow the evaluation of its leftmost
argument.

Heuristic Ordering of DAGs – Algorithm


while (unlisted interior nodes remain)
{
select an unlisted node n, all of whose parents have been listed;
list n;
while (the leftmost child m of n has no unlisted parents and is not leaf) {
// since n was just listed, m is not listed yet
list m;
n ;= m;
}
}

Heuristic Ordering of DAGs – Example

* 1

+ 2 - 3

* 4

- 5 + 6

+ 7 c 8 d 11 e 12

124
a 9 b 10

The resulting list: 1234576  6754321 is evaluation order

t6:=d+e
t7:=a+b
t5:=t7-c
t4:=t5*t6
t3:=t4-e
t2:=t6+t4
t1:=t2*t3

Optimal Ordering for Trees

 If the dag representation of a basic block is a tree, we can have an algorithm


which finds the optimal order (shortest instruction sequence) for the evaluation of
the instructions in that basic block.
 The algorithm has two parts:
 1.Labeling – Label each node of the tree (bottom-up) with an integer that denotes
the fewest number of registers required to evaluate the tree with no stores of
intermediate results.
 2.Code Generation from The Labeled Tree – We traverse the tree by looking
the computed label of the tree, and emit the target code during that traversal.
 –For a binary operator, we evaluate the hardest operand first, then we evaluate the
other operand.
 –The hardest operand requires more registers

Labeling – Algorithm
if (n is a leaf) {
if (n is the leftmost child of its parent)
label(n) := 1;
else
label(n) := 0;
else { // interior node (assume that a binary operator)
if (label(child1)=label(child2))
label(n) := label(child1)+1
else
label(n) := max(label(child1),label(child2));
}

125
In general, if c1,c2,...,ck are children of n ordered by label
label(c1)>=label(c2)>=...>=label(ck)
label(n) := max(label(ci)+i-1) where i from 1 to k.

Labeling – Example

t4 2

t1 1 t3 2

a 1 b 0 e 1 t2 1

c 1 d 0

Code generation From Labeled Tree


 This algorithm takes a labeled tree as input, and generates the target code and
leaves the the result of the tree in the register R0.
 The algorithm is a recursive procedure.
 We have two stacks:
o –rstack – register stack. Initially R0,R1,...,R(r-1)
o –tstack – temporaries stack. Initially T0,T1,...
 We have operations
o –swap(rstack) – to swap two top registers in rstack
o –pop(stack) – pop an item from the stack, and return it
o –push(stack,item) – push the given item into the stack
 –For a binary operator, we evaluate the hardest operand first, then we evaluate the
other operand.
 –The hardest operand requires more registers

Code generation From Labeled Tree – Algorithm


procedure gencode (n) {
// case 0
if (n is a left leaf representing operand x and n is the leftmost child of its parent)
emit(‘MOV’ x ‘,’ top(rstack));
else if (n is an interior node with operator op, left child n1, and
right child n2) {

126
// other cases

// case 1 – n2 is just simple operand


if (label(n2)=0) {
let x be the operand represented by n2;
gencode(n1);
emit(op x ‘,’ top(rstack));
}
// case 2 – n2 is harder
else if (1<=label(n1)<label(n2) and label(n1)<r) {
swap(rstack);
gencode(n2);
R := pop(rstack); // R holds the result of n2
gencode(n1);
emit(op R ‘,’ top(rstack));
push(rstack,R);
swap(rstack);
}
// case 3 – n1 is harder
else if (1<=label(n2)<=label(n1) and label(n2)<r) {
gencode(n1);
R := pop(rstack); // the result of n1 in R
gencode(n2);
emit (op top(rstack) ‘,’ R);
push(rstack,R);
}

// case 4, both labels >= r


else {
gencode(n2);
T := pop(tstack);
emit(‘MOV’ top(rstack) ‘,’ T);
gencode(n1);
push(tstack,T);
emit(op T ‘,’ top(rstack))
}

Codegen From Labeled Tree – Example

gencode(t4) case2 R1 R0
gencode(t3) case3 R0 R1
gencode(e) case0 R0,R1
MOV e,R1
gencode(t2) case1 R0
gencode(c) case0 R0

127
MOV c,R0
ADD d,R0
SUB R0,R1
gencode(t1) case1 R0
gencode(a) case0 R0
MOV a,R0
ADD b,R0
SUB R1,R0

Optimal Code
 codegen produce optimal code for our target machine if we assume that
o –there are no common sub-expression
o –there are no algebraic properties of operators which effect the timing.
 Algebraic properties of operators (such as commutative, associative) may effect
the generated code.
 When there are common sub-expressions, the dag will no longer be a tree. In this
case, we cannot apply the algorithm directly.

GENCODE for general DAGs

 If the dag of a basic block is not tree, we cannot apply gencode procedure directly
to that dag.
 We partition the dag into trees, and apply gencode to these trees. Then we
combine the solutions. This may not be optimal solution, but it will be very good
solution.
 Each shared node will be the root of a tree.
 Then, we can put these trees into an evaluation order.

Partitioning a dag into trees


1 1

2 3 2 3

4 7 4 4 9

5 6

7 8 9 4

128
5 6

7 8 8 9

7 8 9

A Dynamic Programming Alg. for Code Generation


 We can have a general purpose algorithm (using dynamic programming) for
different target machines.
 This algorithm can be applicable to the broad class of register machines with
complex instructions.
 We will assume that we have a similar target machine:
o –r machine registers
o –load instruction
o –store instruction.
o –although each instruction can have different cost, for simplicity we will
assume that the cost of each of them is one unit.

Principle of Dynamic Programming


OP

E1 E2

 Optimal solution of this node can be found:


o –finding the optimal solution E1 and E2
o –and combining these optimal solutions
 What is the optimal solution for E1 with i registers (where i<=r)?
 First, we evaluate optimal solutions (with different number registers) for the
children, then we find the optimal solution for the node

Contiguous Evaluation

 The contiguous evaluation of a tree is:


o –first evaluate left sub-tree , then evaluate right sub-tree, and the root.
o –Or, first evaluate the right sub-tree, then evaluate the left sub-tree, and
finally the root.

129
 In non-contiguous evaluations, we may mix the evaluations of the sub-trees.
 For any given machine-language program P (for register machines) to evaluate an
expression tree T, we can find an equivalent program Q such that:
 1.Q does not have higher cost than P
 2.Q uses no more registers than
 3.Q evaluates the tree in a contiguous fashion.
 –This means that every expression tree can be evaluated optimally by a
contiguous program.

Example
 Assume that we have the following machine codes, and the cost of each of them is
one unit.
o –mov M,Ri
o –mov Ri,M
o –mov Ri,Rj
o –OP M,Ri
o –OP Rj,Ri
 Assume that we have only two registers R0 and R1.
 First, we have to evaluate cost arrays for the tree.

130

You might also like