0% found this document useful (0 votes)
151 views

Code Generation: Issues in The Design of A Code Generator

The document discusses issues in code generation, including input to the code generator, target programs, memory management, instruction selection, register allocation, evaluation order, and program and instruction costs. A code generator converts source code into machine-executable form. It faces challenges like selecting optimal instructions, efficiently allocating registers, and generating code that is correct, maintainable, and testable. Register allocation involves assigning variables to registers and minimizing register swapping, and is an NP-complete problem.
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
151 views

Code Generation: Issues in The Design of A Code Generator

The document discusses issues in code generation, including input to the code generator, target programs, memory management, instruction selection, register allocation, evaluation order, and program and instruction costs. A code generator converts source code into machine-executable form. It faces challenges like selecting optimal instructions, efficiently allocating registers, and generating code that is correct, maintainable, and testable. Register allocation involves assigning variables to registers and minimizing register swapping, and is an NP-complete problem.
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 33

CODE GENERATION

Issues in the design of a code generator


Code generator converts the intermediate representation of source code
into a form that can be readily executed by the machine. A code
generator is expected to generate a correct code. Designing of code
generator should be done in such a way so that it can be easily
implemented, tested and maintained.

The following issue arises during the code generation phase:

1. Input to code generator –


The input to code generator is the intermediate code generated by the
front end, along with information in the symbol table that determines the
run-time addresses of the data-objects denoted by the names in the
intermediate representation. Intermediate codes may be represented
mostly in quadruples, triples, indirect triples, Postfix notation, syntax
trees, DAG’s etc. Assume that they are free from all of syntactic and
state semantic errors, the necessary type checking has taken place and
the type-conversion operators have been inserted wherever necessary.

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

1. Here the fourth statement is redundant as the value of the P is loaded


again in that statement that just has been stored in the previous
statement. It leads to an inefficient code sequence. A given intermediate
representation can be translated into many code sequences, with
significant cost differences between the different implementations. A
prior knowledge of instruction costis needed in order to design good
sequences, but accurate cost information is difficult to predict.
2. Register allocation issues –
Use of registers make the computations faster in comparison to that of
memory, so efficient utilization of registers is important. The use of
registers are subdivided into two subproblems:
1. During Register allocation – we select only those set of
variables that will reside in the registers at each point in the program.
2. During a subsequent Register assignment phase, the specific
register is picked to access the variable.
As the number of variables increase, the optimal assignment of registers
to variables becomes difficult. Mathematically, this problem becomes
NP-complete. Certain machine requires register pairs consist of an even
and next odd-numbered register. For example

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.

Target code generation is the final Phase of Compiler.


1. Input : Optimized Intermediate
Representation.
2. Output : Target Code.
3. Task Performed : Register
allocation methods and optimization, assembly level code.
4. Method : Three popular
strategies for register allocation and optimization.

Computations are generally assumed to be performed on high speed


memory locations, known as registers. Performing various operations on
registers is efficient as registers are faster than cache memory. This
feature is effectively used by compilers, However registers are not
available in large amount and they are costly. Therefore we should try to
use minimum number of registers to incur overall low cost.
Optimized code :
Example 1 :
L1: a = b + c * d

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.

RUNTIME STORAGE MANAGEMENT:


During the execution of a program, the same name in the source can
denote different data objects in the computer. The allocation and
deallocation of data objects is managed by the run-time support
package . Terminologies:
• name → storage space: the mapping of a name to a storage space is
called environment .
• storage space → value: the current value of a storage space is called its
state.
• The association of a name to a storage location is called a binding.
Each execution of a procedure is called an activation .
• If it is a recursive procedure, then several of its activations may exist at
the same time.
• Life time: the time between the first and last steps in a procedure.
• A recursive procedure needs not to call itself directly.
General run time storage layout code static data stack heap dynamic
space storage space that won’t change: global data, constant, ... lower
memory address higher memory address For activation records: local
data, parameters, control info, ... For dynamic memory allocated by the
program
Activation record
 returned value
 actual parameters
 optional control link
 optional access link
 saved machine status
 local data
 temporaries
