CS 3723
 Programming Languages 
   Activation Records  
  Functions & Recursion  


Activation Records in General. If one properly uses a stack at runtime to support function calls, this approach implements recursion "for free", without any additional effort. At run time, each called function is supported by an activation record, which is storage on the stack dedicated to that particular call of that specific function.

The MIPS architecture gives minimal hardware support for recursive functions, so everything must be done "by hand".

The activation record needs to provide storage (at runtime) for at least the following items:

  • The return address (address from which the function was called). A function might be called from any number of places, including from within itself (recursive call) from any number of places.

  • The value returned by the function (which might be a pointer).

  • Locations to hold values of actual parameters fed to the function. (These can be expressions.) After the function is called, these locations function as the formal parameters. They behave like local variables.

  • Space for (non-static, automatic) local variables used by the function.

The work below uses a running example of a function of two parameters: a "raise-to-a-power" function. This has two parameters and doesn't require any local variables besides registers, so the activation record will hold 4 items, as shown in the illustration below. (The order of these items was an arbitrary choice.) This diagram shows the stack before the function is called. The "stack pointer", denoted by $sp, points to one location beyond the top of the stack, that is, it points to the first available location. Since we are working with doubles, we will only consider 8-byte items on the stack, although the return address takes up only 4 bytes.

[The diagrams show the special MIPS "stack pointer" $sp used to access the MIPS system stack. This is what one would normally use. In order to avoid any possible problems with the official stack pointer, I chose to allocate storage on a user-defined stack pointer, using the register $s2 for it. We have been using $s1 to save the return address of our MIPS function main.]

In the diagram below, the stack pointer "old $sp" might be pointing to the bottom of the stack, with nothing below it. Alternatively, which is below will be the mostly recently called function that is still active (has not yet returned). Assume that the next thing to do is to call a function (in fact a pow or rtp function). The old $sp calculates the two actual parameters, and inserts them into the stack, into locations that will be part of the activation record of the function to be called. These locations are shown in red below.

Stack for Activation Records: Before Function Is Called,
First Insert Parameter Values
                   STACK
                |              |                    |  Next (recursive)
                |--------------|                    |  Activation
                |              |                    |  Record
             ===|==============|===          -------+
   old -24 ---> |  Insert P2   |                    |
                |--------------|                    |  Space for Next
   old -16 ---> |  Insert P1   |                    |  Activation
                |--------------|                    |  Record
   old -8  ---> |              |                    |  (or first one)
                |--------------|                    |  
   old $sp ---> |              |                    |
             ===|==============|===          -------+
   old +8  ---> |  Parameter 2 |                    |
                |--------------|                    |  Previous (current)
   old +16 ---> |  Parameter 1 |                    |  Activation
                |--------------|                    |  Record
   old +24 ---> | Return value |                    |  (if present)
                |--------------|                    |  
   old +32 ---> | Return addr  |                    |
             ===|==============|===         --------+

After the function is called (control is passed to the function), the function first allocated an activation record on the stack, in this example by decrementing the $sp by 32 ($sp = $sp - 32). (This stack is growing toward smaller memory locations.) The function can now "find" the values of actual parameters at known offsets. These locations become the formal parameters for the function. First the function inserts its return address into the activation record (MIPS gives access to this return address). Then the function does its computations, ending with a value to return, which is inserted into the activation record. During its execution, the function may have called other functions, and each of those will push additional activation records onto the stack. By the time this function returns, all other called functions will have returned, so there is nothing above the current activation record. Finally, the called function deletes (deallocates) its activation record by incrementing $sp by 32. The function then returns control to the place from which is was called.

