Skip to content
Contents

The stack: memory that grows downward

Section 4.4 7 min read

  • the stack and $sp
  • push and pop without push and pop
  • a stack frame: prologue, body, epilogue
  • o32: 8-byte alignment and the 16-byte argument area

Checked against: MIPS o32 ABI: stack frame layout carolyne_ref/dumps/hello_o0.dump (bfc000b0–bfc000e8 print_char; bfc0018c–bfc001ac main) carolyne_ref/carolyne_io.h (CAROLYNE_STACK_TOP)

Before you start

  • §4.2: base + offset, lw and sw
  • §2.2: $sp and $fp are callee-saved

By the end, you can

  • Say where $sp points and which way it moves when a frame is made
  • Write the push and pop sequences MIPS uses instead of push and pop
  • Read a real prologue and epilogue and name what each line does
  • Draw main's 32-byte frame

A region that grows down

The starts near the top of the data memory and grows toward lower addresses. $sp, the stack pointer, always holds the lowest address in use. On your test machine it starts at 0x10003FF0, just under the doors; crt0 sets it (§2.2).

The rules, all of them:

towriteeffect on $sp
claim N bytes (“push”)addiu $sp,$sp,-Ngoes down by N
save a registersw reg,off($sp)unchanged
restore a registerlw reg,off($sp)unchanged
release N bytes (“pop”)addiu $sp,$sp,Ngoes up by N

N is a multiple of 8, by the o32 convention. There is no push and no pop: one addiu opens a whole frame, and plain stores fill it.

Watch two frames

The runner claims a frame, saves two registers, claims a second frame inside it, and then unwinds both. The stack pane follows $sp.

Two nested frames: the stack grows down, then comes back up

Where does a function keep the registers it must not destroy, and how does $sp move?

program (address · word · instruction)

PC →0xbfc000000x3c1d1000_start:lui $sp,0x1000
0xbfc000040x27bd3ff0addiu $sp,$sp,16368
0xbfc000080x24100007li $s0,7
0xbfc0000c0x241f0100li $ra,256
0xbfc000100x27bdfff8addiu $sp,$sp,-8
0xbfc000140xafbf0004sw $ra,4($sp)
0xbfc000180xafb00000sw $s0,0($sp)
0xbfc0001c0x24100009li $s0,9
0xbfc000200x27bdfff8addiu $sp,$sp,-8
0xbfc000240xafb00000sw $s0,0($sp)
0xbfc000280x8fb00000lw $s0,0($sp)
0xbfc0002c0x27bd0008addiu $sp,$sp,8
0xbfc000300x8fb00000lw $s0,0($sp)
0xbfc000340x8fbf0004lw $ra,4($sp)
0xbfc000380x27bd0008addiu $sp,$sp,8
0xbfc0003c0xafa00008sw $zero,8($sp)

registers — read · written this tick

HI 0x00000000LO 0x00000000PC 0xbfc00000

stack around $sp = 0x00000000 — higher addresses on top

higher addresses ↑ · each row is one word, byte 0 on the left
0x00000014
00000000
0x00000010
00000000
0x0000000c
00000000
0x00000008
00000000
0x00000004
00000000
0x00000000
00000000
0xfffffffc
00000000
0xfffffff8
00000000
lower addresses ↓ · the stack grows this way

console (what the doors printed)

▮
cycle 0/161 tick = 1 instruction (not a cycle)

tick 0 of 16

Nothing has run yet. The PC (program counter) points at the first instruction.

Every addiu $sp,$sp,-8 moves the PC chip's neighbour $sp DOWN by 8 and opens two new word slots below the old top. sw fills them; lw reads them back; addiu $sp,$sp,8 gives them up. At the end $sp is exactly where it started and $s0 and $ra have their old values.

What to notice

Each addiu $sp,$sp,-8 moves $sp down and two fresh words appear under the old top. The second frame sits below the first. Unwinding is the mirror image: lw, then addiu $sp,$sp,8. At the end $sp is back at 0x10003FF0, and $s0 and $ra have their old values.

Before reading on, decide:

Tick 12 is the first `addiu $sp,$sp,8`. What is $sp after it, and which frame is now on top?

The smallest real frame: print_char

A function’s slice of the stack is its . The code that builds it is the prologue; the code that removes it is the epilogue. Here is print_char from hello.c at -O0, the smallest frame in the program.

print_char: one C statement, and the frame GCC wraps around it at -O0

