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

Chapter 6 Code generation and Optimization

Chapter Six discusses code generation and optimization in compilers, focusing on the role of intermediate code in translating source code to machine code. It outlines various representations of intermediate code, such as high-level and low-level intermediate code, and explains the importance of code optimization techniques like dead code elimination and loop optimization. Additionally, it addresses design issues in code generation, including memory management and instruction selection for target machines.

Uploaded by

azalechseko
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views

Chapter 6 Code generation and Optimization

Chapter Six discusses code generation and optimization in compilers, focusing on the role of intermediate code in translating source code to machine code. It outlines various representations of intermediate code, such as high-level and low-level intermediate code, and explains the importance of code optimization techniques like dead code elimination and loop optimization. Additionally, it addresses design issues in code generation, including memory management and instruction selection for target machines.

Uploaded by

azalechseko
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 34

CHAPTER SIX

Code Generation and Optimization


❑ Code generation
❑ Design issue
❑ Target machine
❑ Code optimization
❑ Machine independent
❑ Loop optimization
❑ DAG representation
❑ Data flow Analysis 1
Intermediate code
Intermediate code is used to translate the source code into the machine code. Intermediate code lies
between the high-level language and the machine language.
Intermediate code
Intermediate
code Target
Intermediat
Syntactic machine
parser e code
checker code
generator
generator

• If the compiler directly translates source code into the machine code without generating intermediate
code then a full native compiler is required for each new machine.
• The intermediate code keeps the analysis portion same for all the compilers that's why it doesn't need a
full compiler for every unique machine.
• Intermediate code generator receives input from its predecessor phase and semantic analyzer phase. It
takes input in the form of an annotated syntax tree.
• Using the intermediate code, the second phase of the compiler synthesis phase is changed according to
the target machine.

2
Intermediate representation
Intermediate code can be represented in two ways:
1. High Level intermediate code:
High level intermediate code can be represented as source code. To enhance performance of source
code, we can easily apply code modification. But to optimize the target machine, it is less preferred.
2. Low Level intermediate code:
Low level intermediate code is close to the target machine, which makes it suitable for register and
memory allocation etc. it is used for machine-dependent optimizations.
Intermediate code can be either language specific (e.g., Byte Code for Java) or language independent
(three-address code).
Three-Address Code
Intermediate code generator receives input from its predecessor phase, semantic analyzer, in the form of
an annotated syntax tree. That syntax tree then can be converted into a linear representation, e.g.,
postfix notation. Intermediate code tends to be machine independent code. Therefore, code generator
assumes to have unlimited number of memory storage (register) to generate code.
For example:
a = b + c * d;
The intermediate code generator will try to divide this expression into sub-expressions and then generate
the corresponding code.

3
r1 = c * d;
r2 = b + r1;
a = r2
r being used as registers in the target program.
A three-address code has at most three address locations to calculate the expression. A three-address
code can be represented in two forms : quadruples and triples.
Quadruples
Each instruction in quadruples presentation is divided into four fields: operator, arg1, arg2, and result.
The above example is represented below in quadruples format:

Op arg1 arg2 result


* c d r1
+ b r1 r2
+ r2 r1 r3
= r3 a

4
Triples
Each instruction in triples presentation has three fields : op, arg1, and arg2.The results of respective
sub-expressions are denoted by the position of expression. Triples represent similarity with DAG
and syntax tree. They are equivalent to DAG while representing expressions.

Op arg1 arg2
* c d
+ b (0)
+ (1) (0)
= (2)
Triples face the problem of code immovability while optimization, as the results are positional and
changing the order or position of an expression may cause problems.

Indirect Triples
This representation is an enhancement over triples representation. It uses pointers instead of position
to store results. This enables the optimizers to freely re-position the sub-expression to produce an
optimized code.

5
Postfix Notation

• Postfix notation is the useful form of intermediate code if the given language is expressions.
• Postfix notation is also called as 'suffix notation' and 'reverse polish'.
• Postfix notation is a linear representation of a syntax tree.
• In the postfix notation, any expression can be written unambiguously without parentheses.
• The ordinary (infix) way of writing the sum of x and y is with operator in the middle: x * y. But in the
postfix notation, we place the operator at the right end as xy *.
• In postfix notation, the operator follows the operand.

