unit v compiler design - wordpress.com · unit v compiler design 2 | p a g e types of symbol table...

34
UNIT V Compiler Design 1 | Page UNIT-V Symbol Table & Run-Time Environments Symbol Table Symbol table is a data structure used by compiler to keep track of semantics of variable. i.e. symbol table stores the information about scope and binding information about names. Symbol table is built in lexical and syntax analysis phases. It is used by various phases as follows, semantic analysis phase refers symbol table for type conflict issues. Code generation refers symbol table to know how much run-time space is allocated? What type of space allocated? Use of symbol table To achieve compile time efficiency compiler makes use of symbol table It associates lexical names with their attributes. The items to be stored in symbol table are, Variable name Constants Procedure names Literal constants & strings Compiler generated temporaries Labels in source program Compiler uses following types of information from symbol table Data type Name Procedure declarations Offset in storage In case of structure or record, a pointer to structure table For parameters, whether pass by value or reference Number and type of arguments passed Base address

Upload: buihanh

Post on 28-May-2018

219 views

Category:

Documents


0 download

TRANSCRIPT

UNIT V Compiler Design

1 | P a g e

UNIT-V

Symbol Table & Run-Time Environments

Symbol Table

Symbol table is a data structure used by compiler to keep track of

semantics of variable.

i.e. symbol table stores the information about scope and binding

information about names.

Symbol table is built in lexical and syntax analysis phases.

It is used by various phases as follows, semantic analysis phase refers

symbol table for type conflict issues. Code generation refers symbol table

to know how much run-time space is allocated? What type of space

allocated?

Use of symbol table

To achieve compile time efficiency compiler makes use of symbol table

It associates lexical names with their attributes.

The items to be stored in symbol table are,

Variable name

Constants

Procedure names

Literal constants & strings

Compiler generated temporaries

Labels in source program

Compiler uses following types of information from symbol table

Data type

Name

Procedure declarations

Offset in storage

In case of structure or record, a pointer to structure table

For parameters, whether pass by value or reference

Number and type of arguments passed

Base address

UNIT V Compiler Design

2 | P a g e

Types of symbol table

Ordered symbol table

Here , the entries of variables are made in alphabetical order.

Searching of ordered symbol table can be done using linear and binary

search.

Advantages :

The searching of particular variable is efficient.

Relationship between variables can be established easily.

Disadvantages:

Insertion of element is costly if there are large no. of entries in the

table.

Unordered symbol table

In this type of table, variable entries are not made in sorted manner.

Each time before inserting a variable in the table, a lookup is made to

check whether it is already present in the symbol table or not.

If the variable is not present, then an entry is made.

Advantage:

Insertion of variable is easier.

Disadvantage:

Searching is done using linear search.

For larger tables the method turns to be inefficient, because lookup is

made before every insertion.

How names are stored in symbol table?

There are two ways to store the names in symbol table.

Fixed-length name:

A fixed space is allocated for every symbol in the table. Space is wasted

in this type of storage, if name of the variable is small.

UNIT V Compiler Design

3 | P a g e

For example,

Variable- length names:

The amount of space required by string is used to store the names.

The name can be stored with the help of starting index and length of each

name.

E.g,

Name

Starting

Index length

Atribute

0 10

10 4

14 2

16 2

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

c a l c u l a t e $ s u m $ a $ b $

Name

Atribute

C A L C U L A T E Float…

S U M Float…

A Int…

B Int…

UNIT V Compiler Design

4 | P a g e

Symbol table management Symbol table management is required for the following reasons,

For quick insertion of identifier and related information.

For quick search of identifier.

Following are commonly used data structures for symbol table

construction

List Data structure

Self organizing list/ Linked list

Binary tree

Hash tables

List data structure

Linear list is the simplest kind of mechanism to implement symbol table.

In this method array is used to store names and associated information.

New names can be added in the order they arrive.