carolyne_ref/carolyne_io.h; hello_o0.dump bfc000b0–bfc000e8

carolyne_io.h
1void print_char(char value)
2{
3 CAROLYNE_PUTCHAR = (unsigned char)value;
4}
MIPS (-O0)
bfc000b0addiu sp,sp,-8
bfc000b4sw s8,4(sp)
bfc000b8move s8,sp
bfc000bcmove v0,a0
bfc000c0sb v0,8(s8)
bfc000c4lbu v1,8(s8)
bfc000c8lui v0,0x1000
bfc000ccori v0,v0,0x3ff0
bfc000d0sw v1,0(v0)
bfc000d4nop
bfc000d8move sp,s8
bfc000dclw s8,4(sp)
bfc000e0addiu sp,sp,8
bfc000e4jr ra
bfc000e8nop

Click any highlighted line to see it light up in the other listing, and read what it does.

What to notice

The epilogue is the prologue backwards: lw undoes sw, addiu +8 undoes addiu -8. $ra is never saved, because print_char calls nothing. And value is parked at 8(s8), above the frame, in space the caller set aside: the o32 argument area.

main’s 32-byte frame

main calls other functions, so it must save $ra too. Its prologue at bfc0018c is addiu sp,sp,-32, sw ra,28(sp), sw s8,24(sp), move s8,sp. The frame, drawn from the top down:

offset from $spholdswho uses it
28the saved $rarestored before jr ra
24the saved $s8restored before return
16i, the loop countersw zero,16(s8), lw v0,16(s8)
0 … 15the argument areaspace for $a0–$a3 of every function main calls

The bottom 16 bytes are the o32 rule: a caller always leaves room for the callee to spill its four argument registers. print_char’s sb v0,8(s8) wrote into exactly this area of its caller.

Go deeper: why stacks grow down, and what the argument area is for

Early machines put the program and its data at low addresses and let them grow upward as the program allocated memory. The stack was placed at the top and grown downward, so the two regions could share whatever was in between without either knowing the other’s size. MIPS, C and every operating system since inherited the habit. Your test machine has no heap, but the shape is the same: data at 0x10000000, the stack from 0x10003FF0 downward.

The 16-byte argument area is a 1980s compromise. o32 passes the first four arguments in $a0–$a3 for speed, but a function may need their addresses (a & on a parameter) or run out of registers. So the caller reserves the stack slots those four values would have used, and the callee may store them there. print_char does exactly that at -O0 with sb v0,8(s8). At -O2 the store disappears, but the space is still reserved.

Can you now explain it?

The stack is memory below $sp that grows toward lower addresses. A frame is claimed with addiu -N, filled with sw, emptied with lw and released with addiu +N. Every frame is a multiple of 8 and keeps 16 bytes at the bottom for its callees' arguments.

If you remember only three things

  1. $sp is the lowest address in use. Push subtracts, pop adds. The newest frame is the lowest one.
  2. No push instruction, no pop instruction. One addiu opens the frame; sw and lw at fixed offsets do the saving. The epilogue is the prologue in reverse.
  3. A frame has a shape. Saved $ra and $s registers at the top, locals in the middle, 16 bytes of argument area at the bottom (o32).

Now try to explain this one yourself: print_char saves $s8 but not $ra, while main saves both. Using §2.2’s table, say why each function saves exactly what it saves.

Go deeper: a model answer, once you have tried

$ra is overwritten by jal. main calls print_str and print_int, so its own return address would be destroyed by the first call; it must save $ra first. print_char calls nothing, so $ra still holds the way back at jr ra, and saving it would be wasted work. $s8 is callee-saved: any function that changes it must put it back. Both functions use $s8 as their frame pointer at -O0, so both save the caller’s value and restore it. At -O2 neither uses $s8, and print_char needs no frame at all.

Check your understanding

0/5 answered
  1. 1.$sp starts at 0x10003FF0. After two `addiu sp,sp,-8`, where is it, and how big is the stack in use?

  2. 2.print_char begins `addiu sp,sp,-8` then `sw s8,4(sp)`. Where is the old s8 saved?

  3. 3.Why do MIPS functions use `addiu` once and then `sw` at offsets, instead of a push instruction?

  4. 4.'The stack grows downward' means which of these?

  5. 5.main's frame is 32 bytes and print_char's is 8, although print_char saves only one register. Why 8 and not 4?


Next: Chapter 5 — deciding: beq, bne and the branch target.

Where this comes back

5 later sections build on this one.