Example:
Production
E → E1 op E2
E → (E1)
E → id
Semantic Rule Program fragment
E.code = E1.code || E2.code || op print op
E.code = E1.code
E.code = id print id

6
Parse tree and Syntax tree
• When you create a parse tree then it contains more details than actually needed. So, it is very
difficult to compiler to parse the parse tree. Take the following parse tree as an example:

• In the parse tree, most of the leaf nodes are single child to their parent nodes.
• In the syntax tree, we can eliminate this extra information.
• Syntax tree is a variant of parse tree. In the syntax tree, interior nodes are operators and leaves are
operands.
• Syntax tree is usually used when represent a program in a tree structure.

7
A sentence id + id * id would have the following
syntax tree:

Abstract syntax tree can be represented as:


Abstract syntax trees are important data structures in
a compiler. It contains the least unnecessary
information.

Abstract syntax trees are more compact than a parse


tree and can be easily used by a compiler.

8
Code Generator
Code generator is used to produce the target code for three-address statements. It uses registers to
store the operands of the three address statement.
Example:
Consider the three address statement x:= y + z. It can have the following sequence of codes:
MOV x, R0
ADD y, R0
Register and Address Descriptors:
A register descriptor contains the track of what is currently in each register. The register descriptors
show that all the registers are initially empty.
An address descriptor is used to store the location where current value of the name can be found at
run time.
A 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.

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

10
Code sequence for the example is as follows:

Statement Code Generated Register Address


descriptor 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 co


MOV R0, d

11
Design Issues
In the code generation phase, various issues can arises:
• Input to the code generator
• Target program
• Memory management
• Instruction selection
• Register allocation
• Evaluation order
1. Input to the code generator
The input to the code generator contains the intermediate representation of the source program and
the information of the symbol table. The source program is produced by the front end.
Intermediate representation has the several choices:
a) Postfix notation
b) Syntax tree
c) Three address code
We assume front end produces low-level intermediate representation i.e. values of names in it can
directly manipulated by the machine instructions.
The code generation phase needs complete error-free intermediate code as an input requires.

12
2. Target program:
The target program is the output of the code generator. The output can be:
a) Assembly language: It allows subprogram to be separately compiled.
b) Relocatable machine language: It makes the process of code generation easier.
c) Absolute machine language: It can be placed in a fixed location in memory and can be executed
immediately.

3. Memory management
• During code generation process the symbol table entries have to be mapped to actual p addresses
and levels have to be mapped to instruction address.
• Mapping name in the source program to address of data is co-operating done by the front end and
code generator.
• Local variables are stack allocation in the activation record while global variables are in static area.

4. Instruction selection:
• Nature of instruction set of the target machine should be complete and uniform.
• When you consider the efficiency of target machine then the instruction speed and machine idioms
are important factors.
• The quality of the generated code can be determined by its speed and size.

13
Example:
The Three address code is:
a:= b + c
d:= a + e
Inefficient assembly code is:
MOV b, R0 R0→b
ADD c, R0 R0 →c + R0
MOV R0, a a→R0
MOV a, R0 R0→a
ADD e, R0 R0→ e+R0
MOV R0, d d→R0
5. Register allocation
Register can be accessed faster than memory. The instructions involving operands in register are shorter
and faster than those involving in memory operand.
The following sub problems arise when we use registers:
Register allocation: In register allocation, we select the set of variables that will reside in register.
Register assignment: In Register assignment, we pick the register that contains variable.
Certain machine requires even-odd pairs of registers for some operands and result.

14
For example:
Consider the following division instruction of the form:
D x, y
Where,
x is the dividend even register in even/odd register pair
y is the divisor

Even register is used to hold the reminder.


Old register is used to hold the quotient.

6. Evaluation order
The efficiency of the target code can be affected by the order in which the computations are performed.
Some computation orders need fewer registers to hold results of intermediate than others.

15
Target Machine
The target computer is a type of byte-addressable machine. It has 4 bytes to a word.
The target machine has n general purpose registers, R0, R1,...., Rn-1. It also has two-address instructions
of the form:
op source, destination
Where, op is used as an op-code and source and destination are used as a data field.

