We're studying the MIPS assembler (I guess this question can apply to assembly in general though), and the teacher introduced us to the frame pointer.
If I have a function prologue, I used to do directly the stack pointer:
addiu $sp, $sp, -8 ; alloc 2 words in the stack
sw $s0, 4($sp) ; save caller function $s0 value in the stack
sw $ra, ($sp) ; save the return address for the callee function
And in the function epilogue:
move $v0, $0 ; set 0 as return value
lw $s0, 4($sp) ; pick up caller $s0 value from the stack
lw $ra, ($sp) ; pick up return address to return to the caller
addiu $sp, $sp, 8 ; dealloc the stack words I used
jr $ra ; return back to caller
The teacher said that using frame pointer is useful for us humans when we write functions in assembly:
addiu $sp, $sp, -12 ; alloc 3 words in the stack
sw $fp, 8($sp) ; save caller frame pointer in the stack
addiu $fp, $sp, 8 ; set $fp to the uppermost address of the activation frame
sw $ra, -4($fp) ; saving like the first example, but relative
sw $s0, -8($fp) ; to the frame pointer
The teacher also said that sometimes the stack pointer goes on allocating other space and refering to the activation frame within the function is harder since we need to pay attention. Using frame pointer we'll have a static pointer to the activation frame.
Yes, but will I ever need to use the activation within the function, since it just contains the saved data of the caller function?
I think it makes just things harder to implement. Is there a real practical example where the frame pointer is a great advantage to the programmer?
A frame pointer (the ebp register on intel x86 architectures, rbp on 64-bit architectures) contains the base address of the function's frame. The code to access local variables within a function is generated in terms of offsets to the frame pointer.
The compiler passes parameters and return variables in a block of memory known as a frame. The frame is also used to allocate local variables. The stack elements are frames. A stack pointer (sp) defines the end of the current frame, while a frame pointer (fp) defines the end of the last frame.
the fp stands for the frame pointer which is used as a base pointer to local variables on the stack. Although sp (stack pointer) varies according to function calls from a function, fp holds a fixed value. The relationship between the sp and the fp is a just difference of offsets for the stack.
The SFP is used to restore EBP to its previous value, and the return address is used to restore EIP to the next instruction found after the function call. " @
You only absolutely need a frame pointer when dynamically allocating variable amounts of space on the stack. Functions that use variable length arrays and/or alloca
in C are examples of functions that need a frame pointer. Because the amount stack the function uses is variable you can't use constant offsets from the stack pointer to access variables and you need a way to undo the variable length allocations when the function returns. Using a frame pointer solves both problems. You can use it to both address stack variables using constant offsets and restore the stack pointer to the value it had at the start of the function.
On MIPS, it would also make sense to use a frame pointer as an optimization in a function using only fixed size stack allocations, if the total stack allocation is more than 32k. The limited addressing modes supported by MIPS only allow for a 16-bit sign extended offset relative to the stack pointer or any other register. Since the stack pointer points to the bottom of the stack, only non-negative offsets can be used with the stack pointer, and so only 32k on the stack can be addressed in a single instruction. By using frame pointer and by pointing it at the middle of the stack frame (instead of the top of the frame) it can be used to address up to 64k of the stack in a single instruction.
Otherwise using the frame pointer is something that only benefits the programmer, not the program. If all functions in your program use a standard stack frame with a frame pointer then the frame pointer and all the saved frame pointer values stored in the stack form a linked list of stack frame. This linked list can easily be transversed to create a trackback of function calls when debugging. However, with a suitable modern debugger it's also possible to embed metadata (unwind info) into executables that the debugger can use to walk the stack frames even if a frame pointer isn't used. This something modern compilers can do automatically, but in assembly language it can be quite a pain to include all the necessary extra directives to make it work.
If the stack pointer can change multiple times through fixed size allocations and deallocations during a function then it can be a pain to keep track of a variable's location relative to the stack pointer at any given point in the program. While it would always be at a fixed offset at any given location, that would change depending on the location. Determining that offset can be tricky and error prone. Using the a frame pointer would give each variable a offset relative to the frame pointer that never changes, since the frame pointer value doesn't change.
Note that if you feel you need to use a frame pointer because one of the last two reasons you have to consider why you're programming in assembly in the first place. These are situations where a modern compiler wouldn't need to use a frame pointer, so would generate better code.
Frame pointer omission is a standard optimization option that C and C++ compilers implement. The advantage is that it can free up a register for other use. It is however very often disabled, it makes debugging a program crash excessively difficult. Even basic stuff like generating a stack trace gets very difficult, you no longer know where the frame of the parent function is located so don't know where to look for the return address. Inspecting the state of local variables similarly gets painful. This doesn't just matter while debugging the app, it comes back to haunt when the program crashes in production.
Debug metadata is required to know where to look, you must know the size of the frame. In general, making code debuggable and diagnosable is more important than making it easy to write. The typical programmer spend more time on debugging and testing than writing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With