Code Generation: Issues in The Design of A Code Generator
Code Generation: Issues in The Design of A Code Generator
2. Target Program
Target program is the output of the code generator. The output may be
absolute machine language, relocatable machine language, assembly
language.
1. Absolute machine language as an output has advantages that
it can be placed in a fixed memory location and can be
immediately executed.
2. Relocatable machine language as an output allows
subprograms and subroutines to be compiled separately.
Relocatable object modules can be linked together and
loaded by linking loader.
3. Assembly language as an output makes the code generation
easier. We can generate symbolic instructions and use macro-
facilities of assembler in generating code.
1. Memory Management –
Mapping the names in the source program to addresses of data objects is
done by the front end and the code generator. A name in the three
addressstatement refers to the symbol table entry for name. Then from
the symbol table entry, a relative address can be determined for the name
2. Instruction selection –
Selecting best instructions will improve the efficiency of the program. It
includes the instructions that should be complete and uniform.
Instruction speeds and machine idioms also plays a major role when
efficiency is considered. But if we do not care about the efficiency of the
target program then instruction selection is straight-forward.
For example, the respective three-address statements would be translated
into latter code sequence as shown below: P:=Q+R
S:=P+T
MOV Q, R0
ADD R, R0
MOV R0, P
MOV P, R0
ADD T, R0
MOV R0, S
M a, b
These types of multiplicative instruction involve register pairs where a,
the multiplicand is an even register and b, the multiplier is the odd
register of the even/odd register pair.
Evaluation order –
The code generator decides the order in which the instruction will be
executed. The order of computations affects the efficiency of the target
code. Among many computational orders, some will require only fewer
registers to hold the intermediate results. However, picking the best
order in general case is a difficult NP-complete program.
Approaches to code generation issues: Code generator must always
generate the correct code. It is essential because of the number of special
cases that a code generator might face. Some of the design goals of code
generator are:
Correct
Easily maintainable
Testable
Maintainable
Target Machine
A Target machine is a byte-addressable machine. This machine has n
general-purpose registers, R0, R1,…..Rn-1. A Simple Target Machine
Model has three-address instruction. A full-edged assembly language
would have a variety of instructions. The component of instruction is an
operator, followed by a target, and then followed by a list of source
operands.
Some of the instructions are as follows:
Load operations: LD dst, addr instruction loads the value in
location addr into location dst. It means that assignments dst = addr. L, r,
x is the general form of this instruction. The role of this instruction is to
load the value in location x into register r.
Store operations: ST r, x instruction stores the value in the location
x into register r.
Computation operations: OP, dst, src1, src2 are the form of
computation operations where OP are the add or sub operator and dst,
src1, and src2 are locations. The locations may or may not be distinct.
Unconditional Operations: The instruction BR
L causes control to branch to the machine instruction with label L (BR
stands for the branch).
Conditional jumps: The general form of this operation is Bcond, r,
L. Here R is the register, L is a label, and cond stands for any of the
common tests on the value in register r.
The various addressing modes associated with the target machine are
discussed below:
In instruction, a variable name x means there is a location in
memory reserved for x.
An indexed address in the form a(r), where ‘a’ is a variable and r is
a register, can also be a form of a location. By taking the l-value of ‘a’
and adding it with the value in the register, the value of memory location
denoted by a(r) can be computed.
An integer indexed by a register can be a memory location. For
example, LD R1, 100(R2) has the effect of setting R1 = contents (100 +
contents (R2)).
There are two indirect addressing modes: *r and *100(r). *r has the
address of contents(r), and *100(r) has the address for adding 100 to the
contents(r).
The immediate constant addressing mode is the last addressing
mode, which is denoted by prefix #.
Program and Instruction Costs
The cost refers to compiling and running a program. There are some
aspects of the program on which we optimize the program. The
program’s cost can be determined by the compilation time’s length and
the size, execution time, and power consumption of the target program.
Finding the actual cost of the program is a tough task. Therefore, code
generation use heuristic techniques to produce a good target program.
Each target-machine instruction has an associated cost. The instruction
cost is one plus the cost associated with the addressing modes of the
operands.
Example
LD R0, R1: This instruction copies the contents of register R1 into
register R0. The cost of this instruction is one because no additional
memory is required.
LD R0, M: This instruction’s role is to load the contents of memory
location M into R0. So the cost will be two due to the address of
memory location M is found in the word following the instruction.
LD R1, *100(R2): The role of this instruction is to load the value given
by contents (contents (100 + contents (R2))) into register R1. This
instruction’s cost will be two due to the constant 100 is stored in the
word following the instruction.
optimization :
t0 = c * d
a = b + t0
Example 2 :
L2: e = f - g / d
optimization :
t0 = g / d
e = f - t0
Register Allocation :
Register allocation is the process of assigning program variables to
registers and reducing the number of swaps in and out of the registers.
Movement of variables across memory is time consuming and this is the
main reason why registers are used as they available within the memory
and they are the fastest accessible storage location.
Example 1:
R1<--- a
R2<--- b
R3<--- c
R4<--- d
MOV R3, c
MOV R4, d
MUL R3, R4
MOV R2, b
ADD R2, R3
MOV R1, R2
MOV a, R1
Example 2:
R1<--- e
R2<--- f
R3<--- g
R4<--- h
MOV R3, g
MOV R4, h
DIV R3, R4
MOV R2, f
SUB R2, R3
MOV R1, R2
MOV e, R1
Advantages :
Fast accessible storage
Allows computations to be performed on them
Deterministic as it incurs no miss
Reduce memory traffic
Reduces overall computation time
Disadvantages :
Registers are generally available in small amount ( up to few hundred
Kb )
Register sizes are fixed and it varies from one processor to another
Registers are complicated
Need to save and restore changes during context switch and procedure
calls.
Input: The input for the basic blocks will be a sequence of three-address
code.
Output: The output is a list of basic blocks with each three address
statements in exactly one block.
Flow Graph
It is a directed graph. After partitioning an intermediate code into basic
blocks, the flow of control among basic blocks is represented by a flow
graph. An edge can flow from one block X to another block Y in such a
case when the Y block’s first instruction immediately follows the X
block’s last instruction. The following ways will describe the edge:
Block B1 is the entry point for the flow graph because B1 contains
starting instruction.
Why Optimize?
Optimizing an algorithm is beyond the scope of the code
optimization phase. So the program is optimized. And it may
involve reducing the size of the code. So optimization helps to:
//After Optimization
c=a*b
x=a
till
d=a*b+4
Hence, after variable propagation, a*b and x*b will be identified as
common sub-expression.
//After elimination :
c=a*b
till
d=a*b+4
Code Motion :
• Reduce the evaluation frequency of expression.
• Bring loop invariant statements out of the loop.
a = 200;
while(a>0)
{
b = x + y;
if (a % b == 0}
printf(“%d”, a);
}
//After Reduction
i=1
t=4
{
while( t<40)
y = t;
t = t + 4;
}
Where to apply Optimization?
Now that we learned the need for optimization and its two
types,now let’s see where to apply these optimization.
Source program
Optimizing the source program involves making changes to the
algorithm or changing the loop structures.User is the actor here.
Intermediate Code
Optimizing the intermediate code involves changing the address
calculations and transforming the procedure calls involved. Here
compiler is the actor.
Target Code
Optimizing the target code is done by the compiler. Usage of
registers,select and move instructions is part of optimization
involved in the target code.
Phases of Optimization
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 the value of i is not been changed
from definition to use.
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;
The optimization using copy propagation can be done as follows:
A=Pi*r*r;
Here the variable x is eliminated
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.
Example:
i=0;
if(i=1)
{
a=b+5;
}
Here, ‘if’ statement is dead code because this condition will never
get satisfied.
Constant folding:
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.
Loop Optimizations:
In loops, especially in the inner loops, programs tend to spend the
bulk of their time. The running time of a program may be
improved if the number of instructions in an inner loop is
decreased, even if we increase the amount of code outside that
loop.
Three techniques are important for loop optimization:
Ø Code motion, which moves code outside a loop;
Ø Induction-variable elimination, which we apply to replace
variables from inner loop.
Ø Reduction in strength, which replaces and expensive
operation by a cheaper one, such as a multiplication by an addition.
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 */
Induction Variables :
Loops are usually processed inside out. For example consider the
loop around B3. Note that the values of j and t4 remain in lock-
step; every time the value of j decreases by 1, that of t4 decreases
by 4 because 4*j is assigned to t4. Such identifiers are called
induction variables.
When there are two or more induction variables in a loop, it may
be possible to get rid of all but one, by the process of induction-
variable elimination. For the inner loop around B3 in Fig.5.3 we
cannot get rid of either j or t4 completely; t4 is used in B3 and j in
B4.
However, we can illustrate reduction in strength and illustrate a
part of the process of induction-variable elimination. Eventually j
will be eliminated when the outer loop of B2- B5 is considered.
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
Fig.5.3.
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.
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.
Optimization of Basic Blocks:
Optimization process can be applied on a basic block. While
optimization, we don't need to change the set of expressions
computed by the block.
There are two type of basic block optimization. These are as
follows:
1. Structure-Preserving Transformations
2. Algebraic Transformations
1. Structure preserving transformations:
The primary Structure-Preserving Transformation on basic blocks
is as follows:
o Common sub-expression elimination
o This can be caused when once declared and defined once and
forget to remove them in this case they serve no purpose.
o Suppose the statement x:= y + z appears in a block and x is dead
symbol that means it will never subsequently used. Then without
changing the value of the basic block you can safely remove this
statement.
(c) Renaming temporary variables
A statement t:= b + c can be changed to u:= b + c where t is a
temporary variable and u is a new temporary variable. All the
instance of t can be replaced with the u without changing the basic
block value.
(d) Interchange of statement
Suppose a block has the following two adjacent statements:
1. t1 : = b + c
2. t2 : = x + y
These two statements can be interchanged without affecting the
value of block when value of t1 does not affect the value of t2.
2. Algebraic transformations:
o In the algebraic transformation, we can change the set of
expression into an algebraically equivalent set. Thus the expression
x:= x + 0 or x:= x *1 can be eliminated from a basic block without
changing the set of expression.
o Constant folding is a class of related optimization. Here at compile
time, we evaluate constant expressions and replace the constant
expression by their values. Thus the expression 5*2.7 would be
replaced by13.5.
o Sometimes the unexpected common sub expression is generated by
the relational operators like <=, >=, <, >, +, = etc.
o Sometimes associative expression is applied to expose common
sub expression without changing the basic block value. if the
source code has the assignments
1. a:= b + c
2. e:= c +d +b
The following intermediate code may be generated:
1. a:= b + c
2. t:= c +d
3. e:= t + b