It has the following op-codes:


ADD (add source to destination)
SUB (subtract source from destination)
MOV (move source to destination)
The source and destination of an instruction can be specified by the combination of registers and
memory location with address modes.

16
MODE FORM ADDRESS EXAMPLE ADDED COST

absolute M M Add R0, R1 1


register R R Add temp, R1 0
indexed c(R) C+ ADD 100 (R2), 1
contents(R) R1
indirect *R contents(R) ADD * 100 0
register
indirect *c(R) contents(c+ (R2), R1 1
indexed contents(R))
literal #c c ADD #3, R1 1

• Here, cost 1 means that it occupies only one word of memory.


• Each instruction has a cost of 1 plus added costs for the source and destination.
• Instruction cost = 1 + cost is used for source and destination mode

17
Example:
1. Move register to memory R0 → M

MOV R0, M
cost = 1+1+1 (since address of memory location M is in word following the instruction)
2. Indirect indexed mode:

MOV * 4(R0), M
cost = 1+1+1 (since one word for memory location M, one word
for result of *4(R0) and one for instruction)
3. Literal Mode:

MOV #1, R0
cost = 1+1+1 = 3 (one word for constant 1 and one for instruction)

18
Machine-Independent Optimization
Machine independent optimization attempts to improve the intermediate code to get a better target
code. The part of the code which is transformed here does not involve any absolute memory location or
any CPU registers.
The process of intermediate code generation introduces much inefficiency like: using variable instead of
constants, extra copies of variable, repeated evaluation of expression. Through the code optimization,
you can remove such efficiencies and improves code.
It can change the structure of program sometimes of beyond recognition like: unrolls loops, inline
functions, eliminates some variables that are programmer defined.
Code Optimization can perform in the following different ways:

(1) Compile Time Evaluation:


(a) z = 5*(45.0/5.0)*r
Perform 5*(45.0/5.0)*r at compile time.

(b) x = 5.7
y = x/3.6
Evaluate x/3.6 as 5.7/3.6 at compile time.

19
(2) Variable Propagation:
Before Optimization the code is:
After Optimization the code is:
c=a*b
x=a
c=a*b
till x=a
d=x*b+4 till
d=a*b+4
Here, after variable propagation a*b and x*b identified as common sub expression.
(3) Dead code elimination:
Before elimination the code is:
c=a*b
After elimination the code is:
x=b
till c=a*b
d=a*b+4 till
d=a*b+4
Here, x= b is a dead state because it will never subsequently used in the program. So, we can eliminate
this state.

20
(4) Code Motion:
It reduces the evaluation frequency of expression.
It brings loop invariant statements out of the loop.
do
{
item = 10;
valuevalue = value + item;
} while(value<100);

//This code can be further optimized as

item = 10;
do
{
valuevalue = value + item;
} while(value<100);

21
(5) Induction Variable and Strength Reduction:

Strength reduction is used to replace the high strength operator by the low strength.
An induction variable is used in loop for the following kind of assignment like i = i + constant.
Before reduction the code is:
i = 1;
while(i<10) {
y = i * 4;
i++;
}

After Reduction the code is:


t = 4;
while( t<40) {
y = t;
t += 4;
}

22
Loop Optimization
Loop optimization is most valuable machine-independent optimization because program's inner loop
takes bulk to time of a programmer.
If we decrease the number of instructions in an inner loop then the running time of a program may be
improved even if we increase the amount of code outside that loop.
For loop optimization the following three techniques are important:
✓ Code motion
✓ Induction-variable elimination
✓ Strength reduction
1.Code Motion:
Code motion is used to decrease the amount of code in loop. This transformation takes a statement or
expression which can be moved outside the loop body without affecting the semantics of the program.
For example
In the while statement, the limit-2 equation is a loop invariant equation.
while (i<=limit-2) /*statement does not change limit*/
After code motion the result is as follows:
a= limit-2;
while(i<=a) /*statement does not change limit or a*/

23
2.Induction-Variable Elimination
• Induction variable elimination is used to replace variable from inner loop.
• It can reduce the number of additions in a loop.
• It improves both code space and run time performance.

