1

I was under the impression that the frame pointer $fp is set to the first word of stack according to Computer Organization and Design MIPS 5th ed page 103.

enter image description here

int func(int g) {
    int f = 9;
    return g+f;
}

mips gcc 12.2.0 generated assembly code.

func:
        addiu   $sp,$sp,-24
        sw      $fp,20($sp)
        move    $fp,$sp
        sw      $4,24($fp)
        li      $2,9                        # 0x9
        sw      $2,8($fp)
        lw      $3,24($fp)
        lw      $2,8($fp)
        nop
        addu    $2,$3,$2
        move    $sp,$fp
        lw      $fp,20($sp)
        addiu   $sp,$sp,24
        jr      $31
        nop

Is it valid that $fp (frame pointer) and $sp (stack pointer) contain the same address? I would have thought $fp would point to the first word on the stack i.e

func:
        addiu   $sp,$sp,-24
        sw      $fp,20($sp)
        addiu   $fp,$sp,20          #let $fp point to the first word on the stack

Are there any rules on which part of the stack $fp should point to or is it all dependent on the descretion of the software developer/compiler?

African_king
  • 177
  • 1
  • 8
  • 1
    "**Some** MIPS software..." – Raymond Chen Nov 26 '22 at 04:10
  • @RaymondChen i see but why does gcc mips compiler set $fp to $sp as shown above, is there any advantage? – African_king Nov 26 '22 at 06:31
  • 1
    I'm going to admit, I don't quite get this either, since MIPS has no `push` or `pop` instructions that implicitly modify `SP`, and it uses `$ra` rather than `($sp)` to hold the return address, so functionally there doesn't seem to be anything that makes `$sp` different than the other registers. There doesn't seem to be anything that silently modifies the stack on MIPS so I don't get why we need to replicate the `mov ebp,esp` that x86 does. – puppydrum64 Dec 07 '22 at 18:48

1 Answers1

3

GCC seems to follow the ABI linked in this answer.

That ABI mandates:

The stack pointer must be adjusted to allocate the stack frame before any other use of the stack pointer register.

A function allocates a stack frame by subtracting the size of the stack frame from $sp on entry to the function. This $sp adjustment must occur before $sp is used within the function and prior to any jump or branch instructions.

So it's not possible to implement the frame pointer as depicted in the book, which would require a move $fp, $sp and then an addiu $sp, $sp, XX.

So the code generated by GCC (without optimizations) according to this ABI has an fp below the frame.
The ABI also mandates a homing/shadow area: even though the first four args are not passed on the stack, the caller must always reserve the corresponding space on the stack so that the callee can save the arg registers on that space.
You can see this behavior by looking at the instruction sw $4,24($fp) and noting that $fp + 24 = original $sp = just above the allocated frame.

This means that even non-leaf functions (functions that call other functions) will generally have $sp = $fp because the compiler knows how much space it needs.

But you can create cases where this is not true, for example by using the infamous alloca:

#include <alloca.h>

int bar(int, int, int, int, int);

int func(int g) {
    int f = 9;
    void* h = alloca(g);
    return bar(f, f, f, f, f);
}

This code is compiled (without optimizations) to:

func:
        addiu   $sp,$sp,-48 #Reserve space for the frame
        sw      $31,44($sp) #Save ra in the highest slot
        sw      $fp,40($sp) #Save fp in the slow below
        move    $fp,$sp     #Set the frame pointer
        
        sw      $4,48($fp)  #Spill the first arg (g) in the homing space
        li      $2,9               
        sw      $2,32($fp)  #f = 9
        
        lw      $2,48($fp)  #g
        nop
        addiu   $2,$2,7
        srl     $2,$2,3
        sll     $2,$2,3     #(g + 7) / 8 * 8 = g aligned on a multiple of 8 (required by the ABI)
        
        subu    $sp,$sp,$2  #alloc g (aligned) bytes on the stack
        
        addiu   $2,$sp,24   #Make a pointer 24 bytes ABOVE the new stack pointer
                    #So there still are 16 byte (homing space) + 4 byte (5th arg to bar) + 4 bytes (alignment)
                    #free just above the stack pointer
                    #Note: we can steal this space from the g bytes allocated because there was a corresponding
                    #space in the frame initially allocated
        addiu   $2,$2,7
        srl     $2,$2,3
        sll     $2,$2,3     #Align this pointer to 8 bytes
        sw      $2,36($fp)  #h = that pointer
        
        lw      $2,32($fp)  #f
        nop
        sw      $2,16($sp)  #Note f is stored relative to SP
        lw      $7,32($fp)
        lw      $6,32($fp)
        lw      $5,32($fp)
        lw      $4,32($fp)  #args
        jal     bar     #call bar
        nop

        move    $sp,$fp     #Restore the stack pointer just below the frame
        lw      $31,44($sp)
        lw      $fp,40($sp) #Restore the regs
        addiu   $sp,$sp,48  #Restore the stack pointer 
        jr      $31
        nop

It may be worth drawing the state of the stack to better understand what's going on.

In general, the frame handling strategy used by the compilers will vary with time and you must take what's written in books with a grain of salt because nobody has the time or possibility to update and republish a book each time a new version of a compiler is released.

Just be sure to understand how the examples in the book work and you'll be able to adapt to new conventions pretty easily.

Margaret Bloom
  • 41,768
  • 5
  • 78
  • 124
  • "even though the first four args are passed on the stack," Is this missing a "not"? – ecm Nov 26 '22 at 11:49
  • I would have guessed that "*before $sp is used within the function*" meant before it's dereferenced, mostly just telling you that there's no red-zone so don't store below it. But maybe FP-based stack unwinding depends on seeing the first instruction to use `$sp` at all, instead of just one with it as the destination? – Peter Cordes Nov 26 '22 at 12:04
  • Seems like an oddly intrusive ABI requirement. (Unlike the before-branching requirement, which can make some sense for stack unwinding, although has the downside of disallowing early-out branching (shrink-wrap optimization) unless you pretend that's a different function that falls through into the main function as a tailcall. – Peter Cordes Nov 26 '22 at 12:05
  • Thanks alot Margeret Bloom. Thanks all – African_king Nov 26 '22 at 12:16
  • @PeterCordes It's odd indeed, I also took it as some unwinding requirement but never investigated too much. – Margaret Bloom Nov 26 '22 at 13:08