[It's important to realize that there is only one register $sp. "Old" and "new" above refer to different values stored in the register at different times.]

Stack for Activation Records: Function Executes,
Insert RetAdd, Calculate and Insert Return Value
                   STACK
                |              |                    |  Next (recursive)
                |--------------|                    |  Activation
                |              |  <--- new $sp      |  Record
             ===|==============|===          -------+
   old -24 ---> |  Parameter 2 |  <--- new +8       |
                |--------------|                    |  New (current)
   old -16 ---> |  Parameter 1 |  <--- new +16      |  Activation
                |--------------|                    |  Record
   old -8  ---> | Insert RetVal|  <--- new +24      |  
                |--------------|                    |  
   old $sp ---> | Insert RetAdd|  <--- new +32      |
             ===|==============|===          -------+
   old +8  ---> |  Parameter 2 |                    |
                |--------------|                    |  Old (previous)
   old +16 ---> |  Parameter 1 |                    |  Activation
                |--------------|                    |  Record
   old +24 ---> | Return value |                    |  (if present)
                |--------------|                    |  
   old +32 ---> | Return addr  |                    |
             ===|==============|===         --------+

Now, as shown below, the calling function can access the value returned by the function that it called. The calling function will usually have its own activation record (as shown), but it is the main function, it will have no activation record.

Stack for Activation Records: After Function Returns,
Retrieve Return Value
                   STACK
                |              |                    |  Next (recursive)
                |--------------|                    |  Activation
                |              |   <--- new $sp     |  Record
             ===|==============|===          -------+
   old -24 ---> |  Parameter 2 |   <--- new +8      |
                |--------------|                    |  Old deallocated
   old -16 ---> |  Parameter 1 |   <--- new +16     |  Activation
                |--------------|                    |  Record
   old -8  ---> |Retrieve RetV |   <--- new +24     |  
                |--------------|                    |  
   old $sp ---> |  Old RetAdd  |   <--- new +32     |
             ===|==============|===          -------+
   old +8  ---> |  Parameter 2 |                    |
                |--------------|                    |  Current (restored)
   old +16 ---> |  Parameter 1 |                    |  Activation
                |--------------|                    |  Record
   old +24 ---> | Return value |                    |  (if present)
                |--------------|                    |  
   old +32 ---> | Return addr  |                    |
             ===|==============|===         --------+


Our Specific Example: c = a ^ b. We will start with a simple pow function: MIPS code pow0.s below. Then we will rewrite it as a recursive function using activation records as illustrated above. This will be MIPS code powr.s below. Finally, in the next section we'll implement a much more complicated and efficient recursive raise-to-a-power algorithm, where the recursion is not trivial.

I'm thinking in terms of taking input variables a and b, calculating a^b, and leaving the result in a variable c. However, in order to keep things simpler and shorter, I'm not using any memory locations below for variable, but I'm only using registers. How there double registers are used is shown in the first table on the left below.

The MIPS program on the left below shows a very simple loop to calculate a^b, using the register conventions. The MIPS program on the right uses recursion instead of a loop. This program uses a single sequence of recursive calls, so that at the end we can see all the activation records that were used. (None of the storage was overwritten during execution. In more complex programs, the stack of activation records might grow and shrink many times.) The diagram on the left below shows the 11 activation records used to calculate 7^10, at levels 0 (the first one), through 10.

Variables to Registers
# mapping of variables to registers:
#        $f0  (used for input)
#    a = $f2
#    b = $f4
#    c = $f6
#    0 = $f8
#    1 = $f10
#        $f12 (used for output)
#        $f14 (for return value)
#    2 = $f16
#        $f18 (temporary)

a^b, with simple loop
### pow0.s: calculate c = a^b
main:   addu    $s7, $ra, $zero
# addr of M: constants, vars, temps
        la      $s1, M
# read a as double
        li      $v0, 7
        syscall
        mov.d   $f2, $f0
# read b as double
        li      $v0, 7
        syscall
        mov.d   $f4, $f0
### end reading, start processing
        l.d     $f8, 0($s1)  # 0.0
        l.d     $f10, 8($s1) # 1.0
        l.d     $f6, 8($s1)  # c = 1
WhileStart0:
        c.eq.d  $f4, $f8   # if b==0
        bc1t    WhileEnd0

        mul.d   $f6, $f6, $f2 #c=c*a
        sub.d   $f4, $f4, $f10#b=b-1
        j       WhileStart0
WhileEnd0:
### return to system
        addu    $ra, $s7, $zero
        jr      $ra
        .data
        .align  3
M:      .double 0.,1.
Blank:  .asciiz " "
NewL:   .asciiz "\n"

Level Activation Records for 7^10
P 2P 1 Return ValueRet Addr
10071 4194656
9177 4194656
82749 4194656
737343 4194656
6472401 4194656
55716807 4194656
467117649 4194656
377823543 4194656
2875764801 4194656
19740353607 4194656
01072824752494194396

 
a^b, recursive
### powr.s: act. records and recursion
main:   addu    $s7, $ra, $zero
# addr of S, M: stack, constants
        la      $s2, S
        la      $s1, M
### start of compiled code
# 2 constants
        l.d     $f8,   0($s1)  # 0.0
        l.d     $f10,  8($s1)  # 1.0
# read a as double, store as param1
        li      $v0, 7
        syscall
        s.d     $f0, -16($s2)
# read b as double, store as param2
        li      $v0, 7
        syscall
        s.d     $f0, -24($s2)
# call function
        jal     pow
        l.d     $f14, -8($s2) # ret val
# print return value
        li      $v0, 3
        l.d     $f12, -8($s1)
        syscall
# print NewL as ASCII char
        li      $v0, 4
        la      $a0, NewL
        syscall
# return to system
        addu    $ra, $s7, $zero
        jr      $ra
########## body of pow function #######
pow:
# push activation record
        addi    $s2, $s2, -32
# save return address on stack
        sw      $ra, 32($s2)
# load parameters
        l.d     $f2, 16($s2) # a
        l.d     $f4, 8($s2)  # b
# main part of function
        l.d     $f6, 8($s1)  # c = 1
# check for end to recursion
        c.eq.d  $f4, $f8     # if b==0
        bc1t    RecursEnd
# get ready for recursive call
        sub.d   $f4, $f4, $f10 # b=b-1
# insert 1st and 2nd parameters
        s.d     $f2, -16($s2)  # a
        s.d     $f4, -24($s2)  # b-1
# call function (recursive)
        jal     pow
        l.d     $f14, -8($s2) # ret val
# multiply value returned by a
        mul.d   $f6, $f14, $f2  # c=c*a
RecursEnd:
# insert return value
        s.d     $f6, 24($s2)
# restore return address from stack
        lw      $ra, 32($s2)
# pop activation record
        addi    $s2, $s2, 32
# return
        jr      $ra
######### end of pow function #########
        .data
        .align  3
T:      .space  1000
S:
M:      .double 0.,1.
Blank:  .asciiz " "
NewL:   .asciiz "\n"
Tab:    .asciiz "\t"


Fancy Exponentiation. This example uses a more complex raise-to-a-power algorithm, given by the equations below. Here the recursion is essential, unless one completely rewrites the algorithm. However, again in this case there is only a single tower of recursive calls, so that a dump at the end again shows all the activations records. At the end are two tables showing them for two example runs.

a^b, Fancy Algorithm
### rtp.s: use activation records
main:   addu    $s7, $ra, $zero
# addr of S, M: stack, constants
        la      $s2, S
        la      $s1, M
# 3 constants
        l.d     $f8,   0($s1) # 0.0
        l.d     $f10,  8($s1) # 1.0
        l.d     $f16, 16($s1) # 2.0
# read a as double, store as param1
        li      $v0, 7
        syscall
        s.d     $f0, -16($s2)
# read b as double, store as param2
        li      $v0, 7
        syscall
        s.d     $f0, -24($s2)
# call function
        jal     rtp
# get return value
        l.d     $f14, -8($s2)
# print return value
        li      $v0, 3
        l.d     $f12, -8($s1)
        syscall
# print NewL as ASCII char
        li      $v0, 4
        la      $a0, NewL
        syscall
# return to system
        addu    $ra, $s7, $zero
        jr      $ra
######### body of rtp function ####
rtp:
# push activation record
        addi    $s2, $s2, -32
# save return address on stack
        sw      $ra, 32($s2)
# load parameters
        l.d     $f2, 16($s2) # a
        l.d     $f4, 8($s2)  # b
# start main part of function
        l.d     $f6, 8($s1)  # c=1
# check for end to recursion
        c.eq.d  $f4, $f8 # if b==0
        bc1t    RecursEnd
# next check for even b
        div.d   $f18, $f4, $f16  # t1 = b/2
        trunc.w.d $f18, $f18  # trunc(b)
        cvt.d.w $f18, $f18    # to double
        mul.d   $f18, $f18, $f16 # mult by 2
        sub.d   $f18, $f4, $f18
        c.eq.d  $f18, $f8  # res comp to 0
        bc1f    BisOdd
# here b is non-zero and even
  # get ready for recursive call
        div.d   $f4, $f4, $f16 # b = b / 2
  # insert 1st and 2nd parameters
        s.d     $f2, -16($s2)  # a
        s.d     $f4, -24($s2)  # b/2
  # call function (recursive)
        jal     rtp
        l.d     $f14, -8($s2) # return val
  # square return value, result in c
        mul.d   $f6, $f14, $f14  # c = ret^2
        b       RecursEnd
BisOdd:
# here b is non-zero and odd
  # get ready for recursive call
        sub.d   $f4, $f4, $f10 # b = b - 1
        div.d   $f4, $f4, $f16 # b = b / 2
  # insert 1st and 2nd parameters
        s.d     $f2, -16($s2)  # a
        s.d     $f4, -24($s2)  # b/2
  # call function (recursive)
        jal     rtp
        l.d     $f14, -8($s2) # return val
  # square return value, result in c
        mul.d   $f6, $f14, $f14  # c = ret^2
  # multiply by a
        mul.d   $f6, $f6, $f2
RecursEnd:
# insert return value
        s.d     $f6, 24($s2)
# restore return address from stack
        lw      $ra, 32($s2)
# pop activation record
        addi    $s2, $s2, 32
# return
        jr      $ra
######### end of pow function ###########
        .data
        .align  3
T:      .space  1000
S:
M:      .double 0.,1.,2.
Blank:  .asciiz " "
NewL:   .asciiz "\n"

Level Activation Records for 2^973
P 2P 1 Return ValueRet Addr
100 21 4194720
9 1 22 4194720
8 3 28 4194720
7 7 2128 4194720
6 15 232768 4194688
5 30 21073741824 4194688
4 60 21.15292150e+18 4194720
3 12122.65845599e+36 4194720
2 24321.41347765e+73 4194688
1 48621.99791907e+1464194720
0 97327.98336123e+2924194400

LActivation Records for φ^57 (Return Value)/sqrt(5)
P
2
P
1
Return ValueReturn
Address
60φ1 4194720 0.4472135954999579≐F(0)=0
51φ1.61803398874989494194720 0.7236067977499789≐F(1)=1
43φ4.236067977499789814194720 1.8944271909999157≐F(3)=2
37φ29.03444185374863554194688 12.984597134749391≐F(7)=13
214φ842.9988137587105264194688 377.0005305032323≐F(14)=377
128φ710646.9999985931214194720 317811.0000006294≐F(28)=
317811
057φ817138163596.0007324194400 365435296162.0003≐F(57)=
365435296162


Revision date: 2013-11-20. (Please use ISO 8601, the International Standard.)