0% found this document useful (0 votes)
16 views44 pages

CD Module 5 Answers

The document discusses various code optimization techniques and concepts in compiler design, including induction variable elimination, common subexpression elimination, and peephole optimization. It explains the structure of basic blocks and transformations that preserve their structure while improving efficiency. Additionally, it covers issues in code generation, differentiates between local and global optimizations, and illustrates the role of register and address descriptors in the code generation phase.

Uploaded by

Joseph Liyon
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)
16 views44 pages

CD Module 5 Answers

The document discusses various code optimization techniques and concepts in compiler design, including induction variable elimination, common subexpression elimination, and peephole optimization. It explains the structure of basic blocks and transformations that preserve their structure while improving efficiency. Additionally, it covers issues in code generation, differentiates between local and global optimizations, and illustrates the role of register and address descriptors in the code generation phase.

Uploaded by

Joseph Liyon
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/ 44

Module 5: Code Optimization and Code

Generation - Answers
Part A (3 Marks)
1. With suitable example explain induction variable elimination technique for
loop optimization.
Induction variable elimination is a loop optimization technique that identifies and eliminates
induction variables from loops to improve efficiency. An induction variable is a variable whose
value changes by a fixed amount in each iteration of a loop.

Example:

for(i=0; i<n; i++) {


j = 4*i;
a[j] = a[j] + 2;
}

In this loop, both i and j are induction variables. The variable j depends on i and increases by 4 in
each iteration. We can eliminate j by substituting its value directly:

for(i=0; i<n; i++) {


a[4*i] = a[4*i] + 2;
}

This optimization eliminates the redundant computation of j in each iteration, reducing the
number of operations and improving loop efficiency.

2. Explain any four principal sources of optimization.

Four principal sources of optimization are:

1. Common Subexpression Elimination: Identifying and reusing the results of already


computed expressions instead of recomputing them. For example, in the expression a =
b + c; d = b + c + e;, the subexpression b + c is computed twice. We can optimize
by computing it once and reusing: t = b + c; a = t; d = t + e;
2. Dead Code Elimination: Removing statements that have no effect on the program
output. For example, if a variable is assigned a value but never used afterward, the
assignment can be eliminated.
3. Constant Folding: Evaluating constant expressions at compile time rather than runtime.
For example, the expression x = 5 + 3 can be optimized to x = 8.
4. Copy Propagation: Replacing occurrences of variables with their assigned values. For
example, in the code a = b; c = a + d;, we can replace a with b to get a = b; c = b
+ d;
3. What is a basic block? Explain about the structure preserving transformations
on a basic block?
A basic block is a sequence of consecutive 3-address statements that may be entered only at the
beginning and when entered, statements are executed in sequence without halting or branching.

Structure preserving transformations on a basic block include:

1. Dead Code Elimination: Removing statements that have no effect on program output
2. Copy Propagation: Including variable propagation and constant propagation
3. Common Subexpression Elimination: Identifying and reusing already computed
expressions
4. Strength Reduction: Replacing expensive operations with cheaper ones
5. Constant Folding: Computing constant expressions at compile time
6. Interchange of Independent Statements: Reordering statements that do not depend on
each other

These transformations preserve the basic block structure while improving code efficiency.

4. Explain peephole optimization techniques with example.

Peephole optimization is a technique that examines a small sequence of target instructions


(called the peephole) and replaces them with a shorter or faster sequence whenever possible. The
key peephole optimization techniques include:

1. Redundant Load/Store Elimination: Example:


2. MOV a, R0
3. MOV R0, b
4. MOV b, R0

Can be optimized to:

MOV a, R0
MOV R0, b

The third instruction is redundant since b is already in R0.

5. Remove Unreachable Code: Eliminating code that can never be executed.


6. Flow of Control Optimization: Example:
7. JMP L1
8. L1: JMP L2

Can be optimized to:

JMP L2

9. Algebraic Simplifications: Example:


10. MUL R1, 1

Can be eliminated since multiplying by 1 doesn't change the value.

11. Use of Machine Idioms: Using specialized machine instructions. Example: Using INC a
instead of a = a + 1

5. Explain any three issues in the design of a code generator.

Three issues in the design of a code generator are:

1. Instruction Selection: The code generator must map the intermediate representation (IR)
into a code sequence that can be executed by the target machine. The complexity depends
on the level of IR, the nature of the instruction-set architecture, and the desired quality of
the generated code. If the target machine has special instructions like increment (INC),
then a = a+1 can be implemented more efficiently using INC a rather than a sequence of
load, add, and store instructions.
2. Register Allocation: Efficient use of registers is crucial for generating good code. The
use of registers should be coordinated in such a way that a minimal number of loads and
stores are generated. The code generator must decide what values should remain in
registers and when registers need to be stored to memory. Poor register allocation can
significantly degrade performance.
3. Choice of Evaluation Order: The order in which computations are performed can affect
the efficiency of the target code. Some evaluation orders require fewer registers and
instructions than others. Picking the best evaluation order is an NP-complete problem, but
heuristics can be used to find a good order.

6. Differentiate local and global optimization in compiler design.

Local Optimization:

 Applied over a small segment of program code (basic block)


 Statements are executed in sequential order without any jump or branch
 Examples include common subexpression elimination, constant folding, and copy
propagation within a basic block
 Easier to implement compared to global optimization
 Limited scope of optimization

Global Optimization:

 Applied over a large segment of the program like loops, procedures, functions, etc.
 Considers the flow of control between basic blocks
 Examples include loop optimization techniques like code motion, induction variable
elimination, and loop unrolling
 More complex to implement than local optimization
 Provides more opportunities for optimization
 Local optimization must be done before applying global optimization

7. Write the algorithm for partitioning a sequence of three-address instructions


into basic blocks.

Algorithm to partition three-address code into basic blocks:

1. Determine the set of leaders (first statements of basic blocks):


o The first statement of the program is a leader
o Any statement that is the target of a conditional or unconditional goto/jump is a
leader
o Any statement that immediately follows a goto/jump statement is a leader
2. For each leader, its basic block consists of the leader and all statements up to but not
including the next leader or the end of the program.
3. Form a flow graph whose nodes are the basic blocks and whose edges represent possible
transfers of control between basic blocks:
o If block B1 ends with a conditional or unconditional jump to a statement that is
the leader of block B2, add an edge from B1 to B2.
o If block B1 does not end with an unconditional jump and block B2 immediately
follows B1 in the original order, add an edge from B1 to B2.

8. Explain code motion with an example.


Code motion is a loop optimization technique that involves moving code that computes the same
value in each iteration of a loop to outside the loop, thereby reducing the computation cost.

Example:

for(i=0; i<n; i++) {


x = y + z;
a[i] = x * i;
}

In this code, the expression y + z is computed in each iteration but its value doesn't change
throughout the loop. Using code motion, we can move this computation outside the loop:

x = y + z;
for(i=0; i<n; i++) {
a[i] = x * i;
}

