In this lecture, I gave the following algorithm for exponentiation, in a C-like pseudo-code:

exp(x,y) { if (y == 0) return 1.0; /* 0^{0}indefinite, but returns 1 */ if (y odd) return x * exp(x^{2},^{(y-1)}/_{2}); return exp(x^{2},^{y}/_{2}); }

I proved this algorithm correct (modulo overflows) using induction on the
length of the exponent in bits. The precondition is that `y` is
non-negative.

Base case: `|y| = 1`. So `y = 0` or `y = 1` holds.
First case: `y = 0`.
`exp(x,0)` returns `1.0`, which is what we wanted.
Next case: `y = 1`. For `exp(x,1)`, the code
returns `x * exp(x ^{2},0) = x`, which is what we wanted.
The base case is correct.

Induction step: Assume that `exp(x,y)` returns the correct value
(`x ^{y}`)
when

y = 2 * z + ywhere_{0}

Now we note that `z` is a `k` bit number.
Thus, `exp(x ^{2},z)` will be computed
correctly, yielding

x * exp(xwhich is what we wanted. Next, suppose^{2},^{(y-1)}/_{2}) = x * exp(x^{2},z) = x * x^{2z}= x^{2z+1}= x^{y}

exp(xwhich is the correct answer.^{2},^{y}/_{2}) = exp(x^{2},z) = x^{2z}= x^{y}

This completes the induction, so `exp(x,y)` will return the
correct value for all `x` and `y` where `y` is
non-negative, and the computation does not overflow (the abstract
algorithm never overflows, but implementations may).

To implement the recursive version of `exp`, we need to be able to do
the recursion. Because the `$ra`
register is modified by the `jal`
instruction, each recursive instance of `exp` must save its `$ra`
prior to
recursively calling itself with modified parameters. This is saved in
the stack within a stack frame.

First, the stack pointer. The stack pointer (`$sp`) contains the
address of
the first free word of memory. Above the stack pointer (addresses greater
than the stack pointer) are memory locations used by other routines.
Below the stack pointer are memory that has not yet been claimed. To allocate
temporary memory for a function, the stack pointer is simply decremented
the appropriate amount.

The frame pointer is another register which is used to make it easy to
refer to the stack frame's contents and to arguments to the function
(beyond those that are in `$a0` -- `$a3`).
Different compilers use different
conventions: the C compiler from SGI/MIPS does not use a frame pointer,
but the GNU C compiler does. In any case, always treat `$fp`
as a caller-saved
register, and code generate by either compiler will work.

Compilers insert *prologue* and *epilogue* instructions
before and after the assembly code that correspond to the original C or
C++ source code. The prologue's job is to allocate stack space and
save whichever registers need to be saved, and the epilogue's job is to
restore those registers to their previous values and deallocate stack
space. One example prologue/epilogue was given in class:

fn: sub $sp,$sp,8 sw $fp,4($sp) add $fp,$sp,8 sw $ra,0($fp) ... body of program ... lw $ra,0($fp) lw $fp,4($sp) add $sp,$sp,8 jr $raAnother is the following, which works with

fn: sub $sp,$sp,8 sw $fp,4($sp) add $fp,$sp,8 sw $ra,0($fp) ... body of program ... lw $ra,0($fp) move $t0,$fp lw $fp,-4($fp) move $sp,$t0 jr $raNote that we could save an instruction in the epilogue:

lw $ra,0($fp) move $sp,$fp lw $fp,-4($fp) jr $rabut this is potentially unsafe if the program is interrupted by a signal between the second and third instruction in the epilogue: interrupts assume that anything below the stack is unused (and therefore could be allocated and overwritten), but this code sequence relies on

Space for saving the `$t` and `$s` scratch registers and
for holding local variables are also kept in the stack frame. Instead
of 8 bytes (2 words), the prologue and epilogue code would just allocate
enough space for everything.

[ CSE home | CSE talks | bsy's home page | webster i/f | yahoo | lycos | altavista | pgp key svr | spam | commerce ]

bsy@cse.ucsd.edu, last updated