Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Download as pdf or txt
Download as pdf or txt
You are on page 1of 13

Sir C R Reddy College of Engineering Department of Information Technology

UNIT-V
Code generation: Machine dependent code generation, object code forms, generic code
generation algorithm, Register allocation and assignment. Using DAG representation of Block.

Machine dependent code generation


Machine-dependent optimization is done after the target code has been generated and
when the code is transformed according to the target machine architecture. It involves CPU
registers and may have absolute memory references rather than relative references. Machine-
dependent optimizers put efforts to take maximum advantage of memory hierarchy.
Basic Blocks
Source codes generally have a number of instructions, which are always executed in
sequence and are considered as the basic blocks of the code. These basic blocks do not have any
jump statements among them, i.e., when the first instruction is executed, all the instructions in
the same basic block will be executed in their sequence of appearance without losing the flow
control of the program.
A program can have various constructs as basic blocks, like IF-THEN-ELSE, SWITCH-
CASE conditional statements and loops such as DO-WHILE, FOR, and REPEAT-UNTIL, etc.
Basic block identification
We may use the following algorithm to find the basic blocks in a program:
 Search header statements of all the basic blocks from where a basic block starts:
o First statement of a program.
o Statements that are target of any branch (conditional/unconditional).
o Statements that follow any branch statement.
 Header statements and the statements following them form a basic block.
 A basic block does not include any header statement of any other basic block.
Basic blocks are important concepts from both code generation and optimization point of view.

Basic blocks play an important role in identifying variables, which are being used more than
once in a single basic block. If any variable is being used more than once, the register memory
allocated to that variable need not be emptied unless the block finishes execution.

Automata Theory & Compiler Design


Sir C R Reddy College of Engineering Department of Information Technology

Control Flow Graph


Basic blocks in a program can be represented by means of control flow graphs. A control
flow graph depicts how the program control is being passed among the blocks. It is a useful tool
that helps in optimization by help locating any unwanted loops in the program.

Loop Optimization
Most programs run as a loop in the system. It becomes necessary to optimize the loops in
order to save CPU cycles and memory. Loops can be optimized by the following techniques:
 Invariant code : A fragment of code that resides in the loop and computes the same value
at each iteration is called a loop-invariant code. This code can be moved out of the loop
by saving it to be computed only once, rather than with each iteration.
 Induction analysis : A variable is called an induction variable if its value is altered
within the loop by a loop-invariant value.
 Strength reduction : There are expressions that consume more CPU cycles, time, and
memory. These expressions should be replaced with cheaper expressions without
compromising the output of expression. For example, multiplication (x * 2) is expensive
in terms of CPU cycles than (x << 1) and yields the same result.
Dead-code Elimination
Dead code is one or more than one code statements, which are:
 Either never executed or unreachable,
 Or if executed, their output is never used.
Thus, dead code plays no role in any program operation and therefore it can simply be
eliminated.
Partially dead code
There are some code statements whose computed values are used only under certain
circumstances, i.e., sometimes the values are used and sometimes they are not. Such codes are
known as partially dead-code.

Automata Theory & Compiler Design


Sir C R Reddy College of Engineering Department of Information Technology

The above control flow graph depicts a chunk of program where variable „a‟ is used to assign the
output of expression „x * y‟. Let us assume that the value assigned to „a‟ is never used inside the
loop. Immediately after the control leaves the loop, „a‟ is assigned the value of variable „z‟,
which would be used later in the program. We conclude here that the assignment code of „a‟ is
never used anywhere, therefore it is eligible to be eliminated.

Likewise, the picture above depicts that the conditional statement is always false, implying that
the code, written in true case, will never be executed, hence it can be removed.
Partial Redundancy
Redundant expressions are computed more than once in parallel path, without any change
in operands. whereas partial-redundant expressions are computed more than once in a path,
without any change in operands. For example,

[redundant expression] [partially redundant expression]

Automata Theory & Compiler Design


Sir C R Reddy College of Engineering Department of Information Technology

Loop-invariant code is partially redundant and can be eliminated by using a code-motion


technique.
Another example of a partially redundant code can be:
if (condition)
{
a = y OP z;
}
else
{
...
}
c = y OP z;
We assume that the values of operands (y and z) are not changed from assignment of
variable a to variable c. Here, if the condition statement is true, then y OP z is computed twice,
otherwise once. Code motion can be used to eliminate this redundancy, as shown below:
if (condition)
{
...
tmp = y OP z;
a = tmp;
...
}
else
{
...
tmp = y OP z;
}
c = tmp;
Here, whether the condition is true or false; y OP z should be computed only once.

Object code forms


Let assume that, you have a c program, then you give the C program to compiler and
compiler will produce the output in assembly code. Now, that assembly language code will
give to the assembler and assembler is going to produce you some code. That is known
as Object Code.

But, when you compile a program, then you are not going to use both compiler and
assembler. You just take the program and give it to the compiler and compiler will give you

Automata Theory & Compiler Design


Sir C R Reddy College of Engineering Department of Information Technology

the directly executable code. The compiler is actually combined inside the assembler along
with loader and linker. So all the module kept together in the compiler software itself. So when
you calling gcc, you are actually not just calling the compiler, you are calling the compiler,
then assembler, then linker and loader.
Once you call the compiler, then your object code is going to present in Hard-disk. This
object code contains various part –

 Header –
The header will say what are the various parts present in this object code and then point
that parts. So header will say where the text segment is going to start and a pointer to it and
where the data segment going to start and it say where the relocation information and
symbol information there.
It is nothing but like an index, like you have a textbook, there an index page will contain at
what page number each topic present. Similarly, the header will tell you, what are the
palaces at which each information is present. So that later for other software it will be
useful to directly go into those segment.
 Text segment –
 It is nothing but the set of instruction.
 Data segment –
 Data segment will contain whatever data you have used. For example, you might have used
something constraint, then that going to be present in the data segment.
 Relocation Information –
 Whenever you try to write a program, we generally use symbol to specify anything. Let us
assume you have instruction 1, instruction 2, instruction 3, instruction 4,….

Automata Theory & Compiler Design


Sir C R Reddy College of Engineering Department of Information Technology

 Now if you say somewhere Goto L4 (Even if you don‟t write Goto statement in the high-
level language, the output of the compiler will write it), then that code will be converted
into object code and L4 will be replaced by Goto 4. Now Goto 4 for the level L4 is going to
work fine, as long as the program is going to be loaded starting at address no 0. But in most
cases, the initial part of the RAM is going to be dedicated to the operating system. Even if
it is not dedicated to the operating system, then might be some other process that will
already be running at address no 0. So, when you are going to load the program into
memory, means if the program has to be loaded in the main memory, it might be loaded
anywhere. Let us say 1000 is the new starting address, then all the addresses have to be
changed, that is known as Reallocation.

 The original address is known as Relocatable address and the final address, which we get
after loading the program into main memory, is known as the Absolute address.
Symbol table –
It contains every symbol that you have in your program. for example, int a, b, c then, a, b, c
are the symbol. it will show what are the variables that your program contains.
Debugging information –
It will help to find how a variable is keeping on changing.
Generic code generation algorithm
The algorithm takes a sequence of three-address statements as input. For each three address
statement of the form a:= b op c perform the various actions. These are as follows:
1. Invoke a function getreg to find out the location L where the result of computation b op c
should be stored.
2. Consult the address description for y to determine y'. If the value of y currently in
memory and register both then prefer the register y' . If the value of y is not already in L
then generate the instruction MOV y' , L to place a copy of y in L.
3. Generate the instruction OP z' , L where z' is used to show the current location of z. if z
is in both then prefer a register to a memory location. Update the address descriptor of x
to indicate that x is in location L. If x is in L then update its descriptor and remove x from
all other descriptor.
4. If the current value of y or z have no next uses or not live on exit from the block or in
register then alter the register descriptor to indicate that after execution of x : = y op z
those register will no longer contain y or z.

Automata Theory & Compiler Design


Sir C R Reddy College of Engineering Department of Information Technology

Generating Code for Assignment Statements:


The assignment statement d:= (a-b) + (a-c) + (a-c) can be translated into the following sequence
of three address code:
t:= a-b
u:= a-c
v:= t +u
d:= v+u
Code sequence for the example is as follows:
Register descriptor
Statement Code Generated Address descriptor
Register empty
t:= a - b MOV a, R0 R0 contains t t in R0
SUB b, R0
u:= a - c MOV a, R1 R0 contains t t in R0
SUB c, R1 R1 contains u u in R1
v:= t + u ADD R1, R0 R0 contains v u in R1
R1 contains u v in R1
d:= v + u ADD R1, R0 R0 contains d d in R0
MOV R0, d d in R0 and memory

Register allocation and assignment


Introduction
 In many programming languages, the programmer has the illusion of allocating arbitrarily
many variables.
 However, during compilation, the compiler must decide how to allocate these variables to
a small, finite set of registers.
 Not all variables are in use (or “live”) at the same time, so some registers may be
assigned to more than one variable. However, two variables in use at the same time
cannot be assigned to the same register without corrupting its value.
 Variables which cannot be assigned to some register must be kept in RAM and loaded