By moving the invariant expression y + z outside the loop, we compute it only once instead of n
times, significantly improving the performance for large loops.

9. Differentiate local and global optimizations.

(This question is a repeat of question 6. Please refer to the answer provided for question 6.)
10. Illustrate the role of register descriptor and address descriptor in code
generation phase.
In the code generation phase, register descriptors and address descriptors help manage the
allocation and usage of registers efficiently:

Register Descriptor:

 Keeps track of what is currently in each register


 For each register, it maintains information about what values (variables) are stored in it
 Helps avoid unnecessary load operations by reusing values already in registers
 Updated whenever values are loaded into or stored from registers

Address Descriptor:

 Keeps track of the location(s) where the current value of a variable can be found
 For each variable, it records whether it is in a register, memory, or both
 Helps in determining the best location to access a variable's value
 Updated whenever a variable's value changes or is moved to a different location

Example:

t1 = a + b
t2 = c + d
t3 = t1 * t2

When generating code for these statements:

1. Load values of a and b into registers R1 and R2


o Register descriptor: R1 contains a, R2 contains b
o Address descriptor: a is in memory and R1, b is in memory and R2
2. Add R1 and R2, store result in R3
o Register descriptor: R1 contains a, R2 contains b, R3 contains t1
o Address descriptor: t1 is in R3
3. Load values of c and d into R4 and R5
o Register descriptor: R1-R5 updated accordingly
o Address descriptor: updated for all variables
4. Add R4 and R5, store result in R6
5. Multiply R3 and R6, store result in R7

The descriptors help optimize register usage and minimize memory accesses.

11. How the peephole optimization technique makes its role in the compilation
process?
Peephole optimization plays an important role in the compilation process by improving the target
code through local transformations. It examines a small sequence of target instructions (the
peephole) and replaces them with a shorter or faster sequence whenever possible. Its role
includes:

1. Efficiency Improvement: It improves both execution speed and code size by eliminating
redundancies and simplifying instructions.
2. Machine-Dependent Optimization: It operates on the target code, allowing
optimizations specific to the target machine architecture.
3. Final Optimization Pass: It typically runs as one of the final optimization phases,
catching inefficiencies that may have been introduced by earlier code generation steps.
4. Local Transformations: It performs local transformations that might be missed by
global optimization techniques.
5. Specific Optimizations:
o Eliminates redundant load/store operations
o Removes unreachable code
o Performs flow of control optimizations
o Applies algebraic simplifications
o Replaces sequences with machine idioms

By performing these optimizations, peephole optimization helps produce more efficient target
code without changing the program's meaning.

12. Explain common sub expression elimination with an example.


Common Subexpression Elimination (CSE) is an optimization technique that identifies and
eliminates repeated computations of the same expression by reusing previously computed values.

Example: Consider the following code segment:

a = b + c
d = b + c + e

Here, the expression b + c is computed twice. Using CSE, we can optimize this code as:

t = b + c
a = t
d = t + e

By computing b + c once and storing its result in a temporary variable t, we avoid the
redundant computation in the second statement. This optimization saves both execution time and
the number of operations performed.

Another example:

x = a * b + a * c
y = a * b + d
After applying CSE:

t = a * b
x = t + a * c
y = t + d

This optimization is particularly valuable in expressions with expensive operations like


multiplication, division, or function calls.

Part B (14 Marks)


1. a) Explain any four principal sources of optimization.

Principal Sources of Optimization:

1. Common Subexpression Elimination (CSE): Common subexpression elimination


identifies expressions that are computed more than once and computes them only once,
storing the result for later use.

Example:

a = b + c
d = b + c + e

After CSE:

temp = b + c
a = temp
d = temp + e

This optimization reduces computation redundancy and improves execution speed. CSE
is particularly beneficial when the common subexpressions involve expensive operations
like multiplication, division, or function calls.

2. Dead Code Elimination: Dead code elimination removes statements that have no effect
on the program output.

Types of dead code:

o Unreachable code: Code that can never be executed


o Code that computes values never used
o Code that has no effect on program output

Example:

x = 10;
if (0) {
y = x + 5; // This code is unreachable
}
a = b + 5;
a = c * 2; // The first assignment to 'a' is dead code

After dead code elimination:

x = 10;
a = c * 2;

3. Constant Folding and Propagation:


o Constant folding evaluates constant expressions at compile time
o Constant propagation replaces variables with their constant values

Example:

x = 5;
y = x + 3;
z = y * 2;

After constant folding and propagation:

x = 5;
y = 8;
z = 16;

This reduces the number of operations at runtime and can enable further optimizations.

4. Loop Optimization: Loop optimization techniques improve the efficiency of loops,


which are often the most executed parts of a program.

Techniques include:

o Code motion: Moving invariant computations outside loops


o Strength reduction: Replacing expensive operations with cheaper ones
o Loop unrolling: Expanding loop body to reduce loop control overhead
o Loop jamming: Combining similar loops to reduce loop overhead
o Induction variable elimination: Removing variables that change by fixed amounts

Example of code motion:

for (i = 0; i < n; i++) {


x = y * z;
a[i] = x + i;
}

After code motion:

x = y * z;
for (i = 0; i < n; i++) {
a[i] = x + i;
}

These optimization techniques significantly improve program efficiency by reducing


computation time, memory usage, and code size.

1. b) What is a basic block? Explain about the structure preserving


transformations on a basic block?

Basic Block:

A basic block is a sequence of consecutive three-address statements that satisfies the following
conditions:

1. It may be entered only at the beginning (the first statement)


2. When entered, all statements in the block are executed sequentially without halting or
branching
3. It contains no internal branch points (jumps or conditional statements)

Basic blocks are identified by finding leader statements:

 The first statement of the program is a leader


 Any statement that is the target of a conditional or unconditional goto/jump is a leader
 Any statement that immediately follows a goto/jump statement is a leader

Each basic block consists of a leader and all statements up to, but not including, the next leader
or the end of the program.

Structure Preserving Transformations on Basic Blocks:

Structure preserving transformations optimize code within a basic block without changing its
structure or control flow. The main techniques include:

1. Dead Code Elimination: Removing statements that have no effect on the program
output.

Example:

x = a + b
y = c + d
x = p * q // The first assignment to x is dead

After optimization:

y = c + d
x = p * q
2. Copy Propagation:
o Variable propagation: Replacing occurrences of variables with their assigned
values
o Constant propagation: Replacing variables with their constant values

Example:

a = b
c = a + d // a can be replaced with b

After copy propagation:

a = b
c = b + d

3. Common Subexpression Elimination: Identifying and reusing already computed


expressions.

Example:

t1 = a + b
t2 = c * d
t3 = a + b // Same as t1

After CSE:

t1 = a + b
t2 = c * d
t3 = t1

4. Strength Reduction: Replacing expensive operations with equivalent but cheaper


operations.

Example:

x = y * 2 // Multiplication

After strength reduction:

x = y + y // or x = y << 1 (left shift)

5. Constant Folding: Computing constant expressions at compile time.

Example:

x = 5 + 3 * 2

After constant folding:


x = 11

6. Interchange of Independent Statements: Reordering statements that don't depend on


each other to enable further optimizations.

Example:

a = b + c
x = y + z
d = a + b

After reordering:

a = b + c
d = a + b
x = y + z

These structure preserving transformations can be implemented efficiently using directed acyclic
graphs (DAGs), which represent the dependencies between operations and operands within the
basic block. By applying these optimizations, compilers can significantly improve code
execution without altering the basic control flow.

2. a) Explain peephole optimization techniques with example.

Peephole Optimization Techniques:

Peephole optimization is a machine-dependent optimization technique that operates on the target


code. It examines a small, moving window of target instructions (called the peephole) and
replaces these instructions with shorter or faster sequences when possible. This technique is
applied as one of the final optimization passes to improve performance.

The main peephole optimization techniques are:

1. Redundant Load/Store Elimination: Eliminates unnecessary memory accesses by


removing redundant load and store instructions.

Example:

MOV a, R0 // Load a into register R0


MOV R0, b // Store R0 to b
MOV b, R0 // Load b back into R0 (redundant)

After optimization:

MOV a, R0
MOV R0, b
The third instruction is redundant since b's value is already in R0 from the second
instruction.

2. Remove Unreachable Code: Eliminates code that can never be executed due to program
flow.

Example:

void func() {
int a = 10, b = 20, c;
c = a * 10;
return c;
b = b * 15; // Unreachable
return b; // Unreachable
}

After optimization:

void func() {
int a = 10, c;
c = a * 10;
return c;
}

Note that variable b is also removed as it becomes dead code.

3. Flow of Control Optimization: Simplifies control flow by removing unnecessary jumps.

Example:

JMP L1
L1: JMP L2

After optimization:

JMP L2

This eliminates the intermediate jump by transferring control directly to the final
destination.

4. Algebraic Simplifications: Replaces sequences of instructions with mathematically


equivalent but more efficient sequences.

Examples:

o X = X * 1 → No operation (eliminated)
o X = X + 0 → No operation (eliminated)
o X = X * 2 → X = X << 1 (replace multiplication with shift)
o X = X * 0 → X = 0 (direct assignment)
5. Use of Machine Idioms: Replaces instruction sequences with specialized machine
instructions that perform the same operation more efficiently.

Example:

MOV a, R0
ADD #1, R0
MOV R0, a

If the target machine has an increment instruction, this can be replaced with:

INC a

This uses the machine's specialized instruction to perform the operation more efficiently.

These peephole optimization techniques, although local in scope, can significantly improve code
performance and size. They are particularly effective at cleaning up inefficiencies introduced by
earlier code generation phases.

2. b) Explain any three issues in the design of a code generator.

Three Issues in the Design of a Code Generator:

1. Input to Code Generator and Target Program:

The code generator takes as input the intermediate representation (IR) created by the
front end of the compiler, along with symbol table information. This IR can be in various
forms like postfix notation, three-address code, or syntax trees.

Key considerations:

o The IR should be detailed enough for code generation


o The front end should have performed type checking and error detection
o The code generator must decide the output format: absolute machine language,
relocatable machine language, or assembly language

Output formats have different advantages:

o Absolute machine language can be immediately executed


o Relocatable machine language allows separate compilation and linking
o Assembly language simplifies the code generation process

The target machine architecture (RISC, CISC, or stack-based) significantly impacts the
difficulty of code generation. RISC machines with many registers and simple instruction
sets generally allow for easier code generation than CISC machines with complex
addressing modes and fewer registers.
2. Instruction Selection:

The code generator must map the IR operations into target machine instructions
efficiently. This process is influenced by:

o The level of the IR


o The nature of the instruction set architecture
o The desired quality of the generated code

If the IR is high-level, each IR statement may translate into multiple machine instructions
using code templates. If the IR reflects low-level details of the target machine, more
efficient code can be generated.

The uniformity and completeness of the target instruction set are important factors. When
focusing on efficiency, the code generator must consider:

o Instruction speed
o Machine idioms
o Special-purpose instructions

Example: For the three-address statement a = a+1, a naive translation might be:

MOV a, R0
ADD #1, R0
MOV R0, a

But if the target machine has an increment instruction, this can be replaced with the more
efficient:

INC a

Making these selections optimally is challenging and requires knowledge of both the IR
semantics and the target machine capabilities.

3. Register Allocation:

Efficient use of registers is crucial for generating high-quality code. The code generator
must decide:

o Which values to keep in registers


o When to load values from memory into registers
o When to store values from registers back to memory

The challenge is complex because:

o The number of registers is limited


o Using registers efficiently reduces memory access, which is typically slower
o Some operations require specific registers (e.g., certain arithmetic operations)
o Keeping frequently used variables in registers improves performance

Register allocation strategies include:

o Local allocation (within basic blocks)


o Global allocation (across multiple basic blocks)
o Graph coloring algorithms for optimal allocation

The code generator typically uses register and address descriptors to keep track of what is
in each register and where each variable is stored (in registers, memory, or both).

Poor register allocation can significantly degrade code performance due to unnecessary
load and store operations.

These three issues are central to the design of an effective code generator. Addressing them well
leads to faster, more compact target code, while poor decisions can result in inefficient execution
despite good optimization in earlier phases.

3. a) Explain different code optimization techniques.


Different Code Optimization Techniques:

Code optimization techniques can be broadly classified into machine-independent and machine-
dependent optimizations, as well as local and global optimizations. Here are the key optimization
techniques:

1. Local Optimizations (Basic Block Optimizations):

These are performed within a basic block (a sequence of statements with no branches in or out
except at the beginning and end).

a) Structure Preserving Transformations:

 Common Subexpression Elimination (CSE): Identifies expressions computed multiple


times and computes them only once.
 a = b + c
 d = b + c + e // b + c is computed twice

After CSE:

temp = b + c
a = temp
d = temp + e

 Copy Propagation: Replaces variables with their values.


 x = y
 z = x + 1 // x can be replaced with y

After optimization:

x = y
z = y + 1

 Constant Folding: Evaluates constant expressions at compile time.


 x = 3 + 4 * 2

After constant folding:

x = 11

 Dead Code Elimination: Removes code that has no effect on the program output.
 x = 10
 x = 20 // First assignment is dead

After optimization:

x = 20

 Strength Reduction: Replaces expensive operations with cheaper ones.


 x = y * 2

After strength reduction:

x = y << 1 // Using shift instead of multiplication

b) Algebraic Transformations:

 Based on algebraic properties like associativity, commutativity, distributivity.


 Examples:
 x * 0 = 0x + 0 = xx * 1 = xx - x = 0

2. Global Optimizations (Loop Optimizations):

These optimizations are applied across basic blocks, particularly focusing on loops:

a) Code Motion (Loop Invariant Code Motion): Moves expressions that don't change within a
loop to outside the loop.

for(i=0; i<n; i++) {


x = y + z;
a[i] = x * i;
}