The pointer available is maintained at the end of all stored records.

Name 1 Info 1

Name 2 Info 2

Name 3 Info 3

.

.

.

.

.

.

Name n Info n

To retrieve information about some name we start from the beginning and

go on searching up to available pointer.

UNIT V Compiler Design

5 | P a g e

If we reach the available pointer without finding the name, then we get an

error “use of undeclared name”.

While inserting a new name we should ensure that it should not be already

there. If it is already there, then another error occurs, i.e.. “multiple

defined name”.

Advantage is it takes less amount of space.

Self organizing list

This symbol table representation uses linked list. A link field is added to

each record.

We search the records in the order pointed by the link field.

A pointer “first”, is maintained to point to the first record of the symbol

table.

When the name is referenced or created it is moved to the front of the list.

The most frequently referred names will be tend to be front of the list.

Hence access time to most frequently referred names will be the least.

Insertion is easier.

Binary trees

The symbol table is represented as a binary tree as follows,

Left child

Symbols Information Right child

The left child stores address of previous symbol and right child stores

address of next symbol.

The symbol field is used to store the name of the symbol and information

field is used to store all attributes/information of the symbol.

The binary tree structure is basically a BST in which left side node is

always less and right side node is always more than the parent node.

Hence, insertion of symbol is efficient.

Searching process is efficient.

create a BST structure for following

UNIT V Compiler Design

6 | P a g e

Int m, n, p;

Int compute(int a, int b, int c)

{

t=a + b+ c;

Return (t); }

Void main()

{

Int k;

K= compute(10,20,30);

}

Hash tables

Hashing is an important technique used to search the records of symbol

table. This method is superior to list organization.

In hashing scheme two tables are maintained- a hash table and a symbol

table.

The hash table consists of k entries from o to k-1. these entries are

basically pointers to the symbol table pointing to the names in the symbol

table.

To determine whether the name is there in the symbol table, we use a hash

function ” h” such that h(name) will result any integer between o to k-1.

We can search any name using the function.

The hash function should result in uniform distribution of names in

symbol table.

The hash function should be such that there will be minimum no of

collisions.

Advantages of hashing is quick search is possible and the disadvantage is

it is complicated to implement. Extra space is required. Obtaining scope

of variables is difficult.

UNIT V Compiler Design

7 | P a g e

Runtime environment

The compiler demands a block of memory to the OS. This memory is

utilized for running or executing the compiled program.

This block of memory is called run time storage.

The run time storage is subdivided to hold the following,

The generated target code

Data objects

Information which keeps track of procedure activations

The size of generated code is fixed. Hence the target code occupies

statically determined area of the memory.

Compiler places the target code at the lower end of the memory.

The amount of memory required by the data objects is known at the

compile time and hence data objects also can be placed at the static data

area of memory.

To maximize the utilization of space at run time, the other two areas, stack

and heap are used at opposite ends of the remaining address space.

Stack is used to store data structures called activation records that gets

generated during procedure calls.

Heap area is the area of run time storage which is allocated to variables

during run-time.

Size of stack and heap is not fixed. i.e. it may grow or shrink during

program execution.

Storage Organization The executing target program runs in its own logical address space in which each

program value has a location.

The management and organization of this logical address space is shared between

the complier, operating system and target machine. The operating system maps the

logical address into physical addresses, which are usually spread throughout

memory.

UNIT V Compiler Design

8 | P a g e

Run-time storage comes in blocks, where a byte is the smallest unit of

addressable memory. Four bytes form a machine word. Multi byte objects

are stored in consecutive bytes and given the address of first byte.

The storage layout for data objects is strongly influenced by the addressing

constraints of the target machine.

A character array of length 10 needs only enough bytes to hold 10

characters, a compiler may allocate 12 bytes to get alignment, leaving 2

bytes unused.

This unused space due to alignment considerations is referred to as padding.

The size of some program objects may be known at run time and may be

