10.7. The Stack Frame

10.7.1. The Basics

The stack frame, also known as activation record is the collection of all data on the stack associated with one subprogram call.

The stack frame generally includes the following components:

  • The return address
  • Argument variables passed on the stack
  • Local variables (in HLLs)
  • Saved copies of any registers modified by the subprogram that need to be restored (e.g. $s0 - $s8 in MAL).

Since the stack frame for a given subprogram has a fixed size, we can reduce the number of instructions required to push and pop it by only updating the stack pointer once. Instead of individual pushes and pops, we can use offset addressing:

The following requires 12 instructions:

	    pushw($ra)
	    pushw($a0)
	    pushw($a1)
	    
	    popw($a1)
	    popw($a0)
	    popw($ra)
	    

The same effect can be achieved with 8:

	    addi    $sp, $sp, -12
	    sw      $ra, 0($sp)
	    sw      $a0, 4($sp)
	    sw      $a1, 8($sp)
	    
	    lw      $a1, 8($sp)
	    lw      $a0, 4($sp)
	    lw      $ra, 0($sp)
	    addi    $sp, $sp, 12
	    

One possible view of the stack frame:

		    Address Content
	    $sp --> FF00    Saved return address
		    FF04    Saved value from $a0
		    FF08    Saved value from $a1
	    

10.7.2. Allocating Space on the Stack for Local Variables

Local variables in C, C++, and Java that are not declared as static, actually reside in the stack frame, and have automatic storage class. When a C, C++, or Java program enters a block such a function or method, space is allocated on top of the stack for the local variables there. When the program leaves the block, the space is freed again.

Automatic variables are therefore represented as an offset from the stack pointer, such as 4($sp) or 8($sp).

	    int     function(int a, double b)
	    
	    {
		int     x, y;
		...
		
		return  x;
	    }
	    

Possible stack frame for function:

		    Address Content
	    $sp --> F080    Saved return address
		    F084    a
		    F088    b
		    F090    x
		    F094    y
		    F098    Saved register value
		    F09C    Saved register value
	    

10.7.3. The Frame Pointer

The stack pointer will change when a subprogram does a push or pop operation. Many subprograms do this during calculations for convenience, or to implement algorithms that use a stack.

When this happens, the offset addresses representing local automatic variables such as 4($sp) are no longer valid. The offsets are computed by the compiler, and hard-coded as offset-mode operands in the instructions, so they cannot be easily changed while the program is running.

One way to alleviate this problem is by using the frame pointer. The frame pointer is another register that we set to the address of the stack frame when a subprogram begins executing. If the code refers to local variables as offsets from the frame pointer instead of offsets from the stack pointer, then the program can use the stack pointer without complicating access to auto variables. We would then refer to something in the stack frame as offset($fp) instead of offset($sp).

		    Address Content
	    $fp --> F080    Saved return address
		    F084    a
		    F088    b
		    F090    x
		    F094    y
		    F098    Saved register value
		    F09C    Saved register value
	    
	    $sp --> Who cares?
	    

Use of the frame pointer adds a small amount of overhead to a function call (the frame pointer must be set using the stack pointer upon entry to the subprogram, and restored before returning).

The C code segment below was compiled with gcc on an Intel processor (with no optimizations) to produce the assembly code that follows:

	    # C code
	    
	    int     main()
	    {
		int     x, y, z;
		
		x = 5;
		y = 3;
		z = x + y - 2;
		return z;
	    }
	    
	    # Compiler output with no optimization
	    
	    _main:
	    LFB3:
	    LM1:
		    pushl   %ebp            # Save FP (a.k.a. base pointer)
	    LCFI0:
		    movl    %esp, %ebp      # Copy SP to FP
	    LCFI1:
		    subl    $24, %esp       # Advance SP
	    LCFI2:
	    LM2:
		    movl    $5, -20(%ebp)   # x = 5
	    LM3:
		    movl    $3, -16(%ebp)   # y = 3
	    LM4:
		    movl    -16(%ebp), %eax # z = x + y - 2
		    addl    -20(%ebp), %eax
		    subl    $2, %eax
		    movl    %eax, -12(%ebp) 
	    LM5:
		    movl    -12(%ebp), %eax # Return value goes in eax
					    # Very inefficient
					    # Optimizer would remove this
	    LM6:
		    # The leave instruction restores the base pointer
		    # and other items in a single instruction cycle.
		    # This is a good example of a CISC instruction.
		    leave
		    ret
	
	# Compiled with -O (basic optimizations)
	
	_main:
		pushl   %ebp            # Save base pointer
		movl    %esp, %ebp      # Set base pointer to stack pointer
		
		movl    $6, %eax        # Return 6
		
		leave                   # Restore base and stack pointers
		ret
	