After code motion:


x = y + z;
for(i=0; i<n; i++) {
a[i] = x * i;
}

b) Induction Variable Elimination: Eliminates variables that change by a fixed amount in each
iteration.

for(i=0; i<n; i++) {


j = 4*i;
a[j] = a[j] + 2;
}

After optimization:

for(i=0; i<n; i++) {


a[4*i] = a[4*i] + 2;
}

c) Strength Reduction: Replaces expensive operations in loops with cheaper ones.

for(i=0; i<n; i++) {


x = i * 4;
a[x] = b[i];
}

After strength reduction:

x = 0;
for(i=0; i<n; i++) {
a[x] = b[i];
x = x + 4;
}

d) Loop Jamming (Loop Fusion): Combines similar loops to reduce loop overhead.

for(i=0; i<n; i++)


a[i] = b[i] + c[i];
for(i=0; i<n; i++)
d[i] = a[i] * 2;

After loop jamming:

for(i=0; i<n; i++) {


a[i] = b[i] + c[i];
d[i] = a[i] * 2;
}

e) Loop Unrolling: Reduces loop overhead by duplicating the loop body.

for(i=0; i<100; i++)


a[i] = b[i] + c[i];
After partial unrolling:

for(i=0; i<100; i+=4) {


a[i] = b[i] + c[i];
a[i+1] = b[i+1] + c[i+1];
a[i+2] = b[i+2] + c[i+2];
a[i+3] = b[i+3] + c[i+3];
}

3. Machine-Dependent Optimizations:

a) Peephole Optimization: Examines a small window of target instructions and replaces them
with more efficient sequences.

 Redundant load/store elimination


 Flow of control optimization
 Algebraic simplifications
 Use of machine idioms
 Example: Replacing MOV a, R0; ADD #1, R0; MOV R0, a with INC a

b) Register Allocation: Determines which values to keep in registers to minimize memory


access.

c) Instruction Scheduling: Reorders instructions to maximize pipeline efficiency and avoid


stalls.

d) Instruction Selection: Chooses the most efficient machine instructions for each operation.

These optimization techniques, when applied appropriately, can significantly improve program
performance by reducing execution time and memory usage. The effectiveness of each technique
depends on the program structure and the target machine architecture.

3. b) Generate a code sequence for the assignment x = (a-b)+(a-c)+(a-c).Solution:


Step 1: Convert to Three-Address Code

First, we'll convert the expression x = (a-b)+(a-c)+(a-c) into three-address code form:

t1 = a - b
t2 = a - c
t3 = a - c // Note: This is a common subexpression
t4 = t1 + t2
t5 = t4 + t3
x = t5

We can optimize this by eliminating the common subexpression (a-c):

t1 = a - b
t2 = a - c
t4 = t1 + t2
t5 = t4 + t2 // Reusing t2 instead of computing t3
x = t5
Step 2: Generate Target Code

Assuming a simple machine with load, store, and arithmetic instructions, we can generate the
target code. Let's use registers R0, R1, and R2 for our code generation.

Using the code generation algorithm from the notes:

MOV a, R0 ; Load 'a' into register R0


MOV b, R1 ; Load 'b' into register R1
SUB R1, R0 ; R0 = a - b (t1)
MOV R0, R2 ; Save t1 in R2
MOV a, R0 ; Load 'a' into register R0
MOV c, R1 ; Load 'c' into register R1
SUB R1, R0 ; R0 = a - c (t2)
ADD R2, R0 ; R0 = t1 + t2 (t4)
MOV a, R1 ; Load 'a' into register R1
MOV c, R2 ; Load 'c' into register R2
SUB R2, R1 ; R1 = a - c (t3)
ADD R1, R0 ; R0 = t4 + t3 (t5)
MOV R0, x ; Store the result in x
Step 3: Apply Optimization Techniques

After applying common subexpression elimination:

MOV a, R0 ; Load 'a' into register R0


MOV b, R1 ; Load 'b' into register R1
SUB R1, R0 ; R0 = a - b (t1)
MOV R0, R2 ; Save t1 in R2
MOV a, R0 ; Load 'a' into register R0
MOV c, R1 ; Load 'c' into register R1
SUB R1, R0 ; R0 = a - c (t2)
MOV R0, R1 ; Save t2 in R1 (for reuse)
ADD R2, R0 ; R0 = t1 + t2 (t4)
ADD R1, R0 ; R0 = t4 + t2 (t5)
MOV R0, x ; Store the result in x
Step 4: Further Optimize Using Register Allocation

We can further optimize the code by keeping values in registers as much as possible:

MOV a, R0 ; Load 'a' into register R0


MOV b, R1 ; Load 'b' into register R1
SUB R1, R0 ; R0 = a - b (t1)
MOV a, R1 ; Load 'a' into register R1
MOV c, R2 ; Load 'c' into register R2
SUB R2, R1 ; R1 = a - c (t2)
ADD R1, R0 ; R0 = (a-b) + (a-c) (t4)
ADD R1, R0 ; R0 = t4 + (a-c) (t5)
MOV R0, x ; Store the result in x
This optimized code uses fewer instructions by keeping intermediate results in registers and
eliminating redundant computations.

Notes on Optimizations Applied:

1. Common Subexpression Elimination: We identified that (a-c) appears twice and computed it
only once.
2. Register Allocation: We used registers efficiently to minimize loads and stores.
3. Code Motion: Although not explicitly shown, we arranged the code to minimize register
pressure.

This demonstrates how a compiler's code generator translates high-level statements into efficient
machine code through intermediate representation and optimization techniques.

Question 4.a: Explain the design issues of a code generator.

Solution:

The design of a code generator involves several important issues that affect the quality and
efficiency of the target code. Based on the provided module notes, the key design issues are:

1. Input to Code Generator

 The input consists of intermediate representation (IR) of the source program produced by the
front end, along with symbol table information.
 Choices for intermediate language include postfix notation, three-address representations
(quadruples), virtual machine representations (stack machine code), and graphical
representations (syntax trees and DAGs).
 The front end has already performed scanning, parsing, translation, and type checking before
code generation begins.
 Code generation proceeds assuming the input is error-free.

2. Target Programs

 The output can be absolute machine language, relocatable machine language, or assembly
language.
 Absolute machine language can be placed in fixed memory locations and immediately executed.
 Relocatable machine language allows separate compilation of subprograms, which can be linked
and loaded by a linking loader.
 Assembly language output makes code generation somewhat easier.
 The instruction set architecture significantly impacts code generation difficulty:
o RISC machines: Many registers, three-address instructions, simple addressing modes
o CISC machines: Few registers, two-address instructions, various addressing modes,
register classes, variable-length instructions
o Stack-based machines: Operations performed on stack elements
3. Memory Management

 Variable names are mapped to addresses cooperatively by the front end and code generator.
 Names and widths (storage sizes) are obtained from the symbol table.
 Each three-address code is translated to addresses and instructions during code generation.
 Relative addressing is used for instructions.