placed in an area called static.

The dynamic areas used to maximize the utilization of space at run time are

stack and heap.

Storage organization strategies

Three different strategies based on the division of run time storage

Static allocation

Stack allocation

Heap allocation

Static Allocation

The size of data objects is known at compile time. The names of these

objects are bound to storage at compile time only and such allocation is

called as static allocation. Amount of storage do not change during run

time. At compile time, the compiler can fill the addresses at which the

target code can find the data it operates on.

UNIT V Compiler Design

9 | P a g e

Main limitation is recursive procedures are not supported by this type of

allocation because of the static nature.

Stack allocation

Here the storage is organized as a stack. (LIFO)

It is also called as control stack.

As activation begins the activation records are pushed onto the stack and

on completion of this activation the corresponding activation record is

popped out.

The local variables are stored in each activation record. Hence locals are

bound to corresponding activation record on each fresh activation.

The data structure can be created dynamically for stack allocation.

Here memory addressing is done using pointers or index registers hence

slower than static allocation.

Heap allocation

If the values of non-local variables must be retained even after the

activation record then such a retaining is not possible in stack allocation

because of its LIFO nature. Hence, heap allocation is used in such

situations.

The heap allocation allocates continuous block of memory when required

for storage of activation records or other data objects.

This memory can be deactivated when activation ends.

Heap management can be done by creating a linked list for the free blocks

and when any memory is deallocated that block can be appended to the

list.

UNIT V Compiler Design

10 | P a g e

Activation record

Activation record is a block of memory used for managing information

needed by a single execution of a procedure. The contents of activation

record are,

Temporaries: temporary variables used during evaluation of expressions.

Local data: it is the data that is local to the execution of procedure.

Saved machine status: this field holds information regarding to the status

of machine, before calling the procedure. This field contains the machine

registers and PC.

Control link: it is optional field. It points to activation record of calling

procedure. This link is also called as dynamic link.

Access link: it may be needed by the called procedure but found else

where, i.e. in another activation record.

Actual parameters: passed during call.

Return value: stores result of function call.

UNIT V Compiler Design

11 | P a g e

Example: Sketch of a quicksort program

Activation for Quicksort

UNIT V Compiler Design

12 | P a g e

Activation tree representing calls during an execution of quicksort

Downward-growing stack of activation records

UNIT V Compiler Design

13 | P a g e

Block and non block structure storage allocation

The storage allocation is done for two types of data,

Local data

Non-local data

The local data can be handled using activation record whereas non local data

can be handled using scope information.

Access to Local Data

The local data can be accessed with help of activation record.

The offset relative to base pointer of an activation record points to local data

variables within an activation record.

Hence, Reference to any variable x in procedure= Base pointer pointing to start

of procedure + offset of variable x from base pointer

E.g. consider the following,

Procedure A

Int a;

Procedure B

Int b;

Body of B;

Body of A;

Contents of stack along with base pointer is shown below,

Return Value

Saved Registers

Parameters

Locals: a

Activation

record for A offset

UNIT V Compiler Design

14 | P a g e

Access to non local Data

A procedure may sometimes refer to variables which are not local to it.

Such variables are called as non-local variables.

For the non local names there are two types of scope rules such as static and

dynamic.

The static scope rule is also called as lexical scope. In this type the scope is

determined by examining the program text.

The languages that use the static scope rules are called as block structured

language.

The dynamic scope rules determine the scope of declaration of the names at

run time by considering the current activation.

Static or lexical scope

Block is a sequence of statements containing the local data declarations and

enclosed within the delimiters.

The blocks can be nesting in nature.

The scope of declaration in a block structured language is given by most

closely nested loop or static rule.

offset

Activation record for B

Activation record for A

UNIT V Compiler Design

15 | P a g e

E.g.

Scope_test()

{

Int p, q;

{

Int p;

{

Int r;

}

……

}

….

{

Int q, s,t;

}

}