Activation record: data about an execution of a procedure.
• Parameters:
. Formal parameters: the declaration of parameters.
. Actual parameters: the values of parameters for this activation.
• Links:
. Access (or static) link: a pointer to places of non-local data,
. Control (or dynamic) link: a pointer to the activation record of the
caller.
Static storage allocation (1/3) There are two different approaches for run
time storage allocation.
• Static allocation.
• Dynamic allocation. Static allocation: uses no stack and heap.
• A.R. in static data area, one per procedure.
• Names bounds to locations at compiler time.
• Every time a procedure is called, its names refer to the same
preassigned location.
• Disadvantages: . No recursion. . Waste lots of space when inactive. .
No dynamic allocation.
• Advantage: . No stack manipulation or indirect access to names, i.e.,
faster in accessing variables. . Values are retained from one procedure
call to the next. For example: static variables in C.
On procedure calls:
• the calling procedure: . First evaluate arguments. . Copies arguments
into parameter space in the A.R. of called procedure. Convention: call
that which is passed to a procedure arguments from the calling side, and
parameters from the called side. . May save some registers in its own
A.R. . Jump and link: jump to the first instruction of called procedure
and put address of next instruction (return address) into register RA (the
return address register).
• the called procedure: . Copies return address from RA into its A.R.’s
return address field. . May save some registers. . May initialize local
data.
On procedure returns,
• the called procedure: . Restores values of saved registers. . Jump to
address in the return address field.
• the calling procedure: . May restore some registers. . If the called
procedure was actually a function, put return value in an appropriate
place.

Basic Blocks and Flow Graphs


In this section, we are going to learn how to work with basic block and
flow graphs in compiler design.
Basic Block
The basic block is a set of statements. The basic blocks do not have any
in and out branches except entry and exit. It means the flow of control
enters at the beginning and will leave at the end without any halt. The set
of instructions of basic block executes in sequence.
Here, the first task is to partition a set of three-address code into the
basic block. The new basic block always starts from the first instruction
and keep adding instructions until a jump or a label is met. If no jumps
or labels are found, the control will flow in sequence from one
instruction to another.
The algorithm for the construction of basic blocks is given below:

Algorithm: Partitioning three-address code into basic blocks.

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.

METHOD: First, we will identify the leaders in the intermediate code.


There are some rules for finding leaders, which are given below:
1. The first instruction in the intermediate code will always be a
leader.
2. The instructions that target a conditional or unconditional jump
statement are termed as a leader.
3. Any instructions that are just after a conditional or unconditional
jump statement will be a leader.
Each leader’s basic block will have all the instructions from the leader
itself until the instruction, which is just before the starting of the next
leader.
Example:
Consider the following source code for a 10 x 10 matrix to an identity
matrix

 Instruction 2 is also a leader because this instruction is the target


for instruction 11.

 Instruction 3 is also a leader because this instruction is the target


for instruction 9.

 Instruction 10 is also a leader because it immediately follows the


conditional goto statement.

 Similar to step 4, instruction 12 is also a leader.

 Instruction 13 is also a leader because this instruction is the target


for instruction 17.
So there are six basic blocks for the above code, which are given below:
B1 for statement 1
B2 for statement 2
B3 for statement 3-9
B4 for statement 10-11
B5 for statement 12
B6 for statement 13-17.

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:

 There is a conditional or unconditional jump from the end of X to


the starting of Y.

 Y immediately follows X in the original order of the three-address


code, and X does not end in an unconditional jump.

 Flow graph for the 10 x 10 matrix to an identity matrix.

 Block B1 is the entry point for the flow graph because B1 contains
starting instruction.

 B2 is the only successor of B1 because B1 doesn’t end with


unconditional jumps, and the B2 block’s leader immediately
follows the B1 block’s leader.

 B3 block has two successors. One is a block B3 itself because the