10.7.4. Passing Arguments on the Stack

In many architectures, subprogram arguments are by convention passed on the system stack rather than in registers. The use of registers for arguments is only common in architectures that have many general-purpose registers available. Even in architectures that have a large number of registers such as the MIPS, there are often subprograms that take many arguments, making it impractical to pass all of them in registers. The example below demonstrates how to pass an argument on the stack.

#########################################################################
#   Description:
#       Print factorials from 1 to 10
#########################################################################

#########################################################################
#   Macro definitions and includes
#########################################################################

# Common ASCII/ISO control characters

ISO_EOT             =   4   # End of transmission (Ctrl+D)
ISO_BEL             =   7   # Bell
ISO_BS              =   8   # Backspace
ISO_TAB             =   9   # Tab
ISO_LF              =   10  # Line feed (newline)
ISO_FF              =   12  # Form feed
ISO_CR              =   13  # Carriage return

# System call constants

SYS_PRINT_INT       =   1
SYS_PRINT_FLOAT     =   2
SYS_PRINT_DOUBLE    =   3
SYS_PRINT_STRING    =   4
SYS_READ_INT        =   5
SYS_READ_FLOAT      =   6
SYS_READ_DOUBLE     =   7
SYS_READ_STRING     =   8
SYS_SBRK            =   9
SYS_EXIT            =   10
SYS_PRINT_CHAR      =   11
SYS_READ_CHAR       =   12

	# Save contents of an integer register on the stack
	.macro  pushw($register)
	addi    $sp, $sp, -4
	sw      $register, ($sp)
	.end_macro
	
	# Retrieve top of stack to an integer register
	.macro  popw($register)
	lw      $register, ($sp)
	addi    $sp, $sp, 4
	.end_macro

	# Print the character in $const
	# $const must be an immediate value
	# Example: print_char_const(ISO_LF)
	.macro  print_char_const($const)
	pushw($a0)
	pushw($v0)
	
	li      $a0, $const
	li      $v0, SYS_PRINT_CHAR
	syscall
	
	popw($v0)
	popw($a0)
	.end_macro
	
	# Print the integer in $var
	# $var must be a label
	.macro  print_word_var($var)
	pushw($a0)
	pushw($v0)
	
	lw      $a0, $var
	li      $v0, SYS_PRINT_INT
	syscall
	
	popw($v0)
	popw($a0)
	.end_macro
	

######################################################################### 
#   Subprogram Description:
#       Compute N! for non-negative N
#
#   Arguments received on stack:
#       N
#
#   Returns:
#       $v0 = N!
#
#########################################################################

# Stack frame:
#   $a0         Saved register used to cache argument
#   $ra         Return address
#   N           Argument

# Subprogram body
	.text
factorial:
	# Push rest of stack frame.  Already contains argument
	# pushed by caller.
	addi    $sp, $sp, -8 
	sw      $ra, 0($sp)
	sw      $a0, 4($sp)
	
	lw      $a0, 8($sp)     # Get argument from the stack
	
	# Initialize N! to 1 and then multiply by all values from N to 2
	li      $v0, 1
factorial_loop:
	blt     $a0, 2, factorial_done
	mul     $v0, $v0, $a0
	addi    $a0, $a0, -1
	j       factorial_loop
	
factorial_done:
	
	# Pop stack frame
	lw      $ra, 0($sp)
	lw      $a0, 4($sp)
	addi    $sp, $sp, 12    # Account for argument on stack
	
	jr      $ra


#########################################################################
#   Main program
#########################################################################

	.data
nfactorial: .word   0

# Main body
	.text
main:
	# Push stack frame
	pushw($ra)

	li      $s0, 0
next_factorial:
	pushw($s0)
	jal     factorial
	
	# Print N!
	sw      $v0, nfactorial
	print_word_var(nfactorial)
	print_char_const(ISO_LF)
	
	# Next N
	addi    $s0, $s0, 1
	ble     $s0, 10, next_factorial
	
	# Pop stack frame
	popw($ra)
	
	# Return to system
	jr      $ra