The storage for the names corresponding to a particular block can be shown

below.

B1

B2 B3

B4

UNIT V Compiler Design

16 | P a g e

The lexical scope can be implemented using access link and displays

Access link:

The implementation of lexical scope can be obtained by using pointer to each

activation record. These pointers are called access links.

If procedure P is nested within a procedure q then access link of p points to

access link of most recent activation record of procedure q.

Display:

It is expensive to traverse down access link every time when a particular non

local variable is accessed. To speed up the access to non locals can be achieved

by maintaining an array of pointers called display.

In display,

An array of pointers to activation record is maintained.

Array is indexed by nesting level

The pointers point to only accessible activation record.

The display changes when a new activation occurs and it must be reset

when control returns from the new activation.

Display stack

The advantage of using display is that , if p is executing, and it needs to access

element x belonging to some procedure q, we need to look only in display[i] where

i is the nesting depth of q.We will follow the pointer display[i] to the activation

record for q wherein x is found at some offset.

The compiler knows what ‘i’ is, so it can access display[i] easily.Hence no need to

follow a long chain of access links.

0

1

2

UNIT V Compiler Design

17 | P a g e

Heap Management

Heap is a portion of memory that holds data during the lifetime of the program.

In the heap, the static memory requirements such as global variables will be

allocated space.

In addition, any memory that is supposed to be used throughout the program is

also stored in heap.

Hence managing the heap is important.

A special software called Memory Manager manages allocation and

deallocation of memory.

Memory Manager:

Two basic functions of memory manager are,

Allocation:when a program requests memory for a variable, then the

memory manager produces a chunk of heap memory of requested size.

Deallocation:The memory manager deallocates the space adds it to the

pool of free space so that it can be reused.

Desired Properties of memory managers:

Space efficiency: should minimize the total heap space required by a

program

Program efficiency: should make better use of space, to run the program

faster to increase efficientcy.

Low overhead: allocation and deallocation should be effiecient.

Two types of memory allocation techniques are,

Explicit allocation

Implicit allocation

Explicit allocation is done using functions like new and dispose.

Whereas, implicit memory allocation is done by compiler using run time

support packages.

Explicit allocation

This is the simplest technique of explicit allocation where the size of block for

which memory is allocated is fixed.

UNIT V Compiler Design

18 | P a g e

In this technique, a free list is used which is a set of free blocks. Memory is

allocated from this list.

The blocks are linked each other in a list structure.

The memory allocation can be done by pointing previous node to the newly

allocated block.

Similarly deallocation can be done by dereferencing the previous link.

Explicit allocation of variable sized blocks

Due to frequent memory allocation and deallocation, the heap memory

becomes fragmented.

For allocating variable sized blocks we use strategies like first fit, worst fit and

best fit.

Implicit allocation

The implicit allocation is performed using user program and runtime packages.

The run time package is required to know when the storage block is not in use.

The format of storage block is given below.

Reference count: (RC) It is a special counter used during implicit allocation. If

any block is referred by any other block then its reference count is incremented

by one.

If the value of RC is 0, then it can be deallocated.

Marking techniques: it is an alternative approach whether the block is in use or

not. In this method , the user program is suspended temporarily and frozen

pointers are used to mark the blocks which are in use.

Parameter passing Mechanism

Types of parameters

Formal : parameters used in the function definition.

Actual: parameters passed during function call.

What is l-value & r-value?

R-value is the value of expression which is present at the right side of

assignment operator.

L-value is the address of memory location( or variable) present at the left

UNIT V Compiler Design

19 | P a g e

side of the assignment operator.

What are the Parameter passing methods ?

Call by value/pass by value.

Call by address/ Pass by reference.

Pass by copy-restore

Pass by Name

Example: swapping of two numbers( using ‘C ’ language)

Pass by Value:

In pass by value mechanism, the calling procedure passes the r-value of actual

parameters and the compiler puts that into the called procedure‟s activation record.

Formal parameters then hold the values passed by the calling procedure. If the

values held by the formal parameters are changed, it should have no impact on the

actual parameters.

Pass by Reference:

In pass by reference mechanism, the l-value of the actual parameter is copied to the

activation record of the called procedure. This way, the called procedure now has

the address (memory location) of the actual parameter and the formal parameter

refers to the same memory location. Therefore, if the value pointed by the formal

parameter is changed, the impact should be seen on the actual parameter as they

should also point to the same value.

Pass by Copy-restore:

This parameter passing mechanism works similar to „pass-by-reference‟ except

that the changes to actual parameters are made when the called procedure ends.

Upon function call, the values of actual parameters are copied in the activation

record of the called procedure. Formal parameters if manipulated have no real-time

effect on actual parameters (as l-values are passed), but when the called procedure

ends, the l-values of formal parameters are copied to the l-values of actual

parameters.

UNIT V Compiler Design

20 | P a g e

Example:

int y;

calling_procedure()

{

y = 10;

copy_restore(y); //l-value of y is passed

printf y; //prints 99

}

copy_restore(int x)

{

x = 99; // y still has value 10 (unaffected)

y = 0; // y is now 0

}

When this function ends, the l-value of formal parameter x is copied to the actual

parameter y. Even if the value of y is changed before the procedure ends, the l-

value of x is copied to the l-value of y making it behave like call by reference.

Pass by Name

Languages like Algol provide a new kind of parameter passing mechanism that

works like preprocessor in C language. In pass by name mechanism, the name of

the procedure being called is replaced by its actual body. Pass-by-name textually

substitutes the argument expressions in a procedure call for the corresponding

parameters in the body of the procedure so that it can now work on actual

parameters, much like pass-by-reference.

Garbage collection

The process of collecting unused memory (which was previously allocated to

variables/objects and no longer needed now) in a program and pool it in a form

to be used by other application is called as GARBAGE COLLECTION.

Few languages support automatic garbage collection and in other languages we

UNIT V Compiler Design

21 | P a g e

need to explicitly use the garbage collection techniques.

Basic idea is keep track of what memory is referenced and when it is no longer

accessible, reclaim the memory. Example: linked list.

Reference count garbage collectors

Garbage collection(GC) works as follows.

When an application needs some free space to allocate nodes and there is

no free space available to allocate memory to the objects then a system

routine called GARBAGE COLLECTOR is invoked.

The routine then searches the system for the nodes that are no longer

accessible from the external pointers. These nodes can be made available

for reuse by adding them to the available pool.

Reference count is a special counter used during implicit memory

allocation. If any block is referred by any other block then its reference

count is incremented by one.

UNIT V Compiler Design

22 | P a g e

The block is deallocated as soon as the reference count value becomes

zero.

These kind of garbage collectors are called as reference count garbage

collectors.

Advantages of GC are,

The manual memory management done by the programmer (using malloc,

realloc, free) is time consuming and error prone.

Reusability of memory is achieved using garbage collection.

Disadvantages are,

The execution of program is stopped or paused during garbage collection.

Sometimes a situation called Thrashing occurs.

Introduction

CODE GENERATION

The final phase of a compiler is code generator.

It receives an intermediate representation (IR) along with information in

symbol table.

Produces a semantically equivalent target program

Code generator main tasks:

Instruction selection

Register allocation and assignment

Instruction ordering

UNIT V Compiler Design

23 | P a g e

Issues in the Design of Code Generator:

The most important criterion is that it produces correct code

Input to the code generator: IR + Symbol table

We assume front end produces low-level IR, i.e. values of names in it can

be directly manipulated by the target machine.

Syntactic and semantic errors have been already detected.

The target program

Common target architectures are: RISC, CISC and Stack based machines.

