CD Unit 4
CD Unit 4
Unit – 4
I. Code generation
Issues in the design of a code Generator, Basic blocks and flow graphs, Next-use information, A
simple Code generator, DAG representation of Basic blocks, Peephole Optimization, Generating
code from DAGS.
II. Code optimization
The principle sources of optimization, Optimization of basic blocks, Implementation for
Common Sub expression technique using DAG.
III. Symbol table: The contents of a symbol table, Data structures for Symbol Table,
Representing scope information.
I. CODE GENERATION
▪ The final phase in compiler model is the code generator.
▪ It takes as input an intermediate representation of the source program and produces as
output an equivalent target program.
1| Unit – 4
CE 710 Compiler Design
1. Absolute machine language - It can be placed in a fixed memory location and can be
executed immediately.
2. Relocatable machine language - It allows subprograms to be compiled separately.
3. Assembly language - Code generation is made easier.
2| Unit – 4
CE 710 Compiler Design
Flow Graphs
▪ Flow graph is a directed graph containing the flow-of-control information for the set of
basic blocks making up a program.
Properties of Flow-graph
▪ The nodes of the flow graph are basic blocks and the first block is designated as a Initial
block
▪ There is an directed edge from the basic block B1 to B2, iff B1 is a predecessor of B2.
3| Unit – 4
CE 710 Compiler Design
3. Next-Use Information
▪ If the name in a register is no longer needed, then we remove the name from the
register and the register can be used to store some other names.
▪ Next use information is used in code generation and register allocation.
The use of a name in a three-address statement is defined as
Let the TAC statement “i" assigns a value to the element x,
▪ If TAC statement “j” has x as an operand and control can flow from statement “i" to “j”
along the path there is no intervening assignments to x, then the statement “j” uses the
value of x computed at statement “i".
▪ The Next use Computation algorithm assumes that on exit from the basic block,
1. All temporaries are considered non-live
2. All variables defined in the source program (and non-temps) are live
3. Each procedure/function call is assumed to start a basic block
Algorithm to determine Liveliness and Next-use information
Input : Basic block B of three-address statements
Output : At each statement i: x= y op z, we attach to i the liveliness and next-uses of x,
y and z.
Method : We start at the last statement of B and scan backwards.
1. Attach to statement i the information currently found in the symbol table regarding
the next-use and liveliness of x, y and z.
2. In the symbol table, set x to “not live” and “no next use”.
3. In the symbol table, set y and z to “live”, and next-uses of y and z to i.
4| Unit – 4
CE 710 Compiler Design
Data structures
1. Register Descriptor
▪ A Register Descriptor keeps track of the variables whose current values are in that
Register.
▪ Initially all register descriptors are empty.
2. Address Descriptor
▪ An Address descriptor keeps track of the location or locations where the current value of
that variable can be found.
▪ The location can be a register, memory location, a stack location or some set of more
than one of these.
3. getReg () function
▪ This function selects registers for each memory location associated with the TAC statement
▪ It has access to the register and address descriptors for all variables of the basic block.
1. Use getReg (x : = y op z) to select registers for x, y and z. Call these as Rx, Ry and Rz
2. If y is not in Ry (according to register descriptor for Ry), then
Issue an instruction LD Ry, y’
where y’ is one of the memory locations for y (according to address descriptor for y).
3. Similarly if z is not in Rz, then
Issue an instruction LD Rz, z’
where z’ is one of the memory locations for z (according to address descriptor for z).
4. Issue the instruction ADD Rx, Ry, Rz
5| Unit – 4
CE 710 Compiler Design
Example: The assignment d : = (a-b) + (a-c) + (a-c) might be translated into the following
three - address code sequence as [No of registers available: R1, R2, R3]
t=a–b
u=a–c
v=t+u
d=v+u
u= a–c
LD R3, c
SUB R1, R1, R3
u t c a b C, R3 d R2 R1
v=t+u
ADD R2, R2, R1
u v c a b c, R3 d R1 R2
d=v+u
ADD R1, R1, R2
d v c a b c, R3 d,R1 R2
6| Unit – 4
CE 710 Compiler Design
7| Unit – 4
CE 710 Compiler Design
8| Unit – 4
CE 710 Compiler Design
9| Unit – 4
CE 710 Compiler Design
Application of DAG
1. We can automatically detect common sub expressions.
2. We can determine which identifiers have their values used in the block.
3. We can determine which statements compute values that could be used outside the
block.
6. Peephole Optimization
▪ A statement-by-statement code-generations strategy often produce target code that
contains redundant instructions and suboptimal constructs .The quality of such target
code can be improved by applying “optimizing” transformations to the target program.
▪ A simple but effective technique for improving the target code is peephole optimization, a
method for trying to improve the performance of the target program by examining a short
sequence of target instructions (called the peephole) and replacing these instructions by a
shorter or faster sequence, whenever possible.
▪ The peephole is a small, moving window on the target program. The code in the peephole
need not contiguous, although some implementations do require this. It is characteristic
of peephole optimization that each improvement may spawn opportunities for additional
improvements.
10 | Unit – 4
CE 710 Compiler Design
▪ One obvious peephole optimization is to eliminate jumps over jumps .Thus no matter
what the value of debug; (a) can be replaced by:
If debug ≠1 goto L2
Print debugging information
L2: ……………………………(b)
▪ As the argument of the statement of (b) evaluates to a constant true it can be replaced
by
If debug ≠0 goto L2
Print debugging information
L2: ……………………………(c)
▪ As the argument of the first statement of (c) evaluates to a constant true, it can be
replaced by goto L2. Then all the statement that print debugging aids are manifestly
unreachable and can be eliminated one at a time.
11 | Unit – 4
CE 710 Compiler Design
▪ If there are now no jumps to L1, then it may be possible to eliminate the statement L1:
goto L2 provided it is preceded by an unconditional jump.
▪ While the number of instructions in (1) and (2) is the same, we sometimes skip the
unconditional jump in (2), but never in (1).Thus (2) is superior to (1) in execution time
12 | Unit – 4
CE 710 Compiler Design
13 | Unit – 4
CE 710 Compiler Design
▪ Optimization is the process of transforming a piece of code to make more efficient (either
in terms of time or space) without changing its output or side-effects.
▪ The only difference visible to the code’s user should be that it runs faster and/or
consumes less memory.
▪ Optimizations are classified into two categories. They are
1. Machine independent optimizations
2. Machine dependant optimizations
14 | Unit – 4
CE 710 Compiler Design
(iii) Dead-Code Eliminations - A variable is live at a point in a program if its value can be
used subsequently; otherwise, it is dead at that point.
▪ A related idea is dead or useless code, statements that compute values that never get
used.
▪ While the programmer is unlikely to introduce any dead code intentionally, it may appear
as the result of previous transformations. An optimization can be done by eliminating
dead code.
Example
i = 0;
if (i=1)
{
a=b+5;
}
Here, ‘if’ statement is dead code because this condition will never get satisfied. We
can eliminate both the test and printing from the object code.
15 | Unit – 4
CE 710 Compiler Design
Structure-Preserving Transformations
▪ The primary Structure-Preserving Transformation on basic blocks are
1. Common sub-expression elimination
2. Dead code elimination
3. Renaming of temporary variables
4. Interchange of two independent adjacent statements.
16 | Unit – 4
CE 710 Compiler Design
t2 := x + y
can be interchanged or reordered in its computation in the basic block when value of t1 does
not affect the value of t2.
3. LOOP OPTIMIZATIONS
▪ The loops where programs tend to spend the bulk of their execution time. The running
time of a program may be improved if we decrease the number of instructions in an inner
loop, even if we increase the amount of code outside that loop.
▪ Three techniques are important for loop optimization
1. Code motion, which moves code outside a loop;
2. Induction-variable elimination, which we apply to replace variables from inner loop.
3. Reduction in strength, which replaces and expensive operation by a cheaper one, such
as a multiplication by an addition.
(i) Code Motion
▪ An important modification that decreases the amount of code in a loop is code motion.
▪ This transformation takes an expression that yields the same result independent of the
number of times a loop is executed ( a loop-invariant computation) and places the
expression before the loop.
▪ Note that the notion “before the loop” assumes the existence of an entry for the loop.
▪ For example, evaluation of limit-2 is a loop-invariant computation in the following while
statement:
while (i <= limit-2) /* statement does not change limit*/
Code motion will result in the equivalent of
t= limit-2;
while (i<=t) /* statement does not change limit or t */
Example:
As the relationship t4:=4*j surely holds after such an assignment to t4 in Fig. and t4 is not
changed elsewhere in the inner loop around B3, it follows that just after the statement
j:=j-1 the relationship t4:= 4*j-4 must hold. We may therefore replace the assignment
t4:= 4*j by t4:= t4-4. The only problem is that t4 does not have a value when we enter
block B3 for the first time. Since we must maintain the relationship t4=4*j on entry to
the block B3, we place an initializations of t4 at the end of the block where j itself is
initialized, shown by the dashed addition to block B1 in second Fig. The replacement of
a multiplication by a subtraction will speed up the object code if multiplication takes
more time than addition or subtraction, as is the case on many machines.
Example:
// old loop
for(int i=0; i<3; i++) {
color_map[n+i] = i;
}
// unrolled version
int i = 0;
colormap[n+i] = i;
i++;
colormap[n+i] = i;
i++;
colormap[n+i] = i;
18 | Unit – 4
CE 710 Compiler Design
A symbol table may serve the following purposes depending upon the language in hand:
• To store the names of all entities in a structured form at one place.
• To verify if a variable has been declared.
• To implement type checking, by verifying assignments and expressions in the source
code are semantically correct.
• To determine the scope of a name (scope resolution).
This symbol table data structure hierarchy is stored in the semantic analyzer and whenever
a name needs to be searched in a symbol table, it is searched using the following algorithm:
• First a symbol will be searched in the current scope, i.e. current symbol table.
• if a name is found, then search is completed, else it will be searched in the parent symbol
table until,
• Either the name is found or global symbol table has been searched for the name .
Example:
To determine the scope of a name, symbol tables are arranged in hierarchical structure as
shown in the example below:
...
int value=10;
void pro_one()
{
int one_1;
int one_2;
{ \
int one_3; |_ inner scope 1
int one_4; |
19 | Unit – 4
CE 710 Compiler Design
} /
int one_5;
{ \
int one_6; |_ inner scope 2
int one_7; |
} /
}
void pro_two()
{
int two_1;
int two_2;
{ \
int two_3; |_ inner scope 3
int two_4; |
} /
int two_5;
}
...
20 | Unit – 4
CE 710 Compiler Design
21 | Unit – 4
CE 710 Compiler Design
Complexity: If any symbol table has ‘n’ names then for inserting any new name we must
search list ‘n’ times in worst case. So cost of searching is O(n) and if we want to insert ‘n’ name
then cost of this insert is O(n2) in worst case.
▪ To reduce the time of searching it can add an addition field ‘linker’ to each record field
or each array index.
▪ When a name is inserted then it will insert at ‘space’ and manage all linkers to other
existing name.
2. Tree
▪ Another approach to organize a symbol table is that adds two additional link fields i.e. left
and right child, as binary search tree. All names are created as child of root node that
always follows the property of binary tree.
▪ For inserting any name it always follow binary search tree insert algorithm.
▪ Each subprogram has a symbol table associated to its node in the abstract syntax tree.
▪ The main program has a similar table for globally declared objects.
▪ Quicker than linear lists and also easy to represent scoping.
Example:
3. Hash Table
▪ A hash table, or a hash map, is a data structure that associates keys with values ‘Open
hashing’ is a key that is applied to hash table.
▪ In hashing –open, there is a property that no limit on number of entries that can be made
in table. Hash table consist an array and several buckets attached to array according to
hash function.
23 | Unit – 4
CE 710 Compiler Design
▪ Main advantage of hash table is that we can insert or delete any number or name in O
(n) time, if data are search linearly and there are ‘n’ memory location where data is
stored.
▪ Using hash function any name can be search in O(1) time. However, the rare worst-case
lookup time can be as bad as O(n).
▪ A good hash function is essential for good hash table performance. A poor choice of a
hash function is likely to lead to clustering, in which probability of keys mapping to the
same hash bucket (i.e. a collision) occur. One organization of a hash table that resolves
conflicts is chaining.
Example:
24 | Unit – 4