aarch64-pg
4.2 · The stack and the frame pointer

The stack and the frame pointer

The stack is the memory a function uses for its own working storage: saved registers, local variables, and the bookkeeping that lets control return to the caller. It grows downward, from higher addresses toward lower ones, and the stack pointer sp points at the lowest in-use byte.

The frame pointer fp, the register x29, marks a fixed point in the current function's slice of the stack, its frame. Because sp can move during a function, code reads and writes locals at steady offsets from fp instead, so each local keeps one stable address for the life of the call.

The prologue and epilogue

A function that calls other functions opens with a prologue and closes with an epilogue. The prologue saves the caller's frame pointer and the return address, then claims space for the new frame; the epilogue restores them and hands control back.

The pair instructions do this in one step. stp fp, lr, [sp, alloc]! stores fp and lr and moves sp down by alloc in one pre-indexed access, and ldp fp, lr, [sp], dealloc restores them and lifts sp back afterward. Between the two, mov fp, sp anchors the frame.

// the frame skeleton: prologue, one local by fp offset, epiloguedefine(fp, x29)define(lr, x30)define(n_r, w9)n_s = 16alloc = -(16 + 16) & -16dealloc = -alloc        .datafmt:    .string "n = %d\n"        .text        .balign 4        .global mainmain:        stp     fp, lr, [sp, alloc]!        // save fp+lr and open the frame        mov     fp, sp        mov     w0, 5        str     w0, [fp, n_s]        ldr     n_r, [fp, n_s]        ldr     x0, =fmt        mov     w1, n_r        bl      printf        mov     w0, 0        ldp     fp, lr, [sp], dealloc       // restore fp+lr and close the frame        ret
Open in playground

warning

Two rules keep a frame correct. First, sp must stay 16-byte aligned at every point where another function might run, which is why the frame size is rounded with & -16. Second, a function that calls anything must save fp and lr on entry and restore them before it returns: lr holds the address to return to, and a call overwrites it, so losing it strands the function with no way back.

Locals in the frame

Local variables live at fixed offsets above fp. Storing one is str w0, [fp, 16]; reading it back is ldr w1, [fp, 16]. The offsets are yours to choose, as long as they stay inside the space the prologue reserved.

The reserved size comes from the alignment formula alloc = -(16 + locals) & -16, where locals is the number of bytes the function needs. The & -16 rounds the frame up to a multiple of sixteen so the stack keeps its alignment, and the leading sixteen bytes hold the saved fp and lr pair. The two editors below allocate frames, fill locals with fixed values, and read them back by offset.

loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Step with F10, run with F5, or feed stdin from the box below.

figure 4.2.1runnable: step it and watch the registersOpen in playground
loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Step with F10, run with F5, or feed stdin from the box below.

figure 4.2.2runnable: step it and watch the registersOpen in playground