In this figure, we can replace the assignment t4:=4*j by t4:=t4-4. The only problem which will be arose
that t4 does not have a value when we enter block B2 for the first time. So we place a relation t4=4*j
on entry to the block B2.
24
3.Reduction in Strength
• Strength reduction is used to replace the expensive operation by the cheaper once on the target
machine.
• Addition of a constant is cheaper than a multiplication. So we can replace multiplication with an
addition within the loop.
• Multiplication is cheaper than exponentiation. So we can replace exponentiation with multiplication
within the loop.
Example:
while (i<10) {
j= 3 * i+1;
a[j]=a[j]-2;
i=i+2;
}
After strength reduction the code will be:
s= 3*i+1;
while (i<10) {
j=s;
a[j]= a[j]-2;
i=i+2;
s=s+6;
}
In the above code, it is cheaper to compute s=s+6 than j=3 *i

25
Directed Acyclic Graph representation for basic blocks
A DAG for basic block is a directed acyclic graph with the following labels on nodes:
✓ The leaves of graph are labeled by unique identifier and that identifier can be variable names or
constants.
✓ Interior nodes of the graph is labeled by an operator symbol.
✓ Nodes are also given a sequence of identifiers for labels to store the computed value.
• DAGs are a type of data structure. It is used to implement transformations on basic blocks.
• DAG provides a good way to determine the common sub-expression.
• 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:
✓ Each node contains a label. For leaves, the label is an identifier.
✓ 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

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

27
Example: Stages in DAG Construction:
Consider the following three address
statement:
S1:= 4 * i0
S2:= a[S1]
S3:= 4 * i0
S4:= b[S3]
S5:= s2 * S4
S6:= prod + S5
Prod:= s6
S7:= i0+1
i0:= S7
if i0<= 20 goto (1)

28
S1:= 4 * i0
4*i0 node exist already
S2:= a[S1]
hence attach identifier s3
S3:= 4 * i0
to the existing node for
S4:= b[S3]
statement(3)
S5:= s2 * S4
S6:= prod + S5
Prod:= s6
S7:= i0+1
i0 := S7
if i0<= 20 goto (1)

Statement (5)
Statement(4)

29
S1:= 4 * i0
S2:= a[S1] Statement(6),attach statement(8),attach identifier
S3:= 4 * i0 identifier prod for i for statement(9)
S4:= b[S3] statement (7)
S5:= s2 * S4
S6:= prod + S5
Prod:= s6
S7:= i0+1
i0 := S7
if i0<= 20 goto (1)

30
Given: Generated DAG:

S1:= 4 * i0
S2:= a[S1]
S3:= 4 * i0
S4:= b[S3]
S5:= s2 * S4
S6:= prod + S5
Prod:= s6
S7:= i0+1
i0 := S7
if i0<= 20 goto (1)

31
Global data flow analysis
• To efficiently optimize the code compiler collects all the information about the program and
distribute this information to each block of the flow graph. This process is known as data-flow graph
analysis.
• Certain optimization can only be achieved by examining the entire program.
• It can't be achieve by examining just a portion of the program.
• For this kind of optimization user defined chaining is one particular problem.
• Here using the value of the variable, we try to find out that which definition of a variable is
applicable in a statement.
• Based on the local information a compiler can perform some optimizations. For example, consider
the following code:
x = a + b;
x=6*3;
In this code, the first assignment of x is useless. The value computer for x is never used in the program.
At compile time the expression 6*3 will be computed, simplifying the second assignment statement to x
= 18;

32
Some optimization needs more global information. For example, consider the following code:
a = 1;
b = 2;
c = 3;
if (....) x = a + 5;
else x = b + 4;
c = x + 1;
In this code, at line 3 the initial assignment is useless and x +1 expression can be simplified as 7.

But it is less obvious that how a compiler can discover these facts by looking only at one or two
consecutive statements. A more global analysis is required so that the compiler knows the following
things at each point in the program:
✓ Which variables are guaranteed to have constant values
✓ Which variables will be used before being redefined
Data flow analysis is used to discover this kind of property.
The data flow analysis can be performed on the program's control flow graph (CFG).
The control flow graph of a program is used to determine those parts of a program to which a particular
value assigned to a variable might propagate.

33
34

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy