CS302 Spr'99 Lecture Notes
Lecture 8
Runtime Environments

$\bullet$ Data Representation (covered in CS301)

$\bullet$ Storage Organization

$\bullet$ Procedures (& Stacks) - Activation Records

- Access to non-local names

- Parameter Passing

- Procedures as first-class values

$\bullet$ Storage Allocation

- Static/Stack/Heap

- Garbage Collection

Storage Organization

$\bullet$ Subdivide machine address space by function, access, allocation.

$\bullet$ Typical organization (Unix)
$\infty$\space ---------------
\vert \vert
\vert STACK \v...
...vert {\rm Read-only (in object-file)}
\vert \vert
0 --------------- \end{code}

Storage Classes

Static Data : Permanent Lifetimes

$\bullet$ Global variables and constants.

$\bullet$ Allows fixed address to be compiled into code.

$\bullet$ No runtime management costs.

$\bullet$ Original FORTRAN (no recursion) used static activation records.

Stack Data : Nested Lifetimes

$\bullet$ Allocation/deallocation is cheap (just adjust stack pointer).

$\bullet$ Most architectures support cheap sp-based addressing.

$\bullet$ Good locality for VM systems, caches.

$\bullet$ C, Algol family (including PCAT) use stack for activation records.

Heap Data : Arbitrary Lifetimes

$\bullet$ Requires explicit allocation and (dangerous) explicit deallocation or garbage collection.

$\bullet$ Lisp, ML, many interpreted languages need heap for activation records, which have non-nested lifetimes.

Procedures and Activations

$\bullet$ A procedure definition associates a name with a procedure body and associated formal parameters.