4. Instruction Selection

 The code generator must map IR to executable target machine code.


 Complexity depends on:
o IR level
o Instruction-set architecture
o Desired code quality
 High-level IR may use code templates for translation, but often produces inefficient code.
 Low-level IR that reflects machine details can generate more efficient code.
 Instruction set uniformity and completeness impact instruction selection difficulty.
 Instruction speed and machine idioms are important factors for efficiency.

5. Register Allocation

 Efficient register usage is crucial for performance.


 Register allocation determines which values remain in registers and which are stored in
memory.
 Variables accessed frequently should be kept in registers.
 Limited register count requires careful allocation strategies.
 Register allocation methods involve:
o Usage counting
o Graph coloring algorithms
o Register descriptor tables to track register contents
o Address descriptor tables to track variable locations

6. Choice of Evaluation Order

 The order of evaluation affects the efficiency of target code.


 Some orders require fewer registers and instructions than others.
 Finding the optimal order is an NP-complete problem.
 Code optimization can help by changing instruction order.

Question 4.b: Illustrate the optimization of basic blocks with examples.

Solution:
Basic block optimization involves applying various transformations to improve code efficiency
without changing program semantics. Below are key optimization techniques with examples:
1. Structure Preserving Transformations
A. Dead Code Elimination

Dead code refers to statements that compute values never used in subsequent computations or
that cannot be reached during execution.

Example: Original code:

t1 = a + b
t2 = c + d
x = t1 * 2

If t2 is never used later, the optimized code becomes:

t1 = a + b
x = t1 * 2
B. Copy Propagation

This involves replacing variables with their values when applicable.

Example: Original code:

t1 = a + b
t2 = t1
t3 = t2 * c

After copy propagation:

t1 = a + b
t3 = t1 * c
C. Common Subexpression Elimination

Identifies repeated expressions and computes them only once.

Example: Original code:

t1 = a + b
t2 = c * d
t3 = a + b
t4 = t3 * e

After elimination:

t1 = a + b
t2 = c * d
t4 = t1 * e
D. Strength Reduction

Replaces expensive operations with equivalent cheaper ones.

Example: Original code:

t1 = i * 8

After reduction (assuming 8 = 2³):

t1 = i << 3
E. Constant Folding

Evaluates constant expressions at compile time.

Example: Original code:

t1 = 3 * 4
t2 = t1 + a

After folding:

t2 = 12 + a
F. Interchange of Independent Statements

Reorders non-dependent statements for better resource utilization.

Example: Original code:

t1 = a + b
t2 = c + d
t3 = t1 * t2

Can be reordered as (might improve register usage):

t2 = c + d
t1 = a + b
t3 = t1 * t2

2. Using Directed Acyclic Graphs (DAGs)

DAGs help identify common subexpressions and optimize basic blocks.

Example: For the code:

t1 = a + b
t2 = a + b
t3 = t1 * c
t4 = t2 * d

A DAG would show that t1 and t2 compute the same expression, leading to:

t1 = a + b
t3 = t1 * c
t4 = t1 * d

3. Algebraic Transformations

These involve using algebraic properties to simplify expressions.

Examples:

 x + 0 → x (additive identity)
 x * 1 → x (multiplicative identity)
 x - x → 0 (self-subtraction)
 x + x → 2 * x (addition to multiplication)
 x * 2ⁿ → x << n (multiplication by power of 2)

Question 5.a: With suitable examples, explain the following loop optimization
techniques: (i) Code motion (ii) Induction variable elimination and (iii) Strength
reduction

Solution:

1. Code Motion
Code motion involves moving computations out of loops when their results don't change within
the loop, reducing unnecessary repeated calculations.

Example: Original loop:

for(i = 0; i < n; i++) {


x = y + z;
a[i] = x * i;
}

After code motion:

x = y + z; // Moved outside the loop as it doesn't depend on i


for(i = 0; i < n; i++) {
a[i] = x * i;
}
In this example, y + z doesn't change with each iteration, so computing it once before the loop
is more efficient. This reduces the number of operations from 2n to n+1 (n multiplications inside
the loop plus one addition before the loop).

2. Induction Variable Elimination


Induction variables are variables whose values change by a fixed amount in each iteration. When
multiple induction variables exist, some can be eliminated by expressing them in terms of others.

Example: Original loop:

for(i = 0; i < n; i++) {


j = i * 4;
a[j] = b[i] + 1;
}

After induction variable elimination:

j = 0;
for(i = 0; i < n; i++) {
a[j] = b[i] + 1;
j = j + 4; // Update j directly
}

Here, j was calculated as i * 4 in each iteration. By initializing j = 0 and incrementing by 4


each time, we eliminate the multiplication operation while maintaining the same semantics.

3. Strength Reduction
Strength reduction replaces expensive operations with equivalent but less expensive ones,
especially useful within loops.

Example: Original loop:

for(i = 0; i < n; i++) {


x = i * 8;
a[i] = x + y;
}

After strength reduction:

x = 0;
for(i = 0; i < n; i++) {
a[i] = x + y;
x = x + 8; // Replace multiplication with addition
}
Here, the multiplication i * 8 is replaced with repeated addition. We initialize x = 0 and add 8
in each iteration. This changes the multiplication (a costly operation) to addition (a less
expensive operation).

Another common example involves replacing powers with multiplications or multiplications


with shifts:

Original:

for(i = 0; i < n; i++) {


a[i] = i * 4;
}

After strength reduction:

for(i = 0; i < n; i++) {


a[i] = i << 2; // Shifting left by 2 is equivalent to multiplying by 4
}

In this case, multiplication by 4 is replaced with a left shift by 2 bits, which is typically faster on
most hardware.

Question 5.b: What is Peephole optimization? Explain any three transformations


done in peephole optimization.

Solution:

Peephole Optimization
Peephole optimization is a machine-dependent optimization technique that improves target code
by examining short sequences of instructions (called the "peephole") and replacing them with
more efficient sequences. It views a small window of instructions at a time and makes local
improvements.

The peephole acts as a small, moving window on the target program that scans a few instructions
at a time to find inefficiencies that can be improved.

Key Transformations in Peephole Optimization


1. Redundant Load/Store Elimination

This transformation eliminates unnecessary load and store instructions, particularly when values
are already in registers or when a value is stored and immediately reloaded.

Example: Original code:


MOV a, R0 ; Load a into R0
MOV R0, x ; Store R0 into x
MOV x, R0 ; Load x into R0 again

Optimized code:

MOV a, R0 ; Load a into R0


MOV R0, x ; Store R0 into x

The third instruction is eliminated since the value of x is already in R0.

2. Unreachable Code Elimination

This transformation removes code that can never be executed due to program control flow.

Example: Original code:

MOV a, R0
JMP L2 ; Jump to label L2
MOV b, R1 ; This instruction is unreachable
ADD R1, R0 ; This instruction is unreachable
L2: SUB c, R0

Optimized code:

MOV a, R0
JMP L2 ; Jump to label L2
L2: SUB c, R0

