Compiler Design and ConstructionUnit 910 min read
Run-Time Environments & Activation Records: Stacks, Trees & Procedure Calls
Unit 9 of Compiler Design and Construction explores how compilers manage program execution at runtime, focusing on activation records (stack frames), activation trees, and the caller-callee protocol. You’ll learn how nested procedures, recursion, and local variables are tracked, with visual traces of stack operations a
Core Concepts: Activation Records and Runtime Stacks
1. What is an Activation Record?
An activation record (AR), also called a stack frame, is a block of memory allocated on the runtime stack whenever a procedure (function/subroutine) is called. It stores:
- Control information (return address, saved registers)
- Access links (pointers to parent/child frames)
- Local variables (temporaries, parameters)
- Return value (if any)
Why a stack?
- LIFO (Last-In-First-Out) ensures proper nesting of calls.
- Dynamic allocation (no fixed size) handles recursion and variable scopes.
- Efficient access (top of stack is fastest).
2. Components of an Activation Record
| Component | Purpose | Example (Nepali Context) |
|---|---|---|
| Return Address | Where to jump after procedure ends. | In eSewa’s payment flow, after verifyOTP(), control returns to displayReceipt(). |
| Saved Machine Status | Registers (e.g., PC, SP) saved before callee executes. | Pathao’s ride-matching: Saves driver location before calling calculateFare(). |
| Access Link | Pointer to parent’s AR (for non-local variable access). | Bank loan calculator: Accesses interestRate from the parent Loan procedure. |
| Control Link | Pointer to previous AR (for recursion or nested calls). | Ncell’s call-tree: Tracks callForward() → callForward() (recursive). |
| Local Variables | Temporary variables, loop counters. | Daraz’s order queue: i, j in nested loops for inventory checks. |
| Actual Parameters | Arguments passed to the procedure. | NTC’s fare calculator: Passes distance, vehicleType to computeFare(). |
| Return Value | Result stored if the procedure returns a value. | NEPSE stock app: Returns currentPrice after calling fetchStockData(). |
In the Real World
eSewa’s Payment Flow
- Activation Records: Each step (
selectService→enterAmount→verifyOTP) gets its own AR. - Access Links:
verifyOTP()accessestransactionIDfromenterAmount()’s AR. - Stack Trace: If OTP fails, the system unwinds the stack to return to
selectService().
- Activation Records: Each step (
Pathao’s Ride-Matching Algorithm
- Recursion:
findNearestDriver()calls itself for each driver in the pool (activation tree branches). - Saved Machine Status: Preserves driver location before calculating fare.
- Recursion:
Kathmandu Traffic Routes (Simulated)
- Activation Tree: Each intersection (
Thapathali→Koteshwor) is a procedure call. - Control Links: If a route fails (e.g.,
RingRoadjammed), the system backtracks to the parent node (KTM_CBD).
- Activation Tree: Each intersection (
3. Activation Tree: Visualizing Procedure Calls
An activation tree shows the hierarchy of procedure calls. Each node is an AR, and edges represent caller-callee relationships.
flowchart TD
A["Main()"] --> B["calculateTax()"]
A --> C["processOrder()"]
C --> D["validatePayment()"]
C --> E["updateInventory()"]
E --> F["checkStock()"]
F --> G["checkStock()"] <!-- Recursive call -->Key Observations:
- Depth = Nesting level (e.g.,
checkStock()called fromupdateInventory()). - Recursion creates sibling nodes (e.g.,
checkStock()calling itself). - Leaf nodes are procedures that don’t call others (e.g.,
validatePayment()).
4. Caller vs. Callee: Who Does What?
| Activity | Caller | Callee |
|---|---|---|
| Stack Management | Pushes callee’s AR onto the stack. | Pops its AR when returning. |
| Parameter Passing | Pushes actual parameters. | Receives parameters in its AR. |
| Control Transfer | Jumps to callee’s code. | Executes; returns to caller. |
| Access to Non-Locals | Uses access links to parent AR. | May need access links for nested calls. |
| Register Saving | Saves registers before call. | Restores registers on return. |
Example: Nepali Bank Loan Calculation
def calculateEMI(principal, rate, years):
monthly_rate = rate / 12
n = years * 12
emi = (principal * monthly_rate * (1 + monthly_rate)**n) / ((1 + monthly_rate)**n - 1)
return emi
# Caller: Main program
principal = 1000000
rate = 0.08
years = 5
emi = calculateEMI(principal, rate, years) # AR pushed
Stack Trace:
- Caller (
main) pushesprincipal,rate,yearsand jumps tocalculateEMI(). - Callee (
calculateEMI) allocates its AR, computesemi, then returns tomain. - Stack Unwinds:
calculateEMI()’s AR is popped;mainuses the returnedemi.
5. Handling Recursion in Activation Records
Recursion creates multiple ARs for the same procedure in the stack. Each recursive call gets its own frame.
Example: Factorial Calculation
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n - 1) # Recursive call
Stack Growth for factorial(3):
AR Contents at Each Step:
| Call | Local n |
Return Address | Access Link |
|---|---|---|---|
factorial(3) |
3 | return to main |
NULL (root) |
factorial(2) |
2 | 3 * result |
Pointer to factorial(3) |
factorial(1) |
1 | 2 * result |
Pointer to factorial(2) |
factorial(0) |
0 | return 1 |
Pointer to factorial(1) |
Key Point:
- The access link forms a chain to the original caller.
- Tail recursion (optimized) can reuse the same AR.
6. Three-Address Code and Activation Records
Compilers generate intermediate code (e.g., three-address code) that maps to AR operations. Example:
n = (a + b) * (c - d)
for i = 0; i < n; i++:
for j = 0; j < n; j++:
x = n + i + j
Three-Address Code:
t1 = a + b
t2 = c - d
n = t1 * t2
i = 0
L1: if i >= n goto L3
j = 0
L2: if j >= n goto L4
t3 = n + i
t4 = t3 + j
x = t4
j = j + 1
goto L2
L4: i = i + 1
goto L1
L3: exit
Stack Behavior:
- Loop variables (
i,j) are stored in the AR of the loop’s scope. - Temporaries (
t1,t2) are allocated in the AR of the expression evaluation. - Recursive calls (if any) would push new ARs.
7. Symbol Table and Activation Records
The symbol table tracks variables, but activation records manage their runtime scope.
| Symbol Table | Activation Record |
|---|---|
| Stores variable names, types, scopes. | Holds runtime instances of variables. |
| Static (created at compile time). | Dynamic (created at call time). |
Example: int x = 10; in main. |
Example: x in main()’s AR vs. x in calculateTax()’s AR. |
Example: Scope Conflict
x = 10
def outer():
x = 20
def inner():
x = 30 # Shadows outer’s x
print(x) # Uses inner’s AR
inner()
print(x) # Uses outer’s AR
AR Hierarchy:
outer()’s AR → inner()’s AR (child)
inner()’sxhidesouter()’sxuntilinner()returns.
Exam Tip
- Draw the Stack: Always show the state after each operation (push/pop/call/return).
- Example: For
factorial(3), draw the stack before and after each recursive call.
- Example: For
- Label All Components: In activation records, explicitly mark:
- Access link (parent pointer).
- Control link (for recursion).
- Saved registers (e.g.,
PC,SP).
- Real-World Mapping: Relate to Nepali apps:
- eSewa: Procedure calls = payment steps.
- Pathao: Recursion = driver matching.
- Three-Address Code: For loops/nested calls, show how temporaries map to ARs.
- Common Pitfalls:
- Forgetting to restore registers on return.
- Incorrect access link setup in recursive calls.
- Mismatched parameter passing (e.g., passing by value vs. reference).
Practice Question with Solution
Question: Draw the activation tree and stack frames for the following calls:
def main():
a = 10
b = callA(a)
print(b)
def callA(x):
y = x * 2
z = callB(y)
return z
def callB(w):
return w + 5
Solution:
- Activation Tree:
- Stack Frames:
- After
callA(a):[main()’s AR] - a = 10 - b = ? [callA()’s AR] - x = 10 (passed by value) - y = ? - After
callB(y):[main()’s AR] [callA()’s AR] - y = 20 (10 * 2) [callB()’s AR] - w = 20 - return value = 25 (20 + 5) - After
return:callB()’s AR is popped;callA()getsz = 25.callA()’s AR is popped;main()getsb = 25.
- After
Key Formulas (If Applicable)
For runtime stack growth, the maximum depth is:
Example: factorial(5) with 2 nested loops → Depth = 5 (recursion) + 2 (loops) = 7.
Based on the TU BSc CSIT syllabus for Compiler Design and Construction (CSC365), unit 9.
Discussion
Loading…