CH 5
CH 5
INTRODUCTION
The code produced by the straight forward compiling algorithms can often be made to run faster or take
less space, or both. This improvement is achieved by program transformations that are traditionally called
optimizations. Compilers that apply code-improving transformations are called optimizing compilers.
Machine independent optimizations are program transformations that improve the target code
without taking into consideration any properties of the target machine.
Machine dependant optimizations are based on register allocation and utilization of special machine-
instruction sequences.
Simply stated, the best program transformations are those that yield the most benefit for the least
effort.
The transformation must preserve the meaning of programs. That is, the optimization must not change
the output produced by a program for a given input, or cause an error such as division by zero, that
was not present in the original source program.
At all times we take the“safe” approach of missing an opportunity to apply a transformation rather
than risk changing what the program does.
A transformation must, on the average, speed up programs by a measurable amount. We are also
interested in reducing the size of the compiled code although the size of the code has less importance
than it once had. Not every transformation succeeds in improving every program, occasionally an
“optimization” may slow down a program slightly.
The transformation must be worth the effort. It does not make sense for a compiler writer to expend
the intellectual effort to implement a code improving transformation and to have the compiler expend
the additional time compiling source programs if this effort is not repaid when the target programs are
executed. “Peephole” transformations of this kind are simple enough and beneficial enough to be
included in any compiler.
A transformation of a program is called local if it can be performed by looking only at the statements
in a basic block; otherwise, it is called global.
Many transformations can be performed at both the local and global levels. Local transformations are
usually performed first.
1) Function-Preserving Transformations
There are a number of ways in which a compiler can improve a program without
changing the function it computes.
The transformations,
are common examples of such function-preserving transformations. The other transformations come up
primarily when global optimizations are performed. Frequently, a program will include several calculations
of the same value, such as an offset in an array. Some of the duplicate calculations cannot be avoided by the
programmer because they lie below the level of detail accessible within the source language.
For example
t1: = 4*i
t2: = a [t1]
t3: = 4*j
t4: = 4*i
t5: = n
t6: = b [t4] +t5
The above code can be optimized using the common sub-expression elimination as
t1: = 4*i
t2: = a [t1]
t3: = 4*j
t5: = n
t6: = b [t1] +t5
The common sub expression t4: =4*i is eliminated as its computation is already in t1. And value of i is not
been changed from definition to use.
b) Copy Propagation:
Assignments of the form f : = g called copy statements, or copies for short. The idea behind the copy-
propagation transformation is to use g for f, whenever possible after the copy statement f: = g. Copy
propagation means use of one variable instead of another. This may not appear to be an improvement, but as
we shall see it gives us an opportunity to eliminate x.
For example:
x=Pi;
……
A=x*r*r;
A=Pi*r*r;
- C) 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.
- D) Constant folding:
We can eliminate both the test and printing from the object code. More generally, deducing at
compile time that the value of an expression is a constant and using the constant instead is known as constant
folding.
One advantage of copy propagation is that it often turns the copy statement into dead code.
For example,
a=3.14157/2 can be replaced by
a=1.570 there by eliminating a division operation.
1) Structure-Preserving Transformations
2) Algebraic Transformations
1. Structure-Preserving Transformations:
Common sub expressions need not be computed over and over again. Instead they can be computed
once and kept in store from where it’s referenced when encountered again – of course providing the variable
values in the expression still remain constant.
Example:
a: =b+c
b: =a-d
c: =b+c
d: =a-d
The 2nd and 4th statements compute the same expression: b+c and a-d
a: = b+c
b: = a-d
c: = a
d: = b
It’s possible that a large amount of dead (useless) code may exist in the program. This might be
especially caused when introducing variables and procedures as part of construction or error-correction of a
program – once declared and defined, one forgets to remove them in case they serve no purpose. Eliminating
these will definitely optimize the code.
A statement t:=b+c where t is a temporary name can be changed to u:=b+c where u is another
temporary name, and change all uses of t to u. In this we can transform a basic block to its equivalent block
called normal-form block.
t1:=b+c
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.
2. Algebraic Transformations:
Algebraic identities represent another important class of optimizations on basic blocks. This includes
simplifying expressions or replacing expensive operation by cheaper ones i.e. reduction in strength.
Another class of related optimizations is constant folding. Here we evaluate constant expressions at
compile time and replace the constant expressions by their values. Thus the expression 2*3.14 would
be replaced by 6.28.
The relational operators <=, >=, <, >, + and = sometimes generate unexpected common sub
expressions.
Associative laws may also be applied to expose common sub expressions. For example, if the source
code has the assignments
a :=b+c
e :=c+d+b
a :=b+c
t :=c+d
e :=t+b
Example:
The compiler writer should examine the language carefully to determine what rearrangements of
computations are permitted, since computer arithmetic does not always obey the algebraic identities
of mathematics. Thus, a compiler may evaluate x*y-x*z as x*(y-z) but it may not evaluate a+(b-c) as
(a+b)-c.
A graph representation of three-address statements, called a flow graph, is useful for understanding
code-generation algorithms, even if the graph is not explicitly constructed by a code-generation algorithm.
Nodes in the flow graph represent computations, and the edges represent the flow of control.
Dominators:
In a flow graph, a node d dominates node n, if every path from initial node of the flow graph to n goes
through d. This will be denoted by d dom n. Every initial node dominates all the remaining nodes in the flow
graph and the entry of a loop dominates all nodes in the loop. Similarly every node dominates itself.
The way of presenting dominator information is in a tree, called the dominator tree in which the
initial node is the root.
The parent of each other node is its immediate dominator.
Each node d dominates only its descendents in the tree.
The existence of dominator tree follows from a property of dominators; each node has a unique
immediate dominator in that is the last dominator of n on any path from the initial node to n.
In terms of the dom relation, the immediate dominator m has the property is d=!n and d dom n, then d
dom m.
D(1)={1}
D(2)={1,2}
D(3)={1,3}
D(4)={1,3,4}
D(5)={1,3,4,5}
D(6)={1,3,4,6}
D(7)={1,3,4,7}
D(8)={1,3,4,7,8}
D(9)={1,3,4,7,8,9}
D(10)={1,3,4,7,8,10}
Natural Loop:
One application of dominator information is in determining the loops of a flow graph suitable for
improvement.
One way to find all the loops in a flow graph is to search for edges in the flow graph whose heads
dominate their tails. If a→b is an edge, b is the head and a is the tail. These types of edges are called as back
edges.
Example:
Inner loop:
If we use the natural loops as “the loops”, then we have the useful property that unless two loops have
the same header, they are either disjointed or one is entirely contained in the other. Thus, neglecting
loops with the same header for the moment, we have a natural notion of inner loop: one that contains
no other loop.
When two natural loops have the same header, but neither is nested within the other, they are
combined and treated as a single loop.
Pre-Headers:
Several transformations require us to move statements “before the header”. Therefore begin
treatment of a loop L by creating a new block, called the preheader.
The pre-header has only the header as successor, and all edges which formerly entered the header of
L from outside L instead enter the pre-header.
Edges from inside loop L to the header are not changed.
Initially the pre-header is empty, but transformations on L may place statements in it.
Reducible flow graphs are special flow graphs, for which several code optimization transformations
are especially easy to perform, loops are unambiguously defined, dominators can be easily calculated,
data flow analysis problems can also be solved efficiently.
Exclusive use of structured flow-of-control statements such as if-then-else, while-do, continue, and
break statements produces programs whose flow graphs are always reducible.
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 improving 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.
We shall give the following examples of program transformations that are characteristic of peephole
optimizations:
Redundant-instructions elimination
Flow-of-control optimizations
Algebraic simplifications
Use of machine idioms
Unreachable Code