The unreachable instructions after the jump are eliminated.

3. Flow of Control Optimization

This transformation simplifies jump sequences, particularly eliminating jumps to jumps.

Example: Original code:

JMP L1 ; Jump to L1
...
L1: JMP L2 ; L1 jumps to L2

Optimized code:

JMP L2 ; Jump directly to L2


...
L1: JMP L2 ; L1 jumps to L2

The code now jumps directly to L2, avoiding the intermediate jump.
4. Algebraic Simplifications

This transformation replaces sequences that compute common algebraic identities with simpler
sequences.

Example: Original code:

MOV a, R0
SUB R0, R0 ; R0 = R0 - R0

Optimized code:

MOV 0, R0 ; Directly set R0 to 0

Since X - X = 0 for any X, the subtraction can be replaced with a direct assignment.

5. Use of Machine Idioms

This transformation replaces instruction sequences with specialized machine instructions that
perform the same task more efficiently.

Example: Original code:

MOV i, R0
ADD 1, R0 ; i = i + 1
MOV R0, i

Optimized code:

INC i ; Use increment instruction directly

Using the specialized increment instruction is more efficient than loading, adding, and storing.

Question 6.a: Explain any three issues in the design of a code generator.

Solution:

This question is similar to question 4.a. Here I'll focus on three specific issues in the design of a
code generator:

1. Instruction Selection
Instruction selection is a fundamental issue in code generator design, involving mapping
intermediate representation (IR) to target machine instructions.
Key aspects:

 The complexity of instruction selection depends on the IR level, the instruction set architecture,
and desired code quality.
 If the IR is high-level, each statement might require multiple machine instructions, often using
code templates.
 Low-level IR that reflects machine details can enable more efficient code generation.
 The nature of the instruction set significantly affects selection difficulty:
o Uniformity: Regular patterns in instructions make selection easier
o Completeness: Having instructions for all required operations simplifies mapping
o Instruction speed: Different instructions have different execution costs
o Machine idioms: Special instructions for common operations can improve performance

Example: For the statement a = a + 1, different machine architectures offer different optimal
implementations:

 Generic approach: LOAD a, R0; ADD 1, R0; STORE R0, a


 Using increment instruction: INC a (more efficient if available)

2. Register Allocation
Register allocation determines which values stay in registers (fast access) and which are stored in
memory (slower access).

Key aspects:

 Efficient register usage significantly impacts performance.


 Modern CPUs have limited registers, making allocation crucial.
 Variables used frequently should preferentially stay in registers.
 The allocation must handle cases where there are more variables than registers.
 Register allocation methods include:
o Usage counts to prioritize frequently accessed values
o Graph coloring algorithms for optimal allocation
o Register descriptors tracking what's in each register
o Address descriptors tracking where each variable resides

Example of Register Descriptor:

R0: {a, t1} // Register R0 contains variables a and t1


R1: {b} // Register R1 contains variable b
R2: {empty} // Register R2 is available

Example of Address Descriptor:

a: {R0, memory} // Variable a is in R0 and memory


b: {R1} // Variable b is in R1 only
c: {memory} // Variable c is in memory only
3. Evaluation Order

The order in which expressions are evaluated affects register usage and code efficiency.

Key aspects:

 Different evaluation orders may require different numbers of registers.


 Finding the optimal evaluation order is an NP-complete problem.
 The code generator must choose an order that minimizes register usage.
 Good heuristics include evaluating the most complex sub-expression first.

Example: For the expression a + b * (c + d), two possible evaluation orders are:

1. Compute c + d first, then b * result, then a + result


2. Compute b * c first, then b * d, then a + result

The first approach uses fewer registers and is generally more efficient.

Question 6.b: Translate the expression W:=(A-B)+(A-C)+(A-C) into three


address code and generate the machine code for the three address code.

Solution:

Step 1: Convert to Three-Address Code

For the expression W:=(A-B)+(A-C)+(A-C), we'll first convert it to three-address code:

t1 = A - B
t2 = A - C
t3 = A - C // This is a common subexpression
t4 = t1 + t2
t5 = t4 + t3
W = t5

After applying common subexpression elimination:

t1 = A - B
t2 = A - C
t4 = t1 + t2
t5 = t4 + t2 // Reused t2 instead of computing t3
W = t5

Step 2: Generate Machine Code

Using a typical load-store architecture with registers R0, R1, R2:


MOV A, R0 ; Load A into R0
MOV B, R1 ; Load B into R1
SUB R1, R0 ; R0 = A - B (t1)
MOV R0, R2 ; Save t1 in R2
MOV A, R0 ; Load A into R0
MOV C, R1 ; Load C into R1
SUB R1, R0 ; R0 = A - C (t2)
MOV R0, R1 ; Save t2 in R1
ADD R2, R0 ; R0 = t1 + t2 (t4)
ADD R1, R0 ; R0 = t4 + t2 (t5)
MOV R0, W ; Store result in W

Step 3: Optimized Machine Code

We can optimize this further by keeping values in registers longer:

MOV A, R0 ; Load A into R0


MOV B, R1 ; Load B into R1
SUB R1, R0 ; R0 = A - B (t1)
MOV R0, R2 ; Save t1 in R2
MOV A, R0 ; Load A again
MOV C, R1 ; Load C
SUB R1, R0 ; R0 = A - C (t2)
ADD R2, R0 ; R0 = (A-B) + (A-C)
ADD R0, R0 ; R0 = (A-B) + 2*(A-C)
MOV R0, W ; Store result in W

Note: The final optimization assumes recognition that (A-C)+(A-C) = 2*(A-C), which might be
beyond most basic compilers but demonstrates advanced optimization potential.

Question 7.a: Explain any three code optimization transformation.

Solution:

Three Key Code Optimization Transformations


1. Common Subexpression Elimination (CSE)

Common subexpression elimination identifies repeated expressions and computes them only
once, storing the result for reuse.

Process:

1. Identify expressions that are computed more than once with the same values
2. Store the result of the first computation in a temporary variable
3. Replace subsequent computations with references to that variable

Example: Original code:


t1 = a + b
t2 = c * d
t3 = a + b // Common subexpression
t4 = t3 * e

After CSE:

t1 = a + b
t2 = c * d
t4 = t1 * e // Using t1 instead of recomputing a+b

Benefits:

 Reduces the number of computations


 Reduces code size
 Improves execution speed
 Particularly effective in loops

2. Dead Code Elimination

Dead code elimination removes statements that compute values never used in subsequent
computations or that cannot be reached during execution.

Types of dead code:

1. Unreachable code: Statements that can never be executed


2. Assignments to variables never used later
3. Computations whose results are discarded

Example: Original code:

if (1 == 2) { // This condition is always false


x = y + z; // Unreachable code
}
a = b + c;
d = a * 2;
e = b - c; // Result never used (dead assignment)
return d;

After dead code elimination:

a = b + c;
d = a * 2;
return d;

Benefits:

 Reduces code size


 Eliminates unnecessary computations
 Improves cache utilization
 Makes the code more maintainable

