G27
Self-checkedTier 1 12 min readcd.runtime-activation-records

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 PP are the activations started by PP, from left to right in the order of the calls. If control returns from PP 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):

FieldContent
actual parametersthe values or addresses passed by the caller (often first, in registers on modern machines)
returned valuewhere the callee puts its result (often a register)
control linkpoints to the activation record of the caller (the dynamic link)
access linkpoints to the record of the lexically enclosing procedure (needed for nested procedures)
saved machine statusthe return address and the saved registers, including the old frame pointer
local datathe local variables
temporariesthe 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 kk nesting levels out, follow kk 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 dcd_c calls a procedure QQ declared inside a procedure of depth dpd_p: the new record's access link is found by following dc−dpd_c - d_p access links from the caller (and if QQ is declared inside the caller, dc=dpd_c = d_p 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 typed

static (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();
control stack (top first)
#0 main (outermost)
x = 10
output (nothing yet)

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.

0 / 6
computed by script: the program is executed step by step on a stack of activation records. python3 scripts/gen-cd-run-traces.py

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 typed

dynamic 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();
control stack (top first)
#0 main (outermost)
x = 10
output (nothing yet)

Step 0

The same program, but now a non-local name is looked up in the callers, innermost first.

0 / 6
computed by script: the program is executed step by step on a stack of activation records. python3 scripts/gen-cd-run-traces.py

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 typed

static 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);
control stack (top first)
#0 main (outermost)
a = 1
output (nothing yet)

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.

0 / 6
computed by script: the program is executed step by step on a stack of activation records. python3 scripts/gen-cd-run-traces.py

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 typed

static 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;
control stack (top first)
#0 main (outermost)
r = 1
output (nothing yet)

Step 0

fact(n) multiplies the global r by n after the recursive call returns, so r becomes 1 * 2 * 3.

0 / 14
computed by script: the program is executed step by step on a stack of activation records. python3 scripts/gen-cd-run-traces.py

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 typed

Activation 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.

    control stack (oldest first) (top at the right)
    empty
    empty
    action start

    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.

    0 / 18
    computed by script: the program is executed step by step on a stack of activation records. python3 scripts/gen-cd-run-traces.py

    Worked examples

    EX 1Static or dynamic scope?

    Easy
    var 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

    Easy

    fact(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 nn activations of fact, so the stack has n+1n + 1 records including main, or nn 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

    Medium

    The tree of fib(4) has 9 nodes (the animation). In general with F(0)=0F(0) = 0, F(1)=1F(1) = 1 the number of activations of fib(n) is 2F(n+1)−12F(n+1) - 1: for n=4n = 4: 2⋅5−1=92 \cdot 5 - 1 = 9; for n=5n = 5: 2⋅8−1=152 \cdot 8 - 1 = 15; for n=6n = 6: 2⋅13−1=252 \cdot 13 - 1 = 25. The maximum stack depth is nn calls for n≥1n \ge 1 (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 n≤7n \le 7.) 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?

    Medium

    Depths: 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 (3−03 - 0). (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 3−1=23 - 1 = 2 links from the caller's record, reaching the record of outer. (d) outer calls inner: the caller is outer itself, depth 1, dp=1d_p = 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 2F(n+1)−12F(n+1) - 1 activations and stack depth nn.
    • Display: array by depth for constant-time non-local access.

    Traps and GATE patterns

    Connections

    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

    GATE 2008 · Set 1 · MCQ · 1 markQ40 · page 25 of the book
    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
    GATE 2008 · MCQ · 1 markQ12 · page 253 of the book
    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
    GATE 2014 · MCQQ20 · page 283 of the book
    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
    GATE 2012 · MCQQ24 · page 284 of the book
    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 answer

    15 activations (2F(6)−1=152F(6) - 1 = 15) and the stack holds at most 5 records of fib.

    Question 2

    Procedure PP at depth 2 calls procedure QQ declared inside the depth-1 procedure RR (so QQ has depth 2). How many access links does the caller follow to set the access link of QQ's record?

    Show answer

    dc−dp=2−1=1d_c - d_p = 2 - 1 = 1 link: the access link of PP (which points to RR's record), which becomes the access link of QQ.

    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 answer

    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 answer

    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): 2F(n+1)−12F(n+1) - 1 activations, depth nn.

    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