first instruction of the B3 block is the target for the conditional
jump in the last instruction of block B3. Another successor is block
B4 due to conditional jump at the end of B3 block.

 B6 block is the exit point of the flow graph.

Code Optimization in Compiler Design


The code optimization in the synthesis phase is a program
transformation technique, which tries to improve the intermediate
code by making it consume fewer resources (i.e. CPU, Memory) so
that faster-running machine code will result. Compiler optimizing
process should meet the following objectives :

 The optimization must be correct, it must not, in any way, change


the meaning of the program.

 Optimization should increase the speed and performance of the


program.

 The compilation time must be kept reasonable.

 The optimization process should not delay the overall compiling


process.
When to Optimize?
Optimization of the code is often performed at the end of the
development stage since it reduces readability and adds code that is
used to increase the performance.

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:

 Reduce the space consumed and increases the speed of


compilation.

 Manually analyzing datasets involves a lot of time. Hence we


make use of software like Tableau for data analysis. Similarly
manually performing the optimization is also tedious and is better
done using a code optimizer.
 An optimized code often promotes re-usability.

Types of Code Optimization –The optimization process can


be broadly classified into two types :
1. Machine Independent Optimization – This code optimization
phase attempts to improve the intermediate code to get a better
target code as the output. The part of the intermediate code which
is transformed here does not involve any CPU registers or absolute
memory locations.
2. Machine Dependent Optimization – 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
the memory hierarchy.
Code Optimization is done in the following different ways :
Compile Time Evaluation :
(i) A = 2*(22.0/7.0)*r
Perform 2*(22.0/7.0)*r at compile time.
(ii) x = 12.4
y = x/2.3
Evaluate x/2.3 as 12.4/2.3 at compile time.
Variable Propagation :
//Before Optimization
c=a*b
x=a
till
d=x*b+4

//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.

Dead code elimination : Variable propagation often leads to


making assignment statement into dead code
c=a*b
x=a
till
d=a*b+4

//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);
}

//This code can be further optimized as


a = 200;
b = x + y;
while(a>0)
{
if (a % b == 0}
printf(“%d”, a);
}
Induction Variable and Strength Reduction :
• An induction variable is used in loop for the following kind of
assignment i = i + constant.
• Strength reduction means replacing the high strength operator by
the low strength.
i = 1;
while (i<10)
{
y = i * 4;
}

//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

There are generally two phases of optimization:


Global Optimization:
Transformations are applied to large program segments that
includes functions,procedures and loops.
Local Optimization:
Transformations are applied to small blocks of statements.The
local optimization is done prior to global optimization.
PRINCIPAL SOURCES OF OPTIMISATION
 
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.
Function-Preserving Transformations
 
There are a number of ways in which a compiler can improve a
program without changing the function it computes.
Function preserving transformations examples:
Common sub expression elimination
Copy propagation,
Dead-code elimination
Constant folding
 
The other transformations come up primarily when global
optimizations are performed.
 
Frequently, a program will include several calculations of the
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.
 Common Sub expressions elimination:
  An occurrence of an expression E is called a common sub-
expression if E was previously computed, and the values of
variables in E have not changed since the previous computation.
We can avoid recomputing the expression if we can use the
previously computed value.

   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 Dead code elimination

o Renaming of temporary variables

o Interchange of two independent adjacent statements

(a) Common sub-expression elimination:


In the common sub-expression, you don't need to be computed it
over and over again. Instead of this you can compute it once and
kept in store from where it's referenced when encountered again.
1. a : = b + c  
2. b : = a - d   
3. c : = b + c                          
4. d : = a - d  
In the above expression, the second and forth expression computed
the same expression. So the block can be transformed as follows:
1. a : = b + c   
2. b : = a - d                                                         
3. c : = b + c  
4. d : = b  
(b) Dead-code elimination
o It is possible that a program contains a large amount of dead code.

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  
 

You might also like