Stack Allocation of Space
Stack Allocation of Space
Stack Allocation of Space
1 Activation Trees
2 Activation Records
3 Calling Sequences
4 Variable-Length Data on the Stack
5 Exercises for Section 7.2
Almost all compilers for languages that use procedures, functions, or methods as
units of user-defined actions manage at least part of their run-time memory as a
stack. Each time a procedure is called, space for its local variables is pushed onto a
1
stack, and when the procedure terminates, that space is popped off the stack. As we
shall see, this arrangement not only allows space to be shared by procedure calls
whose durations do not overlap in time, but it allows us to compile code for a
procedure in such a way that the relative addresses of its nonlocal variables are
always the same, regardless of the sequence of procedure calls.
1. Activation Trees
A Version of Quicksort
2
Stack Allocation of Space
Recursive procedure quicksort first decides if it needs to sort more than one
element of the array. Note that one element is always "sorted," so quicksort has
nothing to do in that case. If there are elements to sort, quicksort first calls
partition, which returns an index i to separate the low and high elements. These
two groups of elements are then sorted by two recursive calls to quicksort.
E x a m p l e 7 . 2 : One possible activation tree that completes the sequence of
calls and returns suggested in Fig. 7.3 is shown in Fig. 7.4. Functions are
represented by the first letters of their names. Remember that this tree is only one
possibility, since the arguments of subsequent calls, and also the number of calls
along any branch is influenced by the values returned by partition. •
The use of a run-time stack is enabled by several useful relationships between the
activation tree and the behavior of the program:
The sequence of procedure calls corresponds to a preorder traversal of the
activation tree.
The sequence of returns corresponds to a postorder traversal of the acti-vation
tree.
Suppose that control lies within a particular activation of some procedure,
corresponding to a node N of the activation tree. Then the activations that are
currently open (live) are those that correspond to node N and its ancestors. The
order in which these activations were called is the order in which they appear along
the path to N, starting at the root, and they will return in the reverse of that order.
2. Activation Records
3
Stack Allocation of Space
Procedure calls and returns are usually managed by a run-time stack called
the control stack. Each live activation has an activation record (sometimes called
a frame) on the control stack, with the root of the activation tree at the bottom, and
the entire sequence of activation records on the stack corresponding to the path in
the activation tree to the activation where control currently resides. The latter
activation has its record at the top of the stack.
Example 7 . 3 : If control is currently in the activation 0(2,3) of the tree of Fig. 7.4,
then the activation record for q(2,3) is at the top of the control stack. Just below is
the activation record for 0(1,3), the parent of 0(2,3) in the tree. Below that is the
activation record 0(1,9), and at the bottom is the activation record for m, the main
function and root of the activation tree.
We shall conventionally draw control stacks with the bottom of the stack higher
than the top, so the elements in an activation
record that appear lowest on the page are
actually closest to the top of the stack.
4
Stack Allocation of Space
A saved machine
status, with
information about the
state of the machine
just before the call to
the procedure. This
information typically
includes the return
address (value of the
program counter, to
which the called
procedure must
return) and the
contents of registers
that were used by the
calling procedure and
that must be restored
when the return
occurs.
An "access link" may be needed to locate data needed by the called proce-dure
but found elsewhere, e.g., in another activation record. Access links are discussed
in Section 7.3.5.
A control link,
Pointing to the activation record of the caller.
Space for the return value of the called function, if any. Again, not all called
procedures return a value, and if one does, we may prefer to place that value in a
register for efficiency.
The actual parameters used by the calling procedure. Commonly, these values are
not placed in the activation record but rather in registers, when possible, for greater
efficiency. However, we show a space for them to be completely general.
Example 7.4 : Figure 7.6 shows snapshots of the run-time stack as control flows
through the activation tree of Fig. 7.4. Dashed lines in the partial trees go to
activations that have ended. Since array a is global, space is allocated for it before
execution begins with an activation of procedure main, as shown in Fig. 7.6(a).
When control reaches the first call in the body of main, procedure r is activated,
and its activation record is pushed onto the stack (Fig. 7.6(b)). The activation
record for r contains space for local variable i. Recall that the top of stack is at the
bottom of diagrams. When control returns from this activation, its record is
popped, leaving just the record for main on the stack.
5
Stack Allocation of Space
Control then reaches the call to q (quicksort) with actual parameters 1 and 9, and
an activation record for this call is placed on the top of the stack, as in Fig. 7.6(c).
The activation record for q contains space for the parameters m and n and the local
variable i, following the general layout in Fig. 7.5. Notice that space once used by
the call of r is reused on the stack. No trace of data local to r will be available
to q(l, 9). When q(l, 9) returns, the stack again has only the activation record
for main.
Several activations occur between the last two snapshots in Fig. 7.6. A recursive
call to g(l,3) was made. Activations p ( l , 3 ) and q(l,0) have begun and ended
during the lifetime of q(l, 3), leaving the activation record for q(l, 3) on top (Fig.
7.6(d)). Notice that when a procedure is recursive, it is normal to have several of
its activation records on the stack at the same time. •
3. Calling Sequences
Procedure calls are implemented by what are known as calling sequences, which
consists of code that allocates an activation record on the stack and enters
information into its fields. A return sequence is similar code to restore the state of
the machine so the calling procedure can continue its execution after the call.
Calling sequences and the layout of activation records may differ greatly, even
among implementations of the same language. The code in a calling se-quence is
often divided between the calling procedure (the "caller") and the procedure it calls
(the "callee"). There is no exact division of run-time tasks between caller and
callee; the source language, the target machine, and the op-erating system impose
requirements that may favor one solution over another. In general, if a procedure is
called from n different points, then the portion of the calling sequence assigned to
the caller is generated n times. However, the portion assigned to the callee is
generated only once. Hence, it is desirable to put as much of the calling sequence
into the callee as possible — whatever the callee can be relied upon to know. We
shall see, however, that the callee cannot know everything.
When designing calling sequences and the layout of activation records, the
following principles are helpful:
1. Values communicated between caller and callee are generally placed at the
beginning of the callee's activation record, so they are as close as possible to the
caller's activation record. The motivation is that the caller can compute the values
of the actual parameters of the call and place them on top of its own activation
record, without having to create the entire activation record of the callee, or even to
know the layout of that record.
6
Stack Allocation of Space
2. Moreover, it
allows for the use of
procedures that do not
always take the same
number or type of
arguments, such as C's
p r i n t f function. The
callee knows where to
place the return value,
relative to its own
activation record, while
however many
arguments are present
will appear sequentially
below that place on the stack.
3. Fixed-length items are generally placed in the middle. From Fig. 7.5, such
items typically include the control link, the access link, and the machine status
fields. If exactly the same components of the machine status are saved for each
call, then the same code can do the saving and restoring for each. Moreover, if we
standardize the machine's status information, then programs such as debuggers will
have an easier time deciphering the stack contents if an error occurs.
Items whose size may not be known early enough are placed at the end of the
activation record. Most local variables have a fixed length, which can be
determined by the compiler by examining the type of the variable. However, some
local variables have a size that cannot be determined until the program executes;
the most common example is a dynamically sized array, where the value of one of
the callee's parameters determines the length of the array. Moreover, the amount of
space needed for tempo-raries usually depends on how successful the code-
generation phase is in keeping temporaries in registers. Thus, while the space
needed for tem-poraries is eventually known to the compiler, it may not be known
when the intermediate code is first generated.
4. We must locate the top-of-stack pointer judiciously. A common approach is to
have it point to the end of the fixed-length fields in the activation record. Fixed-
length data can then be accessed by fixed offsets, known to the intermediate-code
generator, relative to the top-of-stack pointer. A consequence of this approach is
that variable-length fields in the activation records are actually "above" the top-of-
stack. Their offsets need to be calculated at run time, but they too can be accessed
from the top-of-stack pointer, by using a positive offset.
An example of how caller and callee might cooperate in managing the stack is
suggested by Fig. 7.7. A register topsp points to the end of the machine-status field
in the current top activation record. This position within the callee's activation
record is known to the caller, so the caller can be made responsible for setting
topsp before control is passed to the callee. The calling sequence and its division
between caller and callee is as follows:
7
Stack Allocation of Space
1. The caller evaluates the actual parameters.
The caller stores a return address and the old value of topsp into the callee's
activation record. The caller then increments topsp to the po-sition shown in Fig.
7.7. That is, topsp is moved past the caller's local data and temporaries and the
callee's parameters and status fields.
The callee saves the register values and other status information.
1. The callee places the return value next to the parameters, as in Fig. 7.5.
2. Using information in the machine-status field, the callee restores topsp and
other registers, and then branches to the return address that the caller placed in the
status field.
3. Although topsp has been decremented, the caller knows where the return
value is, relative to the current value of topsp; the caller therefore may use that
value.
The above calling and return sequences allow the number of arguments of the
called procedure to vary from call to call (e.g., as in C's p r i n t f function). Note
that at compile time, the target code of the caller knows the number and types of
arguments it is supplying to the callee. Hence the caller knows the size of the
parameter area. The target code of the callee, however, must be prepared to handle
other calls as well, so it waits until it is called and then examines the parameter
field. Using the organization of Fig. 7.7, information describing the parameters
must be placed next to the status field, so the callee can find it. For example, in the
p r i n t f function of C, the first argument describes the remaining arguments, so
once the first argument has been located, the caller can find whatever other
arguments there are.
9
Stack Allocation of Space
q's activation record. This length is known at compile time to the caller, although it
may depend on the caller, if the number of parameters can vary across calls to q.
10
Stack Allocation of Space