RISC-V Assembler Stack Frame

Stack Frame

Functions need to keep track of their local variables. Local variables go in stack frames on the call stack. When a function is called it moves the stack pointer down making room for the function's local variables. When it returns it moves the stack pointer up.

A register is used to keep track of the address of the current stack frame. According to the RISC-V calling convention, register sp (x2) contains the address of the bottom (the smallest address) of the current stack frame.

Code interacts with sp in three ways:

In this example addi at the beginning and end of a function "creates" and "destroys" a stack frame of 8 bytes.

func_label: addi sp, sp, -8 ... ... ... addi sp, sp, 8 ret

The Function Prologue (Setting the Frame Pointer)

When a function is called, it must allocate its own stack frame and establish its own frame pointer reference. Under the standard RISC-V Calling Conventions, the callee performs these steps in its prologue:

  1. Allocate Stack Space: Decrement the stack pointer (sp) to make room for local variables, the saved return address, and the old frame pointer.

  2. Save the Caller's Frame Pointer: Store the current value of fp (which belongs to the function that called it) onto the stack so it isn't overwritten.

  3. Establish the New Frame Pointer: Move the relevant stack base address into the fp (s0) register.

# Example: Allocating a 32-byte stack frame addi sp, sp, -32 # 1. move stack pointer down to allocate space sd ra, 24(sp) # 2. save the return address sd fp, 16(sp) # 3. save the caller's old frame pointer mv fp, sp # 4. set the new frame pointer to the base of the frame

The Function Epilogue (Restoring the Frame Pointer)

Before returning control to the caller, the callee must undo its changes so that the caller's environment is perfectly preserved. The callee performs these steps in its epilogue:

  1. Restore the Caller's Frame Pointer: Load the original fp value back from the stack memory.

  2. Deallocate Stack Space: Increment the stack pointer (sp) back to its original position.

  3. Return: Jump back to the calling function.

# Example: De-Allocating a 32-byte stack frame ld fp, 16(sp) # 1. restore the caller's frame pointer ld ra, 24(sp) # 2. restore the return address addi sp, sp, 32 # 3. move stack pointer back up (de-allocate) jr ra # 4. return to caller