in/out for every read/write, a process called spilling.
 Accessing RAM is significantly slower than accessing registers and slows down the
execution speed of the compiled program, so an optimizing compiler aims to assign as
many variables to registers as possible.
 Register pressure is the term used when there are fewer hardware registers available than
would have been optimal; higher pressure usually means that more spills and reloads are
needed.
Better approach = register allocation: keep variable values in registers as long as possible.
Best case: keep a variable‟s value in a register throughout the lifetime of that variable.
 In that case, we don‟t need to ever store it in memory
 We say that the variable has been allocated in a register
 Otherwise allocate variable in activation record
 We say that variable is spilled to memory

Automata Theory & Compiler Design


Sir C R Reddy College of Engineering Department of Information Technology

Which variables can we allocate in registers?


Depends on the number of registers in the machine. Depends on how variables are being used.
 Main Idea: cannot allocate two variables to the same register if they are both live at some
program point.
Spilling
 In most register allocators, each variable is assigned to either a CPU register or to main
memory.
 The advantage of using a register is speed. Computers have a limited number of registers,
so not all variables can be assigned to registers.
 A “spilled variable” is a variable in main memory rather than in a CPU register. The
operation of moving a variable from a register to memory is called spilling, while the
reverse operation of moving a variable from memory to a register is called filling. For
example, a 32-bit variable spilled to memory gets 32 bits of stack space allocated and all
references to the variable are then to that memory.
 Such a variable has a much slower processing speed than a variable in a register. When
deciding which variables to spill, multiple factors are considered: execution time, code
space, data space.
Iterated Register Coalescing
 Register allocators have several types, with Iterated Register Coalescing (IRC) being a
more common one.
 IRC was invented by LAL George and Andrew Appel in 1996, building on earlier work
by Gregory Chaitin.
 IRC works based on a few principles. First, if there are any non-move related vertices in
the graph with degree less than K the graph can be simplified by removing those vertices,
since once those vertices are added back in it is guaranteed that a color can be found for
them (simplification).
Register Allocation Algorithm
Basic algorithm for register allocation is:
1. Perform live variable analysis (over abstract assembly code!)
2. Inspect live variables at each program point
3. If two variables are ever in same live set, they can‟t be allocated to the same register –
they interfere with each other
4. Conversely, if two variables do not interfere with each other, they can be assigned the
same register. We say they have disjoint live ranges.
Interference Graph

Automata Theory & Compiler Design


Sir C R Reddy College of Engineering Department of Information Technology

 Nodes=program variables
 Edges = connect variables that interfere with each other
Register allocation = graph coloring

Using DAG representation of Block


A DAG for basic block is a directed acyclic graph with the following labels on nodes:
1. The leaves of graph are labeled by unique identifier and that identifier can be variable
names or constants.
2. Interior nodes of the graph is labeled by an operator symbol.
3. Nodes are also given a sequence of identifiers for labels to store the computed value.
o DAGs are a type of data structure. It is used to implement transformations on
basic blocks.
o DAG provides a good way to determine the common sub-expression.
o It gives a picture representation of how the value computed by the statement is
used in subsequent statements.
Algorithm for construction of DAG
Input: It contains a basic block
Output: It contains the following information:
o Each node contains a label. For leaves, the label is an identifier.
o Each node contains a list of attached identifiers to hold the computed values.
Case (i) x:= y OP z
Case (ii) x:= OP y
Case (iii) x:= y
Method:
Step 1:
If y operand is undefined then create node(y).
If z operand is undefined then for case(i) create node(z).
Step 2:
For case(i), create node(OP) whose right child is node(z) and left child is node(y).
For case(ii), check whether there is node(OP) with one child node(y).
For case(iii), node n will be node(y).
Output:
For node(x) delete x from the list of identifiers. Append x to attached identifiers list for the node
n found in step 2. Finally set node(x) to n.
Example:
Consider the following three-address statement:
1. S1:= 4 * i
2. S2:= a[S1]
3. S3:= 4 * i
4. S4:= b[S3]

Automata Theory & Compiler Design


Sir C R Reddy College of Engineering Department of Information Technology

5. S5:= s2 * S4
6. S6:= prod + S5
7. Prod:= s6
8. S7:= i+1
9. i := S7
10. if i<= 20 goto (1)
Stages in DAG Construction:

Automata Theory & Compiler Design


Sir C R Reddy College of Engineering Department of Information Technology

Automata Theory & Compiler Design


Sir C R Reddy College of Engineering Department of Information Technology

Automata Theory & Compiler Design


Sir C R Reddy College of Engineering Department of Information Technology

Automata Theory & Compiler Design

You might also like