3. Loop Optimization

Loop optimization techniques improve the efficiency of loops, which are often the most time-
consuming parts of programs.

A. Code Motion

Code motion moves invariant computations outside loops to avoid repeated calculations.

Example: Original loop:

for (i = 0; i < n; i++) {


x = y * z; // Invariant computation
a[i] = x + i;
}

After code motion:

x = y * z; // Moved outside the loop


for (i = 0; i < n; i++) {
a[i] = x + i;
}
B. Strength Reduction

Strength reduction replaces expensive operations with cheaper ones, particularly in loops.

Example: Original loop:

for (i = 0; i < n; i++) {


a[i] = i * 4; // Multiplication in each iteration
}

After strength reduction:

for (i = 0; i < n; i++) {


a[i] = i << 2; // Using shift instead of multiplication
}
C. Loop Unrolling

Loop unrolling reduces loop overhead by duplicating the loop body and adjusting the iteration
count.

Example: Original loop:

for (i = 0; i < 100; i++) {


a[i] = b[i] + c;
}
After loop unrolling:

for (i = 0; i < 100; i += 4) {


a[i] = b[i] + c;
a[i+1] = b[i+1] + c;
a[i+2] = b[i+2] + c;
a[i+3] = b[i+3] + c;
}

Benefits of Loop Optimization:

 Reduces computation within loops


 Decreases loop overhead
 Improves instruction pipelining
 Enhances cache utilization

Question 7.b: Perform common sub-expression elimination for the following


three-address code and represent it as a quadruple table:
t1 = a+b
x = t1
t2 = a+b
t3 = a*c
b = t2
t4 = a*b
y = t4

Solution:

Step 1: Identify Common Subexpressions

In the given three-address code, we can identify the following common subexpressions:

1. a+b appears twice (in t1 and t2)


2. After the assignment b = t2, the value of b changes, so subsequent uses of b refer to the new
value

Step 2: Apply Common Subexpression Elimination

Original code:

t1 = a+b
x = t1
t2 = a+b // Common subexpression - can reuse t1
t3 = a*c
b = t2
t4 = a*b
y = t4

After CSE:

t1 = a+b
x = t1
t2 = t1 // Reusing t1 instead of recomputing a+b
t3 = a*c
b = t2 // b now has the value of a+b (or t1)
t4 = a*b // Since b changed, this is now a*(a+b)
y = t4

Step 3: Represent as Quadruple Table

A quadruple consists of four fields: (op, arg1, arg2, result)

Index Operator Operand 1 Operand 2 Result

0 + a b t1

1 = t1 - x

2 = t1 - t2

3 * a c t3

4 = t2 - b

5 * a b t4

6 = t4 - y

Note that in the original code, statement t2 = a+b was redundant as it recomputed a+b which
was already computed in t1. After common subexpression elimination, we directly assign t1 to
t2 without recomputation.

Question 8.a: Write code generation algorithm.

Solution:

Code Generation Algorithm


The code generation algorithm translates intermediate representation (three-address code) into
target machine code. It uses registers to store operands and results of operations, determining
when to load values from memory to registers and when to store results back to memory.
procedure CodeGen(three-address-code)
for each three-address statement s in the sequence do
case type of s of
x := y op z: // Binary operation
getReg(x, y, z, op);

x := op y: // Unary operation
getReg(x, y, null, op);

x := y: // Assignment
getReg(x, y, null, '=');

if x goto L: // Conditional jump


generateConditionalJump(x, L);

goto L: // Unconditional jump


generateJump(L);

label L: // Label
generateLabel(L);
end case
end for
end procedure

The key component of this algorithm is the getReg function, which handles register allocation:

function getReg(x, y, z, op)


// First, ensure y is in a register
if y is in a register R1 then
// Use that register
else if there is an available register R1 then
generateCode("MOV", y, R1); // Load y into R1
else
// Need to spill a register
select a register R1 for replacement;
if the value in R1 has been changed and needs to be saved then
generateCode("MOV", R1, location of value originally in R1);
end if
generateCode("MOV", y, R1); // Load y into R1
end if

// For binary operations, ensure z is in a register


if op is binary then
if z is in a register R2 then
// Use that register
else if there is an available register R2 then
generateCode("MOV", z, R2); // Load z into R2
else if R1 can be used for both y and op z then
generateCode("MOV", z, R1); // Load z into R1 (overwriting y)
R2 := R1;
else
// Need to spill a register
select a register R2 for replacement;
if the value in R2 has been changed and needs to be saved then
generateCode("MOV", R2, location of value originally in R2);
end if
generateCode("MOV", z, R2); // Load z into R2
end if
end if

// Generate the operation code


if op is binary then
generateCode(op, R2, R1); // R1 = R1 op R2
else if op is unary then
generateCode(op, R1); // R1 = op R1
end if

// Update descriptors to indicate x is now in R1


update register descriptor for R1 to indicate it contains x;
update address descriptor for x to indicate it is in R1;

return R1; // Return the register containing the result


end function

The algorithm uses two important data structures:

1. Register Descriptor: For each register, it keeps track of the variables currently held in it.
2. Address Descriptor: For each variable, it tracks all locations (registers and/or memory) where its
current value can be found.

These descriptors help optimize register usage and minimize load/store operations.

8.b) With suitable examples of a basic block, explain the code-improving


transformations of a basic block.
Code-improving transformations on a basic block are techniques applied to optimize code
efficiency without altering program behavior. There are two main categories of basic block
optimizations:

1. Structure Preserving Transformations


Dead Code Elimination

Dead code refers to statements that compute values not used later in the program or cannot be
reached during execution.

Example:

if (false)
x = y + 5; // This is dead code
a = b + 5; // This is unreachable code if placed after return statement

In this example, the "if" statement with a constant false condition is dead code, while the
assignment after a return statement would be unreachable code. Both can be safely eliminated.
Copy Propagation

Copy propagation involves replacing variables with their defined values throughout the code
where applicable.

Example:

a = b
c = a + d

After copy propagation:

a = b
c = b + d
Common Sub-expression Elimination

When the same expression appears multiple times, it can be computed once and reused.

Example:

t1 = a + b
t2 = a + b
t3 = t2 * c

After optimization:

t1 = a + b
t3 = t1 * c
Strength Reduction

This involves replacing expensive operations with equivalent but less costly ones.

Example:

x = y * 2

After strength reduction:

x = y + y // or x = y << 1
Constant Folding

Evaluating constant expressions at compile time rather than runtime.

Example:

x = 5 * 10

After constant folding:


x = 50
Interchange of Independent Statements

Reordering statements that don't depend on each other to optimize register usage or instruction
scheduling.

2. Algebraic Transformations

These involve applying algebraic identities to simplify expressions:

 Commutative laws: x + y = y + x
 Associative laws: (x + y) + z = x + (y + z)
 Distributive laws: x * (y + z) = x * y + x * z
 Identity laws: x + 0 = x, x * 1 = x
 Constant folding: 2 + 3 = 5

