Computer Architecture CSC 304 Shai Simonson MIPS Method Calls, Stack Frames, and Parameter Conventions Every method should do the following: 1. 2. 3. 4. Copy input parameters from registers $4-$7, and if necessary off the stack. Allocate (i.e., push) space for stack frame (aka. activation record). The system stack ($sp) grows into smaller memory locations, so allocation is done with subtraction. Size is dependent on the number of local variables, parameters, and callee –saved registers that will be saved. Push callee registers (aka. preserved registers) onto the stack, if you intend to use these registers in your method. You will need to restore these registers to their original values before you return to your caller. Callee-registers include the “s” registers $16-$23 $30, and $ra and $fp. Set $fp (frame pointer) to $sp + frame size, and put local variables on stack relative to the frame pointer. Whenever your method invokes another method: a. Pass first 4 parameters in registers $4-$7 ($a0-$a3), and the rest on the stack. b. Save caller-saved registers (aka. temporary registers) on the stack, if you expect to use their current values after the invoked method returns. These include the “a” registers and “t” registers: $4-$15, and $24-$25. c. jal label - This stores the address of next line of your method (the return address) in $ra, and jumps to the address of the method you are invoking, i.e. label. d. Restore caller registers (if necessary), extract output parameters from the stack, and any return value(s) from $2 and $3. 5. 6. 7. 8. Put method’s return value(s) (if any) in $2 and $3. Restore callee registers (if necessary). Pop stack frame by adding frame size to $sp. jr $31 – This jumps back to the line following the jal call in the caller that invoked your method. A typical stack frame is shown below, where the lower addresses are at the bottom and the stack is growing in that direction. The stack frame has just been created and so the stack pointer has just grown by “frame-size” bytes. The frame pointer ($fp) sits at the top of the frame where the $sp used to be, and is used as a fixed point to reference local variables. This is especially important in recursive methods. The stack pointer ($sp) sits at the bottom of the frame and may grow and shrink during the execution of your method as it invokes other methods. incoming parameters from caller . … $fp local variables (This was the old $sp location) … saved registers … parameter 6 parameter 5 $sp Example of Recursive Method with Stack Trace Shown in Class This is a simple recursive factorial method in C++. Note that it is not tail recursive because when the recursion returns, the multiplication by n still needs to occur, so the stack will be used at that point. The equivalent MIPS version appears below the C++. We will do a complete stack trace in class. cout << “The factorial of 3is “ << fact(3); int fact (int n) { if (n < 1) return 1; else return (n* fact(n -1)); } .data .asciiz “The factorial of 3 is” str: .text … calling_method: addiu $sp, $sp, -32 sw $ra, 20($sp) sw $fp, 16($sp) addiu $fp, $sp, 32 li $4, 3 jal fact A: # Create the stack frame and save $sp and $fp # input parameter # invoke recursive method fact move $t0, $2 # extract fact method’s return value # this must be done right now, otherwise # $2 will be lost because the syscall uses the register la $a0, str li $v0, 4 syscall # print output message move $a0, $t0 li $v0, 1 syscall # put the saved output into $4 lw $ra, 20($sp) lw $fp, 16($sp) addiu $sp, $sp, 32 jr $ra # restore $ra # restore $fp # pop the stack frame # return to its caller # print fact(3), i.e. 6 fact: recurse: B: addiu $sp, $sp, -32 sw $ra, 20($sp) sw $fp, 16($sp) addiu $fp, $sp, 32 sw $4, 0($fp) # Create the stack frame and save $sp and $fp lw $$t0, 0($fp) bgtz $t0, recurse li $2, 1 b close_up # This loads n in $t0 and checks for the base case. lw $t1, 0($fp) addiu $4, $t1, -1 # This computes n-1, using temp register $t1 # and moves n-1 to $4. # $t0 and $t1 (caller-saved registers) are not saved # on the stack because the caller does not plan to # use their values after the call returns jal fact # Recursive call lw $t0, 0($fp) mul $2, $2, $t0 # Loads n in $t0, and # Multiplies fact(n-1) by n, and # Stores result in output register $2. # This is the local variable n # If there were other local variables they would # be at -4($fp), -8($fp), etc. # 0! = 1 # The previous two lines show that the method is not tail recursive close_up: lw $ra, 20($sp) lw $fp, 16($sp) addiu $sp, $sp, 32 jr $ra # restore $ra # restore $fp # pop the stack frame # return to the caller Trace of CallingMethod for Fact(3) $sp $fp Incoming $ra $4 Original CallingMethod Fact(3) 1000 968 936 4000 1000 968 -2000 A --3 Fact(2) 904 936 B 2 Fact(1) 872 904 B 1 Fact(0) 840 872 B 0 --3 2 2 1 1 0 1 B B A 2000 -- 0 0 0 0 0 1 2 6 6 6 $2 Now it starts popping the stack Fact(1) Fact(2) Fact(3) CallingMethod Original 872 904 936 968 1000 904 936 968 1000 4000 CallingMethod 1000 Fact(3) 988 984 968 2000 4000 3 Fact(2) 956 952 936 A 1000 2 Fact(1) 924 920 904 B 968 1 Fact(0) 892 888 872 B 936 0 860 856 840 B 904