In this chapter, we use a very simple RISC-like computer with addition of

some CISC-like addressing modes

Instruction selection:

The code generated must map the IR program into a code sequence that can be

executed by the target machine. The complexities of this mapping are,

The level of the IR(Intermediate representation)

The nature of the instruction-set architecture

The desired quality of the generated code.(speed & size)

Register allocation

Two sub problems are,

Register allocation: selecting the set of variables that will reside in

registers at each point in the program

Resister assignment: selecting specific register that a variable reside in.

Evaluation ordering

The order in which computations are performed can affect the efficiency

of the target code.

Because, some computation orders fewer registers to hold intermediate

results than other.

The Target Language

The target language performs following operations,

UNIT V Compiler Design

24 | P a g e

Load operations: LD r,x and LD r1, r2

Store operations: ST x,r

Computation operations: OP dst, src1, src2

Unconditional jumps: BR L

Conditional jumps: Bcond r, L like BLTZ r, L

The simple target machine model uses following addressing modes.

variable name: x

indexed address: a(r) like, LD R1, a(R2) means R1=contents(a + contents(R2))

integer indexed by a register : like LD R1, 100(R2)

Indirect addressing mode: *r means the memory location found in the location

represented by the contents of register r and *100(r) means

content(contents(100+contents(r)))

immediate constant addressing mode: like LD R1, #100

The three address statement x=y-z can be implemented by the machine instructions

LD R1,Y

LD R2,Z

SUB R1,R2

ST X,R1

suppose an array a whose elements are 8-byte real numbers. Then,

b = a [i]

LD R1, i //R1 = i

MUL R1, 8 //R1 = Rl * 8

LD R2, a(R1) //R2=contents(a+contents(R1))

ST b, R2 //b = R2

a[j] = c

LD R1, c //R1 = c

LD R2, j // R2 = j

MUL R2, 8 //R2 = R2 * 8

ST a(R2), R1 //contents(a+contents(R2))=R1

x=*p

LD R1, p //R1 = p

LD R2, 0(R1) // R2 = contents(0+contents(R1))

ST x, R2 // x=R2

UNIT V Compiler Design

25 | P a g e

conditional-jump three-address instruction

If x<y goto L

LD R1, x // R1 = x

LD R2, y // R2 = y

SUB R1, R1, R2 // R1 = R1 - R2

BLTZ R1, M // i f R1 < 0 jump t o M

Basic blocks and flow graphs

A graph representation of intermediate code that is helpful for code generation.

Partition the intermediate code into basic blocks, which are sequence of 3-

address code with properties that,

The flow of control can only enter the basic block through the first

instruction in the block. That is, there are no jumps into the middle of the

block.

Control will leave the block without halting or branching, except possibly

at the last instruction in the block.

The basic blocks become the nodes of a flow graph, whose edges indicate

which block can follow which other blocks.

Rules for finding leaders

The first three-address instruction in the intermediate code is a leader.

Any instruction that is the target of a conditional or unconditional jump is a

leader.

Any instruction that immediately follows a conditional or unconditional jump

is a leader.

Intermediate code to set a 10*10 matrix to an identity.

UNIT V Compiler Design

26 | P a g e

Three address code for the above code is,

Flow graph

Once basic blocks are constructed, the flow of control between these block can

be represented using edges.

There is an edge from B to C iff it is possible for the first instruction in block C

to immediately follow the last instruction in block B.

There are two ways such an edge can be justified.

There is a conditional jump from the end of B to beginning of C

C immediately follows B in the original order of the three address

instructions and B does not end in an unconditional jump.

Example: consider the above three address code, the leader instructions are,

1,2,3,10,12,13 because these statements are the targets of branch instructions. Now,

using these leaders we can construct 6 basic blocks and then these basic blocks can

be connected to each other using edges as shown below.

UNIT V Compiler Design

27 | P a g e

(Flow graph)

A Simple code generator

