CD 4 new
CD 4 new
Explain various storage allocation strategies with its merits and demerits. [7M]
Limited memory size (stack size is fixed).
Answer: Data is lost when function ends.
Cannot allocate variable size data at runtime.
Storage allocation refers to how memory is assigned to programs and data during execution.
There are three main storage allocation strategies: 3. Heap Allocation (Dynamic Allocation)
2. Stack Allocation b) Define activation records. Explain how it is related with runtime storage allocation. (7M)
Definition: Answer:
Memory is allocated and deallocated in a Last In First Out (LIFO) manner during function calls.
Activation Record:
Example:
Local variables inside a function: An activation record is a block of memory that stores information about a single execution of a
procedure (function or method). It is created when a function is called and removed when the
void func() { function exits.
int x = 5;
}
It is also called a stack frame, and it is stored in the runtime stack.
Merits:
Contents of an Activation Record:
Fast allocation and deallocation.
Suitable for recursive function calls. 1. Return Address – Where to return after function execution.
No fragmentation. 2. Parameters – Values passed to the function.
3. Local Variables – Variables declared inside the function.
4. Temporary Values – Intermediate values used during evaluation. 2. Storage Allocation Strategies for Recursive Calls:
5. Saved Registers – If any registers are used, their values are saved.
Recursive procedures call themselves. So, each recursive call needs a separate memory to
Relation with Runtime Storage Allocation: store its data (like local variables and return address).
The main strategy used is:
During program execution, memory is divided into:
o Code Area – For compiled code. a) Stack-based Allocation:
o Static Area – For global/static variables.
o Heap – For dynamically allocated memory. Each time a recursive procedure is called, a new activation record is pushed onto the
o Stack – For activation records. runtime stack.
Each function call creates a new activation record on the runtime stack. This ensures that each call has its own separate copy of local variables and return
When the function returns, its activation record is popped off the stack. address.
When the call finishes, its activation record is removed (popped) from the stack.
Example:
✅Advantages:
void add(int x, int y) {
int sum = x + y;
printf("%d", sum); Supports dynamic allocation during runtime.
} Allows recursive and nested procedure calls efficiently.
1. Runtime Stack: Each call to factorial(n) has its own activation record, so recursive calls don’t overwrite
each other’s data.
A runtime stack (also called call stack) is a memory area used to manage
function/procedure calls during program execution. Q. What is a flow graph? Explain how a flow graph can be constructed for a given
It stores activation records (stack frames) of functions, including local variables, program.
return address, parameters, and saved registers. Also draw the flow graph for the below program:
When a function is called, its activation record is pushed onto the stack. When the
Main()
function returns, the record is popped.
{
It supports Last-In First-Out (LIFO) behavior, making it suitable for nested and int sum, n, i;
recursive calls. sum = 0;
for i := 1 to n do
sum := sum + i;
write(sum);
} +---------+ +---------+
| Block 3 | | Block 4 | --> write(sum)
+---------+ +---------+
Answer: |
v
Flow Graph: (Back to Block 2)
A flow graph is a diagram that represents the control flow of a program using nodes and edges. Explanation:
Understanding program flow Q) What are the principles associated with designing calling sequences and the
Testing (like path testing or loop testing) layout of activation records? [7M]
Debugging
Answer:
Steps to Construct Flow Graph:
Calling sequence and activation record layout are important concepts in the implementation of
1. Divide the program into basic blocks (each block has no jumps except at the end). procedures (functions). Their design affects the efficiency and correctness of function calls and
2. Number each block. returns.
3. Draw arrows showing the flow from one block to another.
Principles for Designing Calling Sequences and Activation Records:
Program Basic Blocks:
1. Simplicity:
Let’s break the program into blocks: o The design should be simple and easy to implement.
o Should support both recursive and non-recursive procedures.
Block 1: sum = 0; 2. Efficiency:
Block 2: i := 1 to n (loop condition) o Minimize the overhead of procedure calls.
Block 3: sum = sum + i; o Optimize the usage of registers and memory.
Block 4: write(sum); 3. Modularity:
o Each function should have its own isolated environment.
Flow Graph (ASCII Diagram): o Avoid interference between functions.
4. Support for Recursion:
o Activation records should be created dynamically during recursive calls.
5. Clear Division of Responsibilities:
+---------+ o Caller and callee should have clearly defined roles in saving and restoring values.
| Block 1 | --> sum = 0 6. Preservation of Registers:
+---------+
o Decide which registers are saved by the caller and which by the callee (caller-
|
v saves vs callee-saves).
+---------+ 7. Proper Access to Variables:
| Block 2 | --> for i := 1 to n o Ensure access to local, global, and non-local variables.
+---------+
/ \
Yes No Activation Record Layout (Example):
/ \
v v An Activation Record typically contains the following fields:
|------------------------| 1. Compile-Time Evaluation:
| Return Address | ← address to return after function ends o Perform operations during compilation.
|------------------------|
| Actual Parameters | ← arguments passed to function o Example:
|------------------------| o int x = 3 * 4; // Compiler changes it to int x = 12;
| Control Link (Dynamic) | ← pointer to caller's activation record 2. Constant Folding:
|------------------------| o Combine constant values at compile time.
| Local Variables | ← variables declared inside function o Example:
|------------------------| o int y = (2 + 5) * 3; // Optimized to int y = 21;
| Temporaries | ← intermediate values during computation
|------------------------|
3. Dead Code Elimination:
o Removes code that never affects the output.
o Example:
o if (false) {
Example (Calling Sequence): o x = 10;
o }
Suppose main() calls sum(a, b): o // The above block is removed.
4. Common Subexpression Elimination:
1. Caller (main) actions: o Avoids repeating the same expression.
o Example:
o Push arguments a, b.
o a = (b + c) + d;
o Save return address. o e = (b + c) * f;
o Transfer control to sum. o // (b + c) computed once and reused
2. Callee (sum) actions: 5. Loop Optimization:
o Save control link (dynamic link). o Improves the performance of loops.
o Allocate space for local variables. o Example:
3. On Return: Move invariant code outside the loop.
o sum removes its activation record. o for (int i = 0; i < 100; i++) {
o x = y * z; // If y and z are constant, move outside loop
o Control returns to main.
o }
6. Strength Reduction:
Question: o Replaces costly operations with cheaper ones.
What is the role of Code Optimizer in a compiler? Is it a mandatory phase? Explain the o Example:
various sources of optimization with example. [7M] o x = y * 2; // Optimized to x = y + y;\
Answer:
a) Explain how data flow equations are set up and solved for improving code.
Role of Code Optimizer in Compiler: [7M]
The Code Optimizer is a phase in the compiler that improves the intermediate code to Answer:
make the final program run faster and consume less memory.
It removes unnecessary code, reduces redundant operations, and improves execution Data flow analysis is used in compilers to collect information about how data values are used
speed. and modified in a program. This helps in code optimization like dead code elimination, constant
Optimization is done without changing the output of the program. propagation, etc.
Is Code Optimization a Mandatory Phase? Steps to set up and solve data flow equations:
Example: Example:
1: a = 5; 1: a = 1;
2: b = 10; 2: b = 2;
3: c = a + b; 3: if (a < b) goto L1;
4: a = 7; 4: c = a + b;
5: d = a + b; 5: goto L2;
L1:
Break into basic blocks: 6: c = a - b;
L2:
7: print(c);
B1: 1, 2
B2: 3 Basic Blocks:
B3: 4, 5
B1: 1, 2, 3
Now: B2: 4, 5
B3: 6
GEN[B1] = {a=5, b=10} B4: 7
KILL[B3] = {a=5} (because a is redefined)
GEN[B3] = {a=7} Flow Graph:
Apply equations to compute IN and OUT for optimization like constant propagation. B1 → B2 (if condition false)
B1 → B3 (if condition true)
b) Discuss basic blocks and flow graphs with an example. [7M] B2 → B4
B3 → B4
Answer:
The flow graph helps in optimization like dead code removal or loop analysis.
A Basic Block is a sequence of instructions with:
a) Give the general structure of an activation record? Explain the purpose of
No branching in except at the beginning. each component involved in it. [7M]
No branching out except at the end.
Answer:
Basic blocks help in identifying areas for optimization.
An activation record is a block of memory used by the compiler to store information about a
Flow Graph (Control Flow Graph): function (procedure) call.
A Flow Graph shows flow of control between basic blocks using nodes and edges. General Structure of Activation Record:
+---------------------+
Nodes represent basic blocks. | Return Value |
Edges show possible flow of control from one block to another. +---------------------+
| Actual Parameters |
+---------------------+
| Control Link |
+---------------------+ Techniques:
| Access Link |
+---------------------+
| Saved Machine Status| 1. Constant Folding:
+---------------------+ Compute constant expressions at compile time.
| Local Variables | Example:
+---------------------+
int x = 3 * 4; → replaced with int x = 12;
| Temporaries |
+---------------------+ 2. Constant Propagation:
Replace variables with known constant values.
Purpose of Each Component: Example:
int a = 5; int b = a + 2; → int b = 7;
1. Return Value: 3. Dead Code Elimination:
Stores the result to be returned to the caller. Remove code that never affects output.
2. Actual Parameters: Example:
Stores the arguments passed to the function. if(false) { x = 10; } → this block is removed.
3. Control Link: 4. Common Subexpression Elimination:
Points to the activation record of the caller function (helps in returning). Avoid re-evaluation of same expressions.
4. Access Link: Example:
Points to non-local variables in the static nesting (used in nested functions). a = b + c; d = b + c; → compute b + c once and reuse it.
5. Saved Machine Status: 5. Strength Reduction:
Stores registers and program counter before the function call. Replace costly operations with cheaper ones.
6. Local Variables: Example:
Stores variables declared inside the function. x = x * 2; → x = x + x;
7. Temporaries: 6. Copy Propagation:
Used to hold intermediate results during expression evaluation. Replace duplicate variable usage.
Example:
Example: a = b; c = a + 1; → c = b + 1;
int add(int a, int b) { 7. Loop Invariant Code Motion:
int sum = a + b; Move code out of loop if it doesn’t change.
return sum;
} Example:
for(int i=0; i<n; i++) { x = 5; y[i] = x + i; } → move x = 5 outside loop.
For the above function, activation record will contain:
Question:
a, b → actual parameters
sum → local variable a) Write a short note on peephole optimization and various operations used in it. [7M]
return value → returned sum
control & access links → for function call management Answer:
Example: =
In a C program, global variables and constants are examples of static storage allocation. / \
For instance, if we declare a global variable like: a[i] -
/ \
int x = 10; // Static allocation
* *
/ \ / \
The variable x will be allocated a fixed memory location before the program starts and b c b d
will retain its value throughout the program's lifetime.
Explanation:
Stack Storage Allocation:
In stack storage allocation, memory is dynamically allocated at runtime for local variables within The root of the tree is the assignment operator =.
functions. When a function is called, its local variables are pushed onto the stack, and when the Left side of = is a[i].
function ends, the memory is automatically freed. This memory allocation is temporary and Right side of = is the subtraction (-) operator, which takes the results of two
managed using a stack structure. multiplications (*).
Example: Quadruples:
In a C program, local variables within a function are allocated using the stack. For
example: In quadruples, we generate a list of four components: (operator, operand1, operand2, result).
void function() { Here's how we translate the expression:
int a = 5; // Stack allocation
int b = 10; // Stack allocation
1. ( *, b, c, t1 )
}
(b * c) is stored in temporary variable t1.
2. ( *, b, d, t2 )
Here, the variables a and b are stored in the stack memory, and their memory is released
(b * d) is stored in temporary variable t2.
when the function finishes execution.
3. ( -, t1, t2, t3 )
Subtract t1 from t2 and store the result in t3.
Key Differences:
4. ( =, t3, , a[i] )
The result t3 is assigned to a[i].
Feature Static Allocation Stack Allocation
Memory Allocation At compile time At runtime (during function call) Triples:
Lifetime of Variables Throughout the program Limited to the function's scope
Memory Release Manually, when program ends Automatically when function returns In triples, we only store the operator and its operands, and use indices for results. Here's the
Examples Global variables, constants Local variables in functions translation:
1. ( *, b, c ) → index 1
Question: 2. ( *, b, d ) → index 2
3. ( -, 1, 2 ) → index 3
Translate the arithmetic expression a[i] = b * c - b * d into a syntax tree, quadruples, and
4. ( =, 3, a[i] )
triples.
Explanation:
In triples, the result of each operation is identified by an index, and the operands are 1. Node 1: Start of the program. Initializes sum to 0 and reads n.
referenced by their corresponding indices. 2. Node 2: The loop condition FOR i = 1 TO n is checked.
o If true, it moves to Node 3.
Summary: o If false, it moves to Node 4 to print the sum and terminate the program.
3. Node 3: The loop body where a number is read, and the sum is updated.
Syntax Tree represents the hierarchical structure of the expression. 4. Node 2: After the loop body, control returns to check the loop condition again.
Quadruples use four fields (operator, operand1, operand2, result). 5. Node 4: Print the final sum and exit.
Triples use only three fields (operator, operand1, operand2), referencing previous results
by indices. Rules for Flow Graph Construction:
Question: 1. Basic Block Representation: Each block represents a sequence of statements without
any branches, i.e., a block that executes sequentially without any condition or decision.
Write the pseudocode for finding the sum of ‘n’ numbers. Identify the basic blocks and construct 2. Control Flow Edges: An edge is drawn from one basic block to another to indicate the
the flow graph for it. Explain the rules used for this. (7M) flow of control. This could be based on decisions (such as loops or conditions) or
sequential execution.
3. Loop Handling: For loops, we create two nodes: one for the loop condition and one for
the loop body. After executing the loop body, the control returns to the condition check.
Answer: 4. End Conditions: Once the loop condition fails, the flow graph moves to the block where
the result is printed, marking the end of the program.
Pseudocode to Find the Sum of ‘n’ Numbers:
Flow Graph for the Pseudocode:
START
Set sum = 0 +------+
Read n | Start|
FOR i = 1 TO n +------+
Read number |
sum = sum + number v
END FOR +------------+
Print sum | Initialize |
END | sum = 0, |
| Read n |
Basic Blocks: +------------+
|
v
1. Block 1: Initialization – The sum is initialized to 0 and the value of n is read. +------------+
o Statement: sum = 0 and Read n | i = 1 to n |
2. Block 2: Loop Setup – The loop is set up to iterate from 1 to n. | (Condition)|
+------------+
o Statement: FOR i = 1 TO n |
3. Block 3: Input and Sum Calculation – Inside the loop, the number is read, and the sum Yes / \ No
is updated. v v
o Statement: Read number and sum = sum + number +------------+ +------------+
| Read number| | Print sum |
4. Block 4: Output – The sum is printed after the loop ends. | sum = sum +|---->| END |
o Statement: Print sum | number | +------------+
+------------+
Flow Graph Construction: |
v
+------------+
The flow graph is a representation of the flow of control in the program. Each basic block is | i = i + 1 |
represented as a node, and edges represent the flow between them. +------------+
| Here, we removed the redundant MOV R2, #5 and used ADD R3, R1, R1 instead, achieving the
v
same result with fewer instructions.
+------------+
| i = 1 to n |
| (Condition)| ii) Elimination of Unreachable Code:
+------------+
Unreachable code refers to parts of the program that cannot be executed due to the flow of
Explanation of Flow Graph Rules: control. These code segments are typically placed after a return, break, or goto statement that
ensures the program will never reach them. Removing such code helps in reducing the program
1. Start Node: Represents the beginning of the program. size and improving performance.
2. Condition Nodes: Used for loops or conditional checks. For example, the loop condition
FOR i = 1 TO n is represented as a decision node. Example: Consider the following code:
3. Action Nodes: Represent the actions being performed, such as reading input, updating
variables, or printing output. int example() {
4. End Nodes: Mark the end of the program or control flow, like the final step where the int a = 5;
return a;
sum is printed. int b = 10; // Unreachable code
}
This flow graph shows how the control flows through different parts of the program, based on
loop conditions and actions performed inside the loop. In the above example, the line int b = 10; is unreachable because the function returns before
it. This line will never be executed, and therefore, it is considered redundant.
Question: Explain the following peephole optimization techniques with examples: i) Elimination
of Redundant Code Optimized Code:
ii) Elimination of Unreachable Code
[7M] int example() {
int a = 5;
return a;
Answer: }
Answer: Example:
In compiler design, Structure Preserving Transformations (SPTs) refer to the techniques used Consider the following code snippet:
to modify the structure of a program while preserving its original semantics (meaning or
behavior). These transformations are crucial during intermediate code optimization phases, int x = 10;
where the goal is to improve the efficiency of the program without changing the output. SPTs int y = x * 2;
maintain the program's logical structure and correctness while optimizing its performance. int z = x + 5;
By applying constant folding and constant propagation, the optimized version can be:
Key points about Structure Preserving Transformations:
int x = 10;
1. Preservation of Semantics: The most important feature of an SPT is that it preserves the int y = 20; // x * 2 is evaluated at compile time
program’s behavior, meaning that after the transformation, the program should still int z = 15; // x + 5 is evaluated at compile time
produce the same output for the same input.
2. Optimization Techniques: These transformations are primarily applied to optimize the
intermediate code generated by the compiler. Optimizations can include removing Q: Describe in detail about Peephole Optimization. What is a flow graph? Explain with a
redundant code, simplifying expressions, and improving control flow. suitable example. [14M]
3. Examples of Structure Preserving Transformations:
o Constant Folding: This optimization involves evaluating constant expressions at Answer:
compile time rather than runtime. For example:
o int x = 3 + 4; Peephole Optimization
This can be transformed to: Peephole optimization is a type of code optimization technique in which a small window or
"peephole" of a few instructions is examined, and the unnecessary or redundant instructions are
int x = 7;
replaced with more efficient ones. This technique is often applied during the code generation
phase of compilers, and it focuses on small-scale improvements that can make a big impact on
The structure of the code remains intact, but the computation is simplified.
performance. The optimization is done by looking at a small set of instructions (usually 3 to 5)
and transforming them into a more optimized form.
Key objectives of Peephole Optimization: Consider the following simple code segment:
An activation record (also known as a stack frame) is a data structure that holds information Consider the following intermediate code statements numbered from 1 to 12:
about a function call. It is created when a function is invoked and destroyed once the function
1 i = 0
execution completes. The activation record contains the following elements: 2 if i < n goto 4
3 goto 11
Return Address: The address to return to after the function completes. 4 t1 = 4 * i
Parameters: The values or references passed to the function. 5 t2 = a[t1]
Local Variables: Variables declared inside the function. 6 t3 = t2 + 1
7 t4 = 4 * i
Saved Registers: Registers that need to be preserved between function calls. 8 b[t4] = t3
Control Link: A pointer to the activation record of the calling function (used for stack 9 i = i + 1
unwinding). 10 goto 2
Data Link: A pointer to the activation record of the currently running function's stack (if 11 t5 = 4 * i
12 b[t5] = 0
needed).
Example: When a function is called, a new activation record is pushed onto the call stack. For (a) Construct a control flow graph for the given code and explain which of the 4 * i computations
instance, in the function int sum(int a, int b), the activation record will store a, b, return in statement 7 and statement 11 are redundant.
address, and any local variables used within the function.
2. Induction Variable:
Answer:
An induction variable is a loop variable that changes in a regular, predictable pattern, typically
incrementing or decrementing by a constant value in each iteration of a loop. Induction variables Control Flow Graph Construction:
help in analyzing the loop behavior and are important in optimization techniques like loop
unrolling. 1. Basic Blocks:
o Block 1: Statement 1.
o Block 2: Statement 2 (condition checking).
Induction Variables in the Given C Code: o Block 3: Statement 3 (goto 11).
o Block 4: Statements 4 to 8 (computation and assignments).
In the given loop:
o Block 5: Statement 9 (increment).
o Block 6: Statement 10 (goto 2).
for( i = 0, j = 1, k = 2, l = 4 ; i < n ; i++) {
j = j + 2; o Block 7: Statement 11.
k = k + 3; o Block 8: Statement 12.
l = l + 4; 2. Control Flow Edges:
sum = sum + i * j * k; o Block 1 → Block 2
}
o Block 2 → Block 3 (if i >= n)
o Block 2 → Block 4 (if i < n)
i is the induction variable because it is being incremented by 1 in each iteration of the
o Block 4 → Block 5
loop (i++).
o Block 5 → Block 6
o Block 6 → Block 2 (loop continues)
o Block 3 → Block 7 (exit condition) In static memory allocation, memory is allocated at compile time, and the size and number of
o Block 7 → Block 8 variables are fixed during the program's execution. This approach is not ideal for languages like
C for the following reasons:
Control Flow Graph:
1. Lack of Flexibility: In C, the size of arrays and variables cannot be changed during
+---------+ +---------+ program execution. This is problematic when the exact size of data is not known at
| Block 1| --->| Block 2| compile time. For example, consider a program that processes an unknown number of
+---------+ +---------+
| ^ student records. Using static memory allocation would require the programmer to decide
+------v--+----+ on a fixed array size at compile time, leading to either wasted memory or insufficient
| Block 3 | space.
+--------------+
|
+---------v----------+ +---------+
Example:
| Block 4 |<------| Block 7|
+-------------------+ +---------+ #include <stdio.h>
| ^ | #define MAX_STUDENTS 100
+--v---+----+ +---------v---------+
| Block 5 | -----> | Block 8 | int main() {
+-------------+ +-------------------+ int student_scores[MAX_STUDENTS];
// But if there are only 10 students, the rest of the array is
wasted
return 0;
Redundant Computations of 4 * i: }
The computation 4 * i is performed in Statement 4 and Statement 7. Let’s analyze: In this example, if there are fewer than 100 students, the remaining array memory goes
unused. On the other hand, if there are more than 100 students, the program will not have
1. Statement 4: The expression t1 = 4 * i computes the value of 4 * i for the current enough space, leading to errors.
value of i and stores it in t1. This value is then used in statement 5 (t2 = a[t1]).
2. Statement 7: The expression t4 = 4 * i computes 4 * i again for the same value of i, 2. Wasted Memory: In static allocation, memory for the largest possible case must be
but this time the result is used to update the array b at position b[t4] = t3. reserved. This often leads to wasted memory when the program doesn't require all the
3. Statement 11: The expression t5 = 4 * i computes 4 * i after the loop ends, and this space.
value is used to assign b[t5] = 0. 3. Inability to Handle Dynamic Data: If a program needs to handle dynamic or user-
defined data (like reading an unknown number of user inputs), static allocation fails
Redundancy: because it cannot adjust memory based on runtime conditions.
1. Return address – where the control should go after the function call is completed. f(1) returns 1 * f(0) = 1 * 1 = 1
2. Parameters – the arguments passed to the function (in this case, n). f(2) returns 2 * f(1) = 2 * 1 = 2
3. Local variables – any local variables used inside the function (if any). f(3) returns 3 * f(2) = 3 * 2 = 6
4. Saved registers – storing the values of registers that will be used in the current function f(4) returns 4 * f(3) = 4 * 6 = 24
call. f(5) returns 5 * f(4) = 5 * 24 = 120
f(6) returns 6 * f(5) = 6 * 120 = 720
Let's now break down the recursive calls and show how the activation records look.
Final Activation Record for f(6):
Function Call Stack for f(6):
The activation record for the final call f(6) looks like this:
1. Call to f(6):
o Return Address: Main function (where control should return after f(6) finishes). Return Address: Main function (to return the result of f(6)).
o Parameter n: 6 Parameter n: 6
o Local Variable: None. Local Variable: None (no local variables).
2. Call to f(5): Return Value: 720
o Return Address: To the point where n * f(n-1) is evaluated in f(6).
o Parameter n: 5 Question:
o Local Variable: None.
3. Call to f(4): Explain in detail common sub-expression elimination, copy propagation, and dead code
o Return Address: To the point where n * f(n-1) is evaluated in f(5). elimination optimizations with examples. (7 Marks)
o Parameter n: 4
o Local Variable: None. Answer:
4. Call to f(3):
o Return Address: To the point where n * f(n-1) is evaluated in f(4). Optimizations in compilers aim to improve the efficiency of the generated code. Some of the
o Parameter n: 3 commonly used optimizations are:
o Local Variable: None.
5. Call to f(2): 1. Common Sub-expression Elimination (CSE)
o Return Address: To the point where n * f(n-1) is evaluated in f(3).
o Parameter n: 2 Definition:
o Local Variable: None. Common Sub-expression Elimination (CSE) is an optimization technique that identifies and
6. Call to f(1): eliminates expressions that are computed multiple times. It involves finding expressions that
o Return Address: To the point where n * f(n-1) is evaluated in f(2). appear more than once and replacing them with a temporary variable.
o Parameter n: 1
o Local Variable: None. Example:
7. Call to f(0): Consider the following code:
o Return Address: To the point where n * f(n-1) is evaluated in f(1).
o Parameter n: 0 a = b + c;
o Local Variable: None. d = b + c;
o Base Case Reached: Since n == 0, f(0) returns 1.
Here, the expression b + c is computed twice. With CSE, we can eliminate the redundancy:
Unwinding the Stack:
t = b + c;
a = t;
d = t; Common Sub-expression Elimination (CSE) reduces repeated calculations by reusing
the results.
By storing the result of b + c in a temporary variable t, we avoid recalculating it. Copy Propagation eliminates redundant assignments by replacing variables with their
equivalent values.
2. Copy Propagation Dead Code Elimination removes code that doesn't contribute to the final result.
Example:
Definition:
Dead Code Elimination is an optimization technique that removes code that does not affect the
Consider the following code:
program's output. This usually involves eliminating unreachable code or instructions that are
never used (e.g., calculations whose results are not used). int a = 10, b = 20, c;
c = a + b;
Example: b = c * 2;
Consider the following code:
In the above code:
a = 5;
b = a + 2; After the statement c = a + b;, a and b are live because their values will be used in the
c = b + 3;
next computation (b = c * 2;).
However, the variable a is no longer used after c = a + b;, making it dead in
Now, if c is never used in the program, the code related to c is considered dead code:
subsequent computations. This can be safely eliminated in an optimization step.
a = 5;
b = a + 2; 2) Structure Preserving Transformations (7 Marks)
In this case, c = b + 3; is eliminated because c is not used, and it has no effect on the Structure Preserving Transformations are compiler transformations that change the internal
program's output. representation of the program without altering the program's semantic meaning. These
transformations aim to optimize the program while maintaining its original structure and
Summary: functionality.
Key Points:
Semantic Integrity: The program's output should remain the same after the
transformation. int main() {
int x = 10, y = 20;
Efficiency: The transformation aims to improve efficiency, such as reducing the int result = add(x, y);
execution time or memory usage, but the logic of the program should not be affected. return 0;
Examples of Structure-Preserving Transformations: }
o Loop Unrolling: This transformation reduces the overhead of looping by
replicating the loop body multiple times. In this program:
Example:
for (int i = 0; i < 4; i++) { When add(x, y) is called, memory for the parameters a, b, and the local variable sum is
a[i] = b[i] + 10; allocated on the stack.
}
Once the function add completes, the memory used by these variables is deallocated.
After unrolling: Working of Stack Allocation:
a[0] = b[0] + 10;
a[1] = b[1] + 10; 1. The call to main pushes a new stack frame for main with local variables x, y, and result.
a[2] = b[2] + 10; 2. The call to add(x, y) pushes a new stack frame containing a, b, and sum.
a[3] = b[3] + 10; 3. After add returns, its stack frame is removed, and control returns to main.
o Strength Reduction: Replaces expensive operations with cheaper ones. The stack grows and shrinks as functions are called and return, ensuring automatic memory
Example: Replacing multiplication by a constant with addition. management for local variables.
for (int i = 0; i < 10; i++) {
a[i] = i * 2;
} (b) Basic Block and Algorithm for Constructing Control Flow Graph:
Transformed to: Basic Block: A Basic Block is a sequence of consecutive statements in a program with a single
entry point and a single exit point. It has no branches (except at the entry and exit points). It is a
for (int i = 0; i < 10; i++) { fundamental unit of control flow in a program.
a[i] = i + i;
} For example, the following code represents a basic block: