
- Compiler Design - Home
- Compiler Design - Overview
- Compiler Design - Architecture
- Phases
- Compiler Design - Phases
- Compiler Design - Global Optimization
- Compiler Design - Local Optimization
- Lexical Analysis
- Compiler Design - Lexical Analysis
- Compiler Design - Regular Expressions
- Compiler Design - Finite Automata
- Compiler Design - Language Elements
- Compiler Design - Lexical Tokens
- Compiler Design - FSM
- Compiler Design - Lexical Table
- Compiler Design - Sequential Search
- Compiler Design - Binary Search Tree
- Compiler Design - Hash Table
- Syntax Analysis
- Compiler Design - Syntax Analysis
- Compiler Design - Parsing Types
- Compiler Design - Grammars
- Compiler Design - Classes Grammars
- Compiler Design - Pushdown Automata
- Compiler Design - Ambiguous Grammar
- Parsing
- Compiler Design - Top-Down Parser
- Compiler Design - Bottom-Up Parser
- Compiler Design - Simple Grammar
- Compiler Design - Quasi-Simple Grammar
- Compiler Design - LL(1) Grammar
- Error Recovery
- Compiler Design - Error Recovery
- Semantic Analysis
- Compiler Design - Semantic Analysis
- Compiler Design - Symbol Table
- Run Time
- Compiler Design - Run-time Environment
- Code Generation
- Compiler Design - Code Generation
- Converting Atoms to Instructions
- Compiler Design - Transfer of Control
- Compiler Design - Register Allocation
- Forward Transfer of Control
- Reverse Transfer of Control
- Code Optimization
- Compiler Design - Code Optimization
- Compiler Design - Intermediate Code
- Basic Blocks and DAGs
- Control Flow Graph
- Compiler Design - Peephole Optimization
- Implementing Translation Grammars
- Compiler Design - Attributed Grammars
Reverse Transfer of Control in Compilers
In code generation, loops and jump instructions play a crucial role in controlling the program flow. Intermediate code generation is a key phase in the compiler that converts high-level constructs into machine-interpretable code. While instructions generally follow a linear sequence, there are many cases where the execution needs to loop back to a previous instruction. This backward flow, known as reverse transfer of control, is essential for implementing loops, recursion, and error recovery.
In this chapter, we will discuss reverse transfer of control, its types, and how compilers generate machine code to handle backward jumps.
What is Reverse Transfer of Control?
Reverse transfer of control occurs when execution jumps backward to re-execute a set of instructions. So, when is it needed?
Reverse transfer of control can be useful in the following scenarios −
- Loops − Repeating code until a condition is met.
- Function Recursion − Functions calling themselves until a base case is reached.
- Error Recovery − Retrying operations after failures.
Unlike the forward transfers, the backward jumps focus on iteration and repetition. Now the compiler generates optimized instructions (for example, J, BGE, JAL) to give efficient backward control flow.
Types of Reverse Transfer of Control
Let us check the different types of reverse transfer of control −
Loops (While, For, Do-While)
Loops rely on backward jumps to repeat code blocks. The compiler places condition checks and jump instructions strategically.
Example: While Loop in C
Take a look at the following example −
while (i < 10) { i = i + 1; }
Intermediate Representation (IR) using Atoms
(LBL, L1) (TST, i, 10, <, L2) # If i >= 10, exit loop (ADD, i, 1, i) # i = i + 1 (JMP, L1) # Repeat loop (LBL, L2)
Translation to Machine Code (MIPS Example)
L1: lw $t1, i li $t2, 10 bge $t1, $t2, L2 # If i >= 10, exit loop addi $t1, $t1, 1 # i = i + 1 sw $t1, i j L1 # Repeat loop L2:
Here, we can see the j L1 instruction transfers control backward to keep executing the loop.
Do-While Loops
Let us see the other type of loop. This is exit control loop. Unlike while loops, do-while loops always execute at least once before checking the condition. The compiler places the loop condition check at the end and jumps backward if the condition is still valid.
Example: Do-While Loop in C
Take a look at the following example −
do { i = i + 1; } while (i < 10);
Intermediate Representation (IR) using Atoms −
(LBL, L1) (ADD, i, 1, i) # i = i + 1 (TST, i, 10, <, L1) # If i < 10, repeat loop
Translation to Machine Code (MIPS Example) −
L1: addi $t1, $t1, 1 # i = i + 1 lw $t1, i li $t2, 10 blt $t1, $t2, L1 # If i < 10, repeat loop
The blt (branch if less than) instruction will transfers control backward to L1, repeating execution.
For Loops
A for loop is also an entry control loop. But it has initialization, condition check, and increment in a structured form. The compiler generates jumps to handle the condition check and iteration step.
Example: For Loop in C
Take a look at the following example −
for (i = 0; i < 10; i++) { sum = sum + i; }
Intermediate Representation (IR) using Atoms −
(MOV, 0, , i) # i = 0 (LBL, L1) (TST, i, 10, <, L2) # If i >= 10, exit loop (ADD, sum, i, sum) # sum = sum + i (ADD, i, 1, i) # i = i + 1 (JMP, L1) # Repeat loop (LBL, L2)
Translation to Machine Code (MIPS Example) −
li $t1, 0 # i = 0 L1: bge $t1, 10, L2 # If i >= 10, exit loop add $t3, $t3, $t1 # sum = sum + i addi $t1, $t1, 1 # i = i + 1 j L1 # Repeat loop L2:
Here, we can see the condition check and increment operations are separate, with j L1 that is transferring control backward.
Function Recursion
For function based approaches like recursions the backward transfer occurs. In recursive function calls, where execution jumps back to the function's starting point until a base condition is met.
Example: Factorial Function in C
Take a look at the following example −
int factorial(int n) { if (n == 0) return 1; return n * factorial(n - 1); }
Intermediate Representation (IR) using Atoms −
(TST, n, 0, ==, L1) # If n == 0, return 1 (SUB, n, 1, T1) (CALL, factorial, T1, T2) (MUL, n, T2, R) # R = n * factorial(n-1) (LBL, L1) (MOV, 1, , R)
Translation to Machine Code (MIPS Example) −
factorial: beq $a0, 0, base_case # If n == 0, return 1 addi $sp, $sp, -4 sw $ra, 0($sp) # Save return address addi $a0, $a0, -1 jal factorial # Recursive call lw $ra, 0($sp) addi $sp, $sp, 4 mul $v0, $a0, $v0 # Multiply n * factorial(n-1) jr $ra # Return base_case: li $v0, 1 # Return 1 jr $ra
Each recursive call transfers control backward to factorial, this is executing another function call until reaching the base case.
To help remember the Reverse Transfer of Control easily, you can follow the table given below −
Type | Usage | Machine Instructions |
---|---|---|
While Loops | Iteration with condition check | BGE, BLT, J |
Do-While Loops | Ensures at least one execution | BLT, J |
For Loops | Structured iteration | BGE, ADD, J |
Recursion | Function calls returning back | JAL, JR, BEQ |
Conclusion
In this chapter, we explored the concept of reverse transfer of control in compiler design which is fundamental for handling loops and recursion. Compilers translate high-level constructs such as while loops, for loops, and recursive functions into branch and jump instructions to enable efficient backward execution.
We covered how compilers manage backward jumps, how developers can write optimized code, and how iteration and recursion function at a low level. Whether it's a simple loop or a complex recursive algorithm, reverse control transfer ensures that programs execute repetitively and predictably.