11

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?

NoImaginationGuy
  • 1,795
  • 14
  • 24
  • for many instruction sets you dont need to use a frame pointer, at best it makes the compiler authors debugging easier which is not really a valid excuse, seems like a waste of a register to me. gcc, etc may allow you to choose not to use a frame pointer, not build code that uses it, to save that overhead. some default with some default without. It makes the offsets within the function fixed for that function, variable X is always fp-N for that function. you can make X sp+M for the whole function to or as the function goes you can move sp around as needed to preserve space. – old_timer Oct 17 '17 at 19:56
  • At the end of the day though, whatever the teacher says, goes, for that class, pass the class, doubt everything and (re-)discover it for yourself to see if you believe it or not. Was it just something to make grading homework easier on the teacher, or does this programming rule I was taught actually "better" in some sense of the word? I agree it makes it easier for humans, but at the same time compiler or human, if you move sp to cover all the usage for the function, it is equally easy without a frame pointer. – old_timer Oct 17 '17 at 19:57
  • 1
    Try this: define a C structure in your mind, with 5 or more fields. Allocate space for it in the stack. Initialize it. Pass its members to other functions as parameters on the stack. Do it with and without a frame pointer. – Margaret Bloom Oct 17 '17 at 20:06
  • @old_timer I know that modern compilers don’t add it by default. And I think exactly the same thing. plus, I really prefer working myself with the stack pointer cause I don’t find any difficulty with it less than with the frame pointer. And I know I need to doubt everything. I heard my teachers saying shit over shit. But I can’t imagine an example where compiling by hand is almost impossible without a frame pointer. – NoImaginationGuy Oct 17 '17 at 20:12
  • @MargaretBloom I got your point, having 5 or more arguments denies me to use $arg* registers, so I need to alloc them on the stack. I don’t think it’s particularly difficult without a frame pointer, but I’ll try it, good example thanks. – NoImaginationGuy Oct 17 '17 at 20:18
  • Some compilers for some targets actually do use it by default, depends on the target. gcc for example, but would have to do some tests to remember which target(s). – old_timer Oct 17 '17 at 20:42
  • the more interesting exercise is not to allocate all of the variables you need at once up front, but dynamically across the function. So X might always be fp-n but in one part of the function it might be sp+m and another sp+k because sp is changing. How concervative or wasteful is the stack usage in the function, part of the function uses an array of 32 variables, during that time one function call is made, later the array is no longer needed and at least one other function call is made. do you burn the stack for the whole function for the array or just part of the function? – old_timer Oct 17 '17 at 20:45
  • a compiler like gcc by default will burn it for the whole function, is there a switch to change that? I dont know, have not looked/tried. but if writing assembly by hand 1) am I actually conforming to a C compiler calling convention? 2) would I only use the stack for something as need and then free up the stack immediately after? (3 would I just use regular memory and not the stack this is asm after all). – old_timer Oct 17 '17 at 20:47
  • A frame pointer is useful for variable sized arrays (or nearly equivalently, `alloca()`). – EOF Oct 17 '17 at 20:57
  • [Phoronix tested](https://www.phoronix.com/scan.php?page=article&item=fedora-frame-pointer&num=1) the performance downside of `-O2 -fno-omit-frame-pointer` with x86-64 GCC12.1 on a Zen3 laptop CPU for multiple open-source programs, as proposed for Fedora 37. Most of them had performance regressions, a few of them very serious, although the biggest ones are probably some kind of fluke or other interaction. Geometric mean 14% faster without frame pointers. – Peter Cordes Jul 02 '22 at 07:11

2 Answers2

8

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.

Ross Ridge
  • 38,414
  • 7
  • 81
  • 112
2

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536