Computer Organization CS345 David Monismith Based upon notes by Dr. Bill Siever and notes from the Patterson and Hennessy Text
Review Last time: Opcode Review Today: We begin to look at how the stack used to implemented recursive functions in MIPS.
The stack: Recall that the stack can be used to keep track of extra arguments. We have lots of memory space to use for the stack because it starts at the end of memory (from address 0xfffffffc) and continues up to statically allocated memory (address 0x ). For compiled programs, the stack is used to keep track of activation records for each function call. Let's look at an example function that uses recursion:
Recursion Example //This function takes x to the power y //The function only works for y >= 0 1. int power(int x, int y) 2. { 3. if(y < 0) 4. System.exit(0); 5. else if(y == 0) 6. return 1; 7. else if(y == 1) 8. return x; 9. else 10. return x * power(x, y-1); 11. }
Calling Function Let's look at an example of calling this function. Suppose we call power(2, 4). What happens in memory? First, we know the result should be 16. On the first call we get an activation frame as follows: call 1: power(2, 4) | x = 2 | | y = 4 | | ra = ? |
Recursion example contd. x and y are local variables, so we must keep track of them in the activation frame. We must keep track of the return address "ra" (the instruction we were working on). We were on line 10. We hit the else statement in the first call, so our second call is power(2, 3). call 2: power(2, 3) | x = 2 | | y = 3 | | ra = ? | Again, we were on line 10.
Recursion example contd. We hit the else statement in the second call, so our third call is power(2, 2). call 3: power(2, 2) | x = 2 | | y = 2 | | ra = ? | Again, we were on line 10. We hit the else statement in the third call, so our fourth call is power(2, 1). call 4: power(2, 1) | x = 2 | | y = 1 | | ra = ? | This time, we were on line 8. So, we will return to the third call. We hit the else statement in the fourth call, so we simply return x = 2.
Recursion Stack Before we return, though, we have lots of data on the stack. Each time we make a nested function call we'll likely need to store our arguments because we only have four argument registers. So right now, our stack should look like this:
< --- End of Memory < --- Top of stack call 1: power(2, 4) X = 2 Y = 4 Ra = ? X = 2 Y = 4 Ra = ? call 2: power(2, 3) X = 2 Y = 3 Ra = ? call 3: power(2, 2) X = 2 Y = 2 Ra = ? X = 2 Y = 2 Ra = ? call 4: power(2, 1) X = 2 Y = 1 Ra = ? Notice that when we return from call 4, we'll return x which is 2, and remove the data from call 4 from the stack. Now the stack looks like this return value is 2. < --- Top of stack Now, when we return from call 3, we'll return x * power(2,1) which is 2*2, and remove the data from call 3 from the stack. Now the stack looks like this: Now, when we return from call 2, we'll return x * power(2,2) which is 2*4, and remove the data from call 2 from the stack. Now the stack looks like this: Now, when we return from call 1, we'll return x * power(2,3) which is 2*8, and remove the data from call 1 from the stack. Now the stack looks like this: < --- Top of stack
Stack Pointer To get our example to work in MIPS, we need to allocate space for variables using the stack pointer $sp. Since the stack pointer starts at the end of memory we need to decrease its value to work with it. Notice that with the example above, we need to do the following: save the $ra register (the return address) if $a1 > 1 return value * power(value - 1) if $a1 == 1 return value if $a1 == 0 return 1
Stack Pointer To keep things simple for this example and future ones, we will allocate space for the return address register and all the argument registers. We'll also allocate space forone more register on top of that. Notice that we've reserved space for six 32 bit registers. This is the same as reserving 24 bytes of space. So, we need to decrease the value of the stack pointer by 24 each time we need an activation record. An example program follows:
Example.text main: sub $sp, $sp, 24 #make space for arguments and $ra #set $a0 here #set $a1 here jal pow add $sp, $sp, 24 #return the stack pointer to its original value #print the result move $a0, $v0 li $v0, 1 syscall #end the program li $v0, 10 syscall
Example pow: sub $sp, $sp, 24 # Set aside space for arguments and ra (24 bytes) sw $ra, 20($sp) #store the ra register move $v0, $a0 #copy contents of $a0 (the value of x) into $v0 bgt $a1, 1, powElse # if y > 1, jump to powElse beq $a1, 1, powEnd # if y == 1, jump to powEnd beq $a1, $zero, powIfZero # if y == 0, return 1 #end the program if we have a negative value li $v0, 10 syscall
Example powIfZero: li $v0, 1 #copy 1 into the return register add $sp, $sp, 24 #revert stack pointer jr $ra #return powElse: sw $a1, 24($sp) #store value of y in caller's space sub $a1, $a1, 1 #set up new arguments jal pow #call pow again lw $a1, 24($sp) # revert $a1 to its original value (note this isn't really necessary) mul $v0, $v0, $a0 #multiply x and the return value, and store the result as our new return value # continue with the code from powEnd powEnd: lw $ra, 20($sp) #restore the ra register add $sp, $sp, 24 #revert the stack pointer (pop the stack) jr $ra #return