Example:

x = y * 0

Can be simplified to:

x = 0

9.a) For the following C statement, write the three-address code and quadruples:
S = A-B+C/D-E+F. Also convert the three-address code into machine code.

Three-address code:
t1 = A - B
t2 = C / D
t3 = t1 + t2
t4 = t3 - E
t5 = t4 + F
S = t5

Quadruples:

Op Arg1 Arg2 Result

- A B t1

/ C D t2

+ t1 t2 t3
Op Arg1 Arg2 Result

- t3 E t4

+ t4 F t5

= t5 - S

Machine Code Generation:

Assuming a simple machine with MOV, ADD, SUB, DIV, and STORE instructions:

MOV A, R0 ; Load A into register R0


SUB B, R0 ; Subtract B from R0, result in R0 (t1)
MOV C, R1 ; Load C into register R1
DIV D, R1 ; Divide R1 by D, result in R1 (t2)
ADD R1, R0 ; Add R1 to R0, result in R0 (t3)
SUB E, R0 ; Subtract E from R0, result in R0 (t4)
ADD F, R0 ; Add F to R0, result in R0 (t5)
STORE R0, S ; Store R0 into S

9.b) Write the Code Generation Algorithm and explain the getreg function.

Code Generation Algorithm:

The algorithm takes a sequence of three-address statements as input and produces target machine
code. For each three-address statement of the form x := y op z:

1. Invoke getReg(x, y, z) to determine register allocation


2. Generate instruction to load y into register Ry if not already there
3. Generate instruction to perform operation on registers Ry and Rz
4. Update the register descriptor and address descriptor to indicate where values are stored
5. If x is not used later, store the result from register into memory location of x

The getReg Function:

The getReg function determines which register to use for computation. It works as follows:

1. If variable Y is already in register R, it uses that register.


2. Else if some register R is available (empty), it uses that register.
3. Else if both the above options are not possible, it chooses a register that requires minimal
number of load and store instructions. That is, it takes a register which is already occupied,
moves its contents into memory using the instruction MOV R, M, and then uses the register R.

This function optimizes register usage by minimizing unnecessary load/store operations, which
are typically more expensive than register-to-register operations.
10.a) Write code generation algorithm. Using this algorithm generate code for
the expression x=(a-b)+(a+c)+(a+c)

Code Generation Algorithm:

For each three-address statement of the form x := y op z:

1. Call getReg to find registers for operands and result


2. Load values into registers if needed
3. Perform the operation
4. Update descriptors
5. Store results if necessary

Three-address code for x=(a-b)+(a+c)+(a+c):


t1 = a - b
t2 = a + c
t3 = t1 + t2
t4 = a + c // Common subexpression
t5 = t3 + t4
x = t5

After common subexpression elimination:

t1 = a - b
t2 = a + c
t3 = t1 + t2
t5 = t3 + t2 // Reusing t2 instead of computing t4
x = t5

Machine Code Generation:


// t1 = a - b
MOV a, R0 // Load a into R0
SUB b, R0 // R0 = a - b (t1)

// t2 = a + c
MOV a, R1 // Load a into R1
ADD c, R1 // R1 = a + c (t2)

// t3 = t1 + t2
ADD R1, R0 // R0 = R0 + R1 = t1 + t2 (t3)

// t5 = t3 + t2 (reusing t2 in R1)
ADD R1, R0 // R0 = R0 + R1 = t3 + t2 (t5)

// x = t5
STORE R0, x // Store R0 to x
11.a) With suitable examples explain the following loop optimization techniques:
(i) Code motion (ii) Induction variable elimination and (iii) Strength reduction

(i) Code Motion


Code motion involves moving invariant computations outside of loops to reduce redundant
calculations.

Example:

for (i = 0; i < n; i++) {


x = y + z; // This computation doesn't depend on i
a[i] = x * i;
}

After code motion:

x = y + z; // Moved outside the loop


for (i = 0; i < n; i++) {
a[i] = x * i;
}

This optimization reduces the number of operations from 2n to n+1 (one addition and n
multiplications).

(ii) Induction Variable Elimination

Induction variables are variables that change by a fixed amount in each iteration. When multiple
induction variables exist with linear relationships, we can eliminate redundant ones.

Example:

for (i = 0; i < n; i++) {


j = i * 4; // j is an induction variable derived from i
a[j] = b[i];
}

After induction variable elimination:

for (i = 0; i < n; i++) {


a[i*4] = b[i]; // Eliminated j
}

(iii) Strength Reduction

This involves replacing expensive operations with cheaper ones, especially for induction
variables.
Example:

for (i = 0; i < n; i++) {


x = i * 4; // Multiplication in each iteration
a[x] = b[i];
}

After strength reduction:

x = 0; // Initialize x to 0
for (i = 0; i < n; i++) {
a[x] = b[i];
x = x + 4; // Addition instead of multiplication
}

This replaces the multiplication operation in each iteration with an addition, which is typically
faster.

11.b) Explain the optimization of basic blocks.


Optimization of basic blocks involves applying various transformations to improve code
efficiency without changing program behavior. The primary goal is to reduce computation time
and memory usage. Key optimization techniques include:

1. Structure Preserving Transformations:


o Dead Code Elimination: Removing statements that have no effect on program output
o Copy Propagation: Substituting variables with their values where appropriate
o Common Subexpression Elimination: Computing repeated expressions only once
o Constant Folding: Evaluating constant expressions at compile time
o Strength Reduction: Replacing expensive operations with cheaper ones
2. Algebraic Transformations:
o Applying algebraic identities to simplify expressions
o Eliminating redundant operations
3. DAG (Directed Acyclic Graph) Representation:
o Using DAGs to identify common subexpressions
o Determining the optimal evaluation order of expressions
4. Register Allocation:
o Optimizing register usage to minimize memory access
o Using algorithms to determine which values should be kept in registers

The optimization process typically involves creating a DAG representation of the basic block,
identifying optimization opportunities, and then generating improved code based on the
optimized DAG.

12.a) Convert to three-address code and write machine code for given statement:
x = a/b + a/b*(c-d)
Three-address code:
t1 = a / b
t2 = c - d
t3 = a / b // Common subexpression
t4 = t3 * t2
t5 = t1 + t4
x = t5

After common subexpression elimination:

t1 = a / b
t2 = c - d
t4 = t1 * t2
t5 = t1 + t4
x = t5

Machine Code:
// t1 = a / b
MOV a, R0 // Load a into R0
DIV b, R0 // R0 = a / b (t1)

// t2 = c - d
MOV c, R1 // Load c into R1
SUB d, R1 // R1 = c - d (t2)

// t4 = t1 * t2
MOV R0, R2 // Copy R0 to R2 to preserve t1
MUL R1, R2 // R2 = R2 * R1 = t1 * t2 (t4)

// t5 = t1 + t4
ADD R2, R0 // R0 = R0 + R2 = t1 + t4 (t5)

// x = t5
STORE R0, x // Store R0 to x

You might also like