One of the primary issues in code generation is deciding how to use registers to

best advantage. Following are principal use of registers.

In most machine architectures, some or all of the operands of an operation must

be in registers in order to perform the operation.

Registers make good temporaries - places to hold the result of a subexpression

while a larger expression is being evaluated.

Registers used to hold global values which are computed in one basic block

and used in other blocks.

Registers are often used to help with run-time storage management, for

example, to manage the run-time stack, including the maintenance of stack

pointers and possibly the top elements of the stack itself.

UNIT V Compiler Design

28 | P a g e

Descriptors

For each available register, a register descriptor keeps track of the variable

names whose current value is in that register. Since we shall use only those

registers that are available for local use within a basic block, we assume that

initially, all register descriptors are empty. As the code generation progresses,

each register will hold the value of zero or more names.

For each program variable, an address descriptor keeps track of the location or

locations where the current value of that variable can be found. The location

might be a register, a memory address, a stack location, or some set of more

than one of these. The information can be stored in the symbol-table entry for

that variable name.

The Code-generation Algorithm

This algorithm uses a function called getReg(I) which select registers for each

memory location associated with instruction I.

Use getReg(x = y + z) to select registers for x, y, and z. Call these Rx, Ry and

Rz.

If y is not in Ry (according to the register descriptor for Ry), then issue an

instruction LD Ry, y', where y' is one of the memory locations for y (according

to the address descriptor for y).

Similarly, if z is not in Rz, issue and instruction LD Rz, z', where z' is a location

for x .

Issue the instruction ADD Rx , Ry, Rz.

Rules for updating the register and address descriptors

1. For the instruction LD R, x

Change the register descriptor for register R so it holds only x.

Change the address descriptor for x by adding register R as an additional

location.

2. For the instruction ST x, R, change the address descriptor for x to include its

own memory location.

3. For an operation such as ADD Rx, Ry, Rz implementing a three-address

instruction x = y + x

Change the register descriptor for Rx so that it holds only x.

Change the address descriptor for x so that its only location is Rx. Note

UNIT V Compiler Design

29 | P a g e

that the memory location for x is not now in the address descriptor for x.

Remove Rx from the address descriptor of any variable other than x.

4. When we process a copy statement x = y, after generating the load for y into

register Ry, if needed, and after managing descriptors as for all load statements (per

rule I):

Add x to the register descriptor for Ry.

Change the address descriptor for x so that its only location is Ry .

Example

Let us consider a basic block containing the following 3-address code.

t=a-b

u=a-c

v=t+u

a=d

d=v+u

Instructions generated and the changes in the register and address descriptors is

shown below.

UNIT V Compiler Design

30 | P a g e

Rules for picking register Ry for y

If y is currently in a register, pick a register already containing y as Ry. Do not

issue a machine instruction to load this register, as none is needed.

If y is not in a register, but there is a register that is currently empty, pick one

such register as Ry.

The difficult case occurs when y is not in a register, and there is no register that

is currently empty. We need to pick one of the allowable registers anyway, and

we need to make it safe to reuse.

Peephole optimizations

An alternative approach of code generation is to generate naïve code and then

improve the quality of the target code by applying optimization.

A simple but effective technique for locally improving the target code is

peephole optimization.

This is done by examining a sliding window of target instructions (called

peephole) and replacing the instruction sequences within the peephole by a

shorter or faster sequence whenever possible.

UNIT V Compiler Design

31 | P a g e

Characteristic of peephole optimizations

Redundant-instruction elimination

Flow-of-control optimizations

Algebraic simplifications

Use of machine idioms

Redundant-instruction elimination

LD R0,a

ST a,R0

Eliminating unreachable code

Example 1:

Sum=0

if(sum)

printf(“%d”,sum);

Example 2:

Int fun(int a, int b)

{ c=a+b;

Return c;

Printf(“%d”,c);}

Flow-of-control optimizations