$\bullet$ A procedure activation is created during execution when the procedure is called (with actual parameters.

$\bullet$ Activations have lifetimes: the time between execution of the first and last statements in the procedure.

$\bullet$ Activations are either nested (e.g., a,b) or non-overlapping (e.g., b,c):

\begin{code}a() \{ a
b(); / \\
c(); / \\
\} b c
c() \{ \vert
d(); d

$\bullet$ Procedure f is recursive if two or more activations of f are nested. (Note that f need not call itself directly.)

Activation Trees - Example
\begin{code}1 int a[9] = \{10,32,567,-1,789,3,18,0,-51\};
\par 2 main() \{
3 qui...
21 quicksort(m,i-1);
22 quicksort(i+1,n);
23 \};
24 \}\end{code}
Activation Trees - Example (Cont.)

(A.k.a. ``call tree'')

Execution corresponds to depth-first traversal of tree.

Identify activations by name of function (e = exchange, p = partition, q = quicksort) and actual argument values.


Control stack keeps track of live activations; it contains activations along the path from root to ``current'' activation (see example above). Activation Records (a.k.a. ``Frames'')

Contain data associated with a particular activation of a procedure:

$\bullet$ Actual parameters (maybe in registers).

$\bullet$ Return value (maybe in register).

$\bullet$ Local variables, including temporaries (perhaps containing saved registers).

$\bullet$ Access (or Static) link = pointer to statically enclosing activation record (to access non-local variables, if needed).

Also convenient to include control information about the calling procedure:

$\bullet$ Return address in caller.

$\bullet$ Control (or Dynamic) Link = pointer to caller's activation record (if needed).

Use fixed layout (as far as possible) for activation records, so contents can be referenced as:
\begin{code}(frame pointer) + (statically-known offset)\end{code}
Most architectures perform such references efficiently.

Activation Record Lifetimes

The lifetime of an activation record corresponds to the longest lifetime of anything contained in it.

The lifetimes of all contents begin when the activation begins (i.e., when the procedure is called).

The lifetime of control information ends when the activation's lifetime ends (i.e., when the procedure returns).

For most conventional languages, including C, Pascal, PCAT, etc., the lifetimes of local data are also contained within the activation's lifetime.

Thus, since activation lifetimes behave in a stack-like manner, we can allocate and deallocate activation records on a stack.

(For some languages, like Lisp, ML, etc., data lifetimes don't obey these rules; such data cannot be stack-allocated. More later.)

We don't ``push'' and ``pop'' whole activation records; instead, we build and destroy them in pieces.

Calling Sequence

= Sequence of steps that, taken together, build and destroy activation records.

Typically divide into:

$\bullet$ Call sequence - performed by caller.

$\bullet$ Entry sequence - performed by callee.

$\bullet$ Exit sequence - performed by either/both.

Key problem: caller and callee know very little about each other.

$\bullet$ Single callee may have many callers.

$\bullet$ Caller may not callee statically (e.g., C function pointers).

$\bullet$ Caller shouldn't need to know details of callee's implementation (may be compiled separately).

Thus, caller and callee must blindly cooperate via a set of conventions.

Typical Activation Records (e.g., X86)


Typical Calling Sequence (e.g., X86)

1. Caller pushes caller-save registers.

2. Caller pushes arguments (in reverse order) and static link (if any).

3. Caller executes call instruction, which pushes pc (details vary according to machine architecture).

4. Callee pushes fp as dynamic link and sets fp = sp.

5. Callee adjusts sp to make room for fixed-size locals.

6. Callee pushes callee-save registers.

7. Callee can adjust sp dynamically during procedure execution to allocate dynamically-sized data on the stack.

Typical Return Sequence (e.g., X86)

1. Callee restores callee-save registers.

2. Callee resets sp = fp, thereby popping locals and any dynamically-sized data.

3. Callee pops dynamic link into fp.

4. Callee does a return, which pops return address into pc.

5. Caller pops static link and args.

6. Caller restores caller-save registers.

Common variations

$\bullet$ Hardware instructions do more or less.

$\bullet$ Arguments may be passed in registers. (Return value almost always is.)

$\bullet$ If everything is fixed-size, there's no real need for a frame pointer, or a dynamic link - but still handy.

Typical Sequence - Example (Quicksort)

Access to Non-local Variables

$\bullet$ In Pascal, Ada, PCAT, etc., we can nest procedure declarations inside other procedure declarations. (Cannot do this in C!)

$\bullet$ Parameters and local variables of outer procedures are visible within inner procedures.

$\bullet$ More precisely, the variables associated with the most recent still-live activation of the outer procedure are visible within inner procedures.

$\bullet$ References to these variables must be compiled to code that can locate the corresponding values at runtime.

$\bullet$ Any variables that are referenced non-locally must be stored in activation records in memory; they cannot be held just in registers.

$\bullet$ Can analyze program to tell which variables are so referenced (or, as we'll do for PCAT, just assume the worst).

$\bullet$ In most languages, if procedure f is declared inside g, then f can only appear as descendent of g in the activation tree. This allows us to stack-allocate activation records, and still guarantee that non-local variables will still exist when they are needed.

Example: Quicksort Revisited (in PCAT)
1 PROGRAM (* $1$\space *) IS
29 END;
31 sort(9,a);
32 sort(99,b);
33 END;
Access Links for Non-local Variables

$\bullet$ Each activation record can include an access link (a.k.a. static link) pointing to the statically enclosing activation record.

$\bullet$ If p is nested immediately inside q, then the access link in p's activation record points to the activation record for the most recent live activation of q.

$\bullet$ Non-local v is found by following one or more access links to the activation record that contains v, and then taking the appropriate offset within that record.

$\bullet$ If v is declared locally at depth nv, and accessed in p at depth np, then the number of access links to follow is just (np - nv).

$\bullet$ In general, variable locations can be described as a pair:

(number of access links to follow,
offset within resulting activation record)

Local variable at offset z is represented as (0,z).

These locations are fully known at compile time.

$\bullet$ Access links are initialized during the calling sequence; usually calculated by the caller and passed as a ``hidden'' first argument. Example with Static Links: Quicksort


Note: A static link always points into an activation record at the same place as the frame pointer for that activation.

Another Addressing Example
\begin{code}PROCEDURE foo IS
VAR ...
If the body of foo is at scope level
n, then:

$\bullet$ Symbol foo is at scope level n-1
$\bullet$ Symbols x,f are at scope level n
$\bullet$ Symbols a,y,g are at scope level n+1
$\bullet$ Symbols b,z are at scope level n+2
Make sure you can calculate the addressing code for each mention of each symbol.

SPARC Runtime Environment

$\bullet$ Somewhat different from ``generic'' architecture.

$\bullet$ Procedure frame contains procedure's own locals and arguments passed to called routines.

$\bullet$ Frame is normally fixed-size.

$\bullet$ Most register saving (including old fp,sp,pc) is taken care of by register windows.

$\bullet$ Arguments are normally passed in registers (up to 6); we won't do this for PCAT, however.

\begin{code}Register window shifts:
\par . .
. <-- restore .
. .

$\bullet$ None the less, extra space must be reserved in frame for registers and arguments (due to system and C conventions). SPARC/C Language Frame Layout

\psfig{figure=sparc.eps,height=8in,width=7.0in} SPARC/C Calling Sequence

1. Caller puts arguments in %o0, %o1, $\ldots$, %o5. (Arguments beyond 6th word are put in argument build area of frame.)

2. Caller executes call instruction, which jumps to label and stores old pc in %o7.

3. Callee executes save instruction, which shifts register window and allocates frame. (Small leaf procedures can avoid this.)

4. Callee optionally stores arguments into argument build area of caller's frame.

Return Sequence

1. Callee puts (integer) return value in %i0.

2. Callee executes restore instruction, which resets register window and deallocates frame.

3. Callee executes ret instruction, which jumps to %i7+8 (just past delay slot in caller).

Floating Point arguments are spread over pairs of registers; f.p. return values are in %f0; all f.p. registers are caller-save.

Structures (may be multiple words)

Arguments are spread over multiple registers; return values are written to space allocated by the caller, and pointed to by %sp+64.

Andrew P. Tolmach