Runtime environments and activation records
How procedure calls are implemented at run time: activations and the activation tree, the control stack, the fields of an activation record, calling and return sequences, static (lexical) and dynamic scoping, access links for nested procedures, and counting activations and stack depth of recursive programs.
Why it exists
A procedure can be called many times, even from inside itself, and each call needs its own copies of the parameters and local variables. The storage cannot be fixed at compile time (static allocation fails for recursion), and it is not enough to allocate on call and free on return in an arbitrary order: because calls are properly nested, a stack is the right structure. The compiler must therefore design what is stored for one call (the activation record), how a call and a return manipulate the stack, and how a name used in a procedure is mapped to the right storage (the scope rules).
Prerequisites
You need the picture of recursion and the call stack from recursion and the call stack: a call pushes a frame, a return pops it.
Precise definitions
Activation. One execution of a procedure body, from the call to the return. Different calls of the same procedure are different activations. The lifetime of an activation is the time from the first step of the body to the last (including the time spent in procedures it calls).
Activation tree. A tree with one node per activation: the root is the activation of main, and the children of the node for are the activations started by , from left to right in the order of the calls. If control returns from to its caller, the node is complete. The lifetimes of two activations are either disjoint or nested (never partly overlapping), which is what makes a stack possible.
Control stack (run-time stack). The stack of activation records of the activations that are currently live: it holds exactly the path from the root of the activation tree to the activation that is running. A call pushes one record, a return pops one.
Activation record (frame). The block of storage for one activation. The usual fields, from the bottom of the record upwards (the order and the exact content depend on the compiler and the machine):
| Field | Content |
|---|---|
| actual parameters | the values or addresses passed by the caller (often first, in registers on modern machines) |
| returned value | where the callee puts its result (often a register) |
| control link | points to the activation record of the caller (the dynamic link) |
| access link | points to the record of the lexically enclosing procedure (needed for nested procedures) |
| saved machine status | the return address and the saved registers, including the old frame pointer |
| local data | the local variables |
| temporaries | the temporary values of expression evaluation |
A frame pointer (or base pointer) points to a fixed place in the current record; the local variables are addressed as constant offsets from it, and a stack pointer points to the top.
Calling sequence and return sequence. On a call: the caller evaluates and stores the actual parameters, saves the return address and calls; the callee saves the old frame pointer and registers, sets the new frame pointer, allocates space for locals and temporaries and starts to execute. On return: the callee puts the return value in place, restores the registers and the frame pointer and releases its record; the caller reads the result. The split of the work between caller and callee is a design choice; what the callee can know (its own frame size) it should do, which keeps the caller's code small.
Scope rules. Which declaration does a name refer to?
- Static (lexical) scoping: a non-local name refers to the declaration in the textually enclosing block or procedure that is nearest outwards. This is determined from the program text; most languages (C, Pascal, Java) use it.
- Dynamic scoping: a non-local name refers to the most recent active binding, found by searching the callers (the control links) from the innermost outwards. It depends on the run-time call chain. (Some Lisps and shell languages.)
The same program may print different values under the two rules (first animation below).
Access to non-local data with nested procedures (static scoping). A procedure declared inside another one can use the outer procedure's variables. Each activation record has an access link to the activation record of the procedure that encloses it in the program text (the most recent one). To find a variable declared nesting levels out, follow access links and use the variable's offset. If the nesting depth of a procedure is the number of procedures that enclose it (the outermost program has depth 0), then a procedure at depth calls a procedure declared inside a procedure of depth : the new record's access link is found by following access links from the caller (and if is declared inside the caller, and the link just points to the caller). A display is an array indexed by nesting depth that holds a pointer to the most recent record at each depth: non-local access then takes constant time instead of following a chain, at the cost of updating the display on calls and returns.
Access to non-local data with dynamic scoping. Deep access searches the control links at run time (slow but cheap to call); shallow access keeps a stack of values for each name (fast access but each call must save and restore the bindings).
How it works
The same program under both scoping rules. The program has a global x = 10; f prints x; g declares its own x = 20 and calls f. The control link of f points to g (its caller); the access link of f points to main (where f is declared). Under static scoping f follows the access link and prints 10.
Static scoping: which x does f print?: start
Program executed, not typedstatic (lexical) scoping: a non-local name is found by following the access links
1var x = 10;2proc f() {3 print x;4}5proc g() {6 var x = 20;7 f();8}9g();
Step 0
The program has a global x = 10; f prints x; g declares its own x = 20 and calls f. Execution starts in main, whose record is the first on the stack.
Under dynamic scoping the same call finds the x of the caller g first and prints 20.
Dynamic scoping: which x does f print?: start
Program executed, not typeddynamic scoping: a non-local name is found by following the control links (the callers)
1var x = 10;2proc f() {3 print x;4}5proc g() {6 var x = 20;7 f();8}9g();
Step 0
The same program, but now a non-local name is looked up in the callers, innermost first.
Now nested procedures: inner is declared inside outer, which is declared in main. When inner prints a + b + m, it finds m in its own record, b one access link out (in outer) and a two access links out (in main).
Nested procedures and access links: start
Program executed, not typedstatic scoping: inner is declared inside outer, which is declared in main
1var a = 1;2proc outer(n) {3 var b = 10;4 proc inner(m) {5 print (a + b) + m;6 }7 inner(n);8}9outer(5);
Step 0
main declares a = 1 and outer; outer declares b = 10 and inner; inner prints a + b + m. The access link of an activation points to the activation of the procedure that encloses it in the text.
Recursion: one record per activation. fact(3) makes the stack four records deep (the three activations of fact and main); each activation has its own n, while the global r is shared.
Recursion: one activation record per call: start
Program executed, not typedstatic scoping
1var r = 1;2proc fact(n) {3 if (n > 1) {4 fact(n - 1);5 }6 r = r * n;7}8fact(3);9print r;
Step 0
fact(n) multiplies the global r by n after the recursive call returns, so r becomes 1 * 2 * 3.
The activation tree of the recursive Fibonacci function: every call is a node, and the control stack at any moment is the path from the root to the running activation. For fib(4) there are 9 activations and the stack is never more than 4 deep.
Activation tree of fib(4): start
Parser run by a script, not typedActivation tree and control stack of the recursive function fib (fib(0) = 0, fib(1) = 1, fib(k) = fib(k - 1) + fib(k - 2)). The stack is shown with the newest call at the right.
Step 0
The activation tree has one node for every call. A node's children are the calls it makes, in order. The control stack holds exactly the path from the root to the call that is running now.
Worked examples
EX 1Static or dynamic scope?
Easyvar x = 10;
proc f() { print x; }
proc g() { var x = 20; f(); }
g();Under static scoping f refers to the x declared where f is declared (the global one): the output is 10. Under dynamic scoping f finds the most recent active binding of x, the one in g: 20. The same program text, two answers: these questions test only whether you follow lexical nesting or the call chain.
EX 2Stack depth of a recursion
Easyfact(3) as in the animation: activations fact(3), fact(2), fact(1), plus main. At the deepest point the control stack has 4 records. In general fact(n) (with the base case at 1) has activations of fact, so the stack has records including main, or records if main is not counted. The activation tree is a chain (no branching): each node has one child except the last.
EX 3Number of activations of fib
MediumThe tree of fib(4) has 9 nodes (the animation). In general with , the number of activations of fib(n) is : for : ; for : ; for : . The maximum stack depth is calls for (the leftmost path fib(n), fib(n-1), ..., fib(1)), so the time is exponential but the space is linear. (The formula was checked by counting the calls of the program for .) A frequent question: "how many times is fib(2) called during fib(5)?" Count the nodes labelled fib(2) in the tree: 3.
EX 4Access links: how many to follow?
MediumDepths: main 0, outer 1, inner 2 (declared inside outer), deeper 3 (declared inside inner). (a) deeper uses a variable of main: 3 access links out (). (b) inner uses a variable of outer: 1 link. (c) deeper calls inner: the caller has depth 3 and inner is declared in outer (depth 1), so the new access link is found by following links from the caller's record, reaching the record of outer. (d) outer calls inner: the caller is outer itself, depth 1, : 0 links, the access link points to the caller. In the animation of nested procedures the access link of inner points to outer and that of outer to main.
EX 5Which statements are true?
GATE level(a) "In static scoping the scope of a name is decided at compile time from the program text": true. (b) "The control link of an activation record always points to the activation of the procedure that textually encloses it": false: that is the access link; the control link points to the caller. (c) "Under dynamic scoping a non-local name is resolved through the control links": true. (d) "A language that allows recursion cannot use only static allocation for locals": true: each activation needs its own copy. (e) "The activation records of two procedures that are live at the same time can be freed in any order": false: they are freed in reverse order of allocation (stack discipline). (f) "In the animation of the nested example the lookup of a from inner follows two access links": true.
Cheat box
Cheat box
- Activation = one call; activation tree = calls as nodes; control stack = path from the root to the running call.
- Record fields: parameters, return value, control link (caller), access link (lexical parent), saved registers and return address, locals, temporaries.
- Control link: caller. Access link: lexically enclosing procedure. Do not mix them up.
- Static scoping follows access links (text); dynamic scoping follows control links (calls).
- Access link hops = depth of the user minus depth of the declaring procedure; new access link: follow (caller depth - declaring depth of callee's parent) links from the caller.
fib(n)has activations and stack depth .- Display: array by depth for constant-time non-local access.
Traps and GATE patterns
Connections
Builds on
Unlocks
Across subjects
Within this subject
Journeys that pass through here
The stack picture is the one of recursion and the call stack and, at machine level, of stack frames and calls. How the actual parameters are passed is parameter passing. Where the stack sits among static and heap storage is storage organisation. The symbol table of phases of a compiler supplies the offsets of the variables within the record.
PYQs
4 previous year questions from your book. I re-solved each one myself (0 by running or simulating, 4 by reasoning only); the book's key is shown next to my answer. All PYQs
Which of the following are true? I. A programming language which does not permit global variables of any kind and has no nesting of procedures/functions, but permits recursion can be implemented with static storage allocation. II. Multi-level access link (or display) arrangement is needed to arrange activation records only if the programming language being implemented has nesting of procedures/functions. III. Recursion in programming languages cannot be implemented with dynamic storage allocation. IV. Nesting of procedures/functions and recursion require a dynamic heap allocation scheme and cannot be implemented with a stack- based allocation scheme for activation records. V. Programming languages which permit a function to return a function as its result cannot be implemented with a stack-based storage allocation scheme for activation records.
- (a)II and V only
- (b)I, III and IV only
- (c)I, II and V only
- (d)II, III and V only
Which of the following are true? (I) A programming language which does not permit global variables of any kind and has no nesting of procedures/functions, but permits recursion can be implemented with static storage allocation (II) Multi-level access link (or display) arrangement is needed to arrange activation records only if the programming language being implemented has nesting of procedures/functions. (III) Recursion in programming languages cannot be implemented with dynamic storage allocation (IV) Nesting procedures/functions and recursion require a dynamic heap allocation scheme and cannot be implemented with a stack- based allocation scheme for activation records (V) Programming languages which permit a function to return a function as its result cannot be implemented with a stack-based storage allocation scheme for activation records.
- (a)II and V only
- (b)I, III and IV only
- (c)I, II and IV only
- (d)II, III and V only
Which of the following statements are Correct? 1. Static allocation of all data areas by a compiler makes it impossible to implement recursion. 2. Automatic garbage collection is essential to implement recursion. 3. Dynamic allocation of activation records is essential to implement recursion. 4. Both heap and stack are essential to implement recursion.
- (a)1 and 2 only
- (b)2 and 3 only
- (c)3 and 4 only
- (d)1 and 3 only
Consider the program given below, in a block-
structured pseudo-language with lexical scoping
and
nesting of procedures permitted.
Program main;
Var ...
Procedure A1;
Var ...
Call A2;
End A1
Procedure A2;
Var ...
Procedure A21;
Var
Call A1;
End A21
Call A21;
End A2
Call A1;
End main.
Consider the calling chain: Main → A1 → A2 →
A21 → A1
The correct set of activation records along with
their access links is given by- (a)
- (c)
- (d)
Self-check
Question 1
How many activations does fib(5) have, and how deep does the control stack get?
Show answerHide
15 activations () and the stack holds at most 5 records of fib.
Question 2
Procedure at depth 2 calls procedure declared inside the depth-1 procedure (so has depth 2). How many access links does the caller follow to set the access link of 's record?
Show answerHide
link: the access link of (which points to 's record), which becomes the access link of .
Question 3
Name the field that lets a callee return to the right place, and the field that points to the caller's record.
Show answerHide
The return address (in the saved machine status), and the control link.
Question 4
Under dynamic scoping, a name used in f is not declared in f. Where is it looked up next?
Show answerHide
In the caller of f, then the caller's caller, and so on along the control links.
Revision card
One-screen revision
- Activation tree and control stack; record: parameters, return value, control link, access link, saved status, locals, temporaries.
- Static scoping: access links (lexical); dynamic scoping: control links (callers).
- Hops to a variable = depth of use minus depth of declaration.
fib(n): activations, depth .
References
- ·Aho, Lam, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (2nd edition), Chapter 7 Run-Time Environments (7.1 storage organization, 7.2 stack allocation of space: activation trees, activation records, calling sequences, access to nonlocal data on the stack: access links and displays)
- ·Cooper and Torczon, Engineering a Compiler, Chapter 6 The Procedure Abstraction (activation records, name spaces, access links and displays)
Verification
All animations on this page are runs of a small interpreter (scripts/cd_run.py) that keeps the stack of records, control links and access links. scripts/enum-checks/cd_run.py executes every program a second way, by translating it to Python whose nested functions give static scoping (and, for dynamic scoping, a stack of dictionaries searched from the top); the outputs agree on the programs of the page and on 3500 random programs. The number of activations of fib is recounted by the closed form 2 F(n+1) - 1. The layout and order of the fields of an activation record are one common textbook form; real systems differ, which is why the page names the fields and not their offsets. SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand or by a small script (token counts, FIRST and FOLLOW sets, LL(1) tables and moves, LR item sets, state counts and parses, operator-precedence values, attribute evaluation and printing schemes, activation-record counts and access links, parameter-passing results, three-address code, jumping and backpatched code, leaders and blocks, value numbering and DAGs, gen/kill and data flow sets, Sethi-Ullman labels and live ranges); long C programs were not re-read line by line (they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose); tables and animations are machine output and were read, not re-run; No other defect found. Status raised to self-checked; verified needs a textbook or human check.
last verified: never
My notes (saved in this browser)