goto L1

...

Ll: goto L2

Can be replaced by:

goto L2

Algebraic simplifications

There is no end to the amount of algebraic simplification that can be attempted

through peephole optimization. Only a few algebraic identities occur frequently

enough that it is worth considering implementing them .For example, statements

such as

x := x+0

Or

x := x * 1

UNIT V Compiler Design

32 | P a g e

Are often produced by straightforward intermediate code-generation algorithms,

and they can be eliminated easily through peephole optimization.

Reduction in Strength: Reduction in strength replaces expensive operations by

equivalent cheaper ones on the target machine. Certain machine instructions are

considerably cheaper than others and can often be used as special cases of more

expensive operators.

For example, x² is invariably cheaper to implement as x*x than as a call to an

exponentiation routine. Fixed-point multiplication or division by a power of two is

cheaper to implement as a shift. Floating-point division by a constant can be

implemented as multiplication by a constant, which may be cheaper.

X2 → X*X.

Use of Machine Idioms:

The target instructions have equivalent machine instruction for performing some

operations. Hence we can replace these target instructions by equivalent machine

instructions in order to improve efficiency. E.g. some machines have auto-

increment and auto-decrement addressing modes that are used to perform addition

and subtraction.

Register Allocation and Assignment

Instructions involving register operands are shorter and faster than those involving

operands in memory.

The use of registers is subdivided into two sub problems:

Register allocation – the set of variables that will reside in registers at a point in

the program is selected.

Register assignment – the specific register that a variable will reside in is picked.

Following are the techniques.

Global Register Allocation

Usage Counts

Register Assignment for Outer Loops

Register Allocation by Graph Coloring

Global register allocation

Previously explained algorithm does local (block based) register allocation

This resulted that all live variables be stored at the end of block

UNIT V Compiler Design

33 | P a g e

To save some of these stores and their corresponding loads, we might arrange

to assign registers to frequently used variables and keep these registers

consistent across block boundaries (globally)

Some options are:

Keep values of variables used in loops inside registers

Use graph coloring approach for more globally allocation

Usage counts

The usage count is the count for the use of some variable x in some register

used in any basic block.

Usage count gives the idea about how many units of cost can be saved by

selecting a specific variable for global register allocation.

The approximate formula for usage count for the loop L in some basic block B

can be given by,

∑ (use(x,B)+2*(live(x,B))

Sum over all blocks (B) in a loop (L)

For each uses of x before any definition in the block we add one unit of

saving

If x is live on exit from B and is assigned a value in B, then we add 2 units

of saving

Ex:

Here, usage count of a,b,c,d,e & f is 4,5,3,6,4 &4 respectively. Hence,

if you have three registers (R1,R2,R3) then , these registers are allocated to the

UNIT V Compiler Design

34 | P a g e

variables c,b and a because they have highest usage count value.

Flow graph of an inner loop

Register assignment for outer loops

Consider that there are two loops L1 and L2 where L2 is the inner loop and

allocation of variable a is to be done to some register.

Following criteria should be adopted for register assignment for outer loop.

If a is allocated in loop L2 then it need not be allocated in L1-L2.

If a is allocated in L1 and it is not allocated in L2 then store a on an entrance to L2

and load a while leaving L2.

If a is allocated in L2 and not in L1 then load a on entrance of L2 and store a on

exit from L2.

Register allocation by Graph coloring

When we need a register for allocation but all registers are occupied then we

need to make some register free for reusability. The register selection is done

using following technique.(Graph coloring)

Two passes are used in this technique.

In the first pass the specific machine instruction is selected for register

allocation. For each variable a symbolic register is allocated.

In the second pass the register interference graph is constructed. In this

graph, each node is a symbolic register and an edge connects two nodes

where one is live at a point where other is defined.

Use a graph coloring algorithm to assign registers so that no two registers

can interfere with each other with assigned physical registers.