Skip to content
Contents

Register names with a job: the o32 convention

Section 2.2 9 min read

  • calling convention (o32 ABI)
  • argument and return-value registers
  • caller-saved and callee-saved
  • $ra, $sp, $gp, $fp
  • $at, $k0, $k1

Checked against: MIPS o32 ABI (SYSV supplement): register usage carolyne_ref/dumps/hello_o0.dump (bfc00000–bfc000a0, crt0) carolyne_ref/programs/crt0_mips.S

Before you start

  • §2.1: the 32 registers are alike to the hardware, except $zero
  • §1.2: reading a listing

By the end, you can

  • Read a register name in a dump and say its number and its job
  • Name the four register groups and what happens to each across a call
  • State which two registers the hardware treats specially, and why
  • Explain what crt0 does to the registers before main runs

Names are an agreement, not hardware

The CPU numbers its registers $0 to $31. Nothing else. The names you see in a dump, a0, sp, ra, come from the . It is a document, not a circuit. Your toolchain follows it because of the flag -mabi=32. ABI means application binary interface: the rules two compiled pieces of code need to work together.

Here is the whole table. Read it once now; chapter 6 uses every row.

The 32 registers and their o32 jobs

MIPS o32 ABI; names as GCC and objdump print them

$0$zeroalways 0 (hardware)always 0
$1$atassembler temporarygone (the assembler may use it)
$2$v0return valuegone (holds the result)
$3$v1return value, second wordgone
$4$a0argument 1gone
$5$a1argument 2gone
$6$a2argument 3gone
$7$a3argument 4gone
$8$t0temporarygone (caller-saved)
$9$t1temporarygone (caller-saved)
$10$t2temporarygone (caller-saved)
$11$t3temporarygone (caller-saved)
$12$t4temporarygone (caller-saved)
$13$t5temporarygone (caller-saved)
$14$t6temporarygone (caller-saved)
$15$t7temporarygone (caller-saved)
$16$s0savedkept (callee-saved)
$17$s1savedkept (callee-saved)
$18$s2savedkept (callee-saved)
$19$s3savedkept (callee-saved)
$20$s4savedkept (callee-saved)
$21$s5savedkept (callee-saved)
$22$s6savedkept (callee-saved)
$23$s7savedkept (callee-saved)
$24$t8temporarygone (caller-saved)
$25$t9temporarygone (caller-saved)
$26$k0reserved for the operating systemnever use
$27$k1reserved for the operating systemnever use
$28$gpglobal pointerkept
$29$spstack pointerkept
$30$fpframe pointer (objdump: s8)kept (callee-saved)
$31$rareturn address (written by jal, hardware)gone (jal overwrites it)

The same table as colors. Each color is one group, and the colors stay the same in every register picture in this book.

The four groups, by color

Which column is all one color, and why?

HI 0x00000000LO 0x00000000PC 0xbfc00094
  • hardwired / reserved
  • argument / result
  • temporary (caller-saved)
  • saved (callee-saved)
  • pointer
  • read this step
  • written this step

What to notice

The middle two columns are pure: $8–$15 are all temporaries, $16–$23 all saved. The first and last columns mix the special registers with the arguments and the pointers. The values shown are what crt0 leaves behind.

The four groups

Arguments and results. A function’s first four arguments arrive in the $a0–$a3. Its answer goes back in the $v0 and $v1.

Temporaries. $t0–$t9 are . A called function may overwrite them freely. If you still need one after a call, save it yourself before the call.

Saved registers. $s0–$s7 are . A function that wants one must save the old value and put it back before returning. Your values in them survive any call.

Pointers and specials. is where jal saves the way back. points at the top of the stack (§4.4). points near the data. is the base of the current function’s frame; objdump calls it s8. belongs to the assembler (§9.1). The $k0 and $k1 are for an operating system, and this book never touches them.

Watch crt0 fill the registers

Every program your core runs starts with the same 42 words: the start-up code, crt0. It clears all 31 writable registers, builds $gp and $sp, calls main, and sends main’s result to the exit door. Here it is, with a tiny stand-in for main that returns 0.

The first 42 words of every program: crt0 fills the registers

What does the start-up code do with the registers before main runs?

program (address · word · instruction)

PC →0xbfc000000x00000825_start:move $at,$zero
0xbfc000040x00001025move $v0,$zero
0xbfc000080x00001825move $v1,$zero
0xbfc0000c0x00002025move $a0,$zero
0xbfc000100x00002825move $a1,$zero
0xbfc000140x00003025move $a2,$zero
0xbfc000180x00003825move $a3,$zero
0xbfc0001c0x00004025move $t0,$zero
0xbfc000200x00004825move $t1,$zero
0xbfc000240x00005025move $t2,$zero
0xbfc000280x00005825move $t3,$zero
0xbfc0002c0x00006025move $t4,$zero
0xbfc000300x00006825move $t5,$zero
0xbfc000340x00007025move $t6,$zero
0xbfc000380x00007825move $t7,$zero
0xbfc0003c0x00008025move $s0,$zero
0xbfc000400x00008825move $s1,$zero
0xbfc000440x00009025move $s2,$zero
0xbfc000480x00009825move $s3,$zero
0xbfc0004c0x0000a025move $s4,$zero
0xbfc000500x0000a825move $s5,$zero
0xbfc000540x0000b025move $s6,$zero
0xbfc000580x0000b825move $s7,$zero
0xbfc0005c0x0000c025move $t8,$zero
0xbfc000600x0000c825move $t9,$zero
0xbfc000640x0000d025move $k0,$zero
0xbfc000680x0000d825move $k1,$zero
0xbfc0006c0x0000e025move $gp,$zero
0xbfc000700x0000e825move $sp,$zero
0xbfc000740x0000f025move $fp,$zero
0xbfc000780x0000f825move $ra,$zero
0xbfc0007c0x3c1c1001lui $gp,0x1001
0xbfc000800x279c8010addiu $gp,$gp,-32752
0xbfc000840x3c1d1000lui $sp,0x1000
0xbfc000880x27bd3ff0addiu $sp,$sp,16368
0xbfc0008c0x0ff00063jal 0xbfc0018c
0xbfc000900x00000000nop
0xbfc000940x3c081000lui $t0,0x1000
0xbfc000980x25083ff8addiu $t0,$t0,16376
0xbfc0009c0xad020000sw $v0,0($t0)
0xbfc000a00x1000ffff_park:b 0xbfc000a0
0xbfc000a40x00000000nop
0xbfc0018c0x24020000main:li $v0,0
0xbfc001900x03e00008jr $ra
0xbfc001940x00000000nop

registers — read · written this tick

HI 0x00000000LO 0x00000000PC 0xbfc00000

console (what the doors printed)

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

tick 0 of 43

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

Thirty-one move instructions write 0 into every register except $zero. Then gp and sp get real addresses in two steps each. jal writes $ra and jumps to main; jr $ra comes back; the last store hits the exit door.

What to notice

Thirty-one move instructions in a row, one per register, each one a green box. Then $gp and $sp each take two ticks: lui for the top half, addiu for the bottom. At jal main, $ra turns green with the address of the instruction after the nop.

Scrub to the jal and decide:

`jal main` sits at 0xBFC0008C. After it runs, what does $ra hold?

Only two registers are special to the hardware

The CPU treats $8 and $16 the same way. It treats $a0 and $t0 the same way. Exactly two registers have hardware meaning: $zero, which is always 0, and $31, which jal writes. Everything else in the table is a promise between compilers.

Go deeper: why gp takes two steps with a strange constant, and why crt0 zeroes everything

lui $gp,0x1001 then addiu $gp,$gp,-32752 gives 0x10010000 − 0x7FF0 = 0x10008010. Why not lui 0x1000 and add 0x8010? Because addiu sign-extends its 16-bit constant, and 0x8010 has its top bit set, so it would count as −32752. The assembler therefore puts one more in the lui and subtracts. §3.2 makes this rule precise. _gp itself is a linker symbol: the middle of the small-data area, so that one lw with a 16-bit offset reaches all of it. Your builds use -G0, so nothing is placed there and $gp is set but unused.

Why does crt0 clear every register? On real hardware a register powers up with an unknown value. In a simulator it powers up as X, “unknown”, and a single X that reaches a compare poisons the whole run. Thirty-one move instructions make the start state the same on every run, on every machine. The three programs in carolyne_ref/programs/ all begin exactly this way.

Can you now explain it?

Register names are a table compilers agree on. Arguments in $a, results in $v, temporaries may be lost across a call, saved registers must survive one. Only $zero and $ra mean anything to the hardware.

If you remember only three things

  1. A name is a job, not a circuit. $8 and $t0 are one box. The convention says what to keep in it; the CPU does not check.
  2. Two kinds of promise across a call. Caller-saved ($t, $a, $v): gone unless the caller saves them. Callee-saved ($s, $sp, $fp, $gp): the function must restore them.
  3. jal writes $31 with PC + 8. PC is the program counter; the return address skips the delay slot. jr $ra goes back there. That is the whole hardware side of a function call.

Now try to explain this one yourself: GCC (the C compiler) at -O0 keeps locals at fixed offsets from $fp. Why is $fp callee-saved? What would go wrong if a called function changed it and did not restore it?

Go deeper: a model answer, once you have tried

The caller reads and writes its locals as lw/sw at offsets from $fp for its whole life, including after every call it makes. If a callee left $fp pointing somewhere else, every later access in the caller would hit the wrong memory: reads return garbage, writes corrupt another frame. So the convention makes $fp callee-saved: a function that needs its own frame pointer first stores the caller’s $fp on the stack and restores it before jr $ra. §6.3 shows a real prologue and epilogue doing exactly this.

Check your understanding

0/5 answered
  1. 1.A C function is called as `f(7, 9)`. Where does the compiler put the 7?

  2. 2.Which registers may a function overwrite without saving them first?

  3. 3.What does `jal main` at address 0xBFC0008C write, and where?

  4. 4.A function wants to use $s0 as a counter. What must it do?

  5. 5.The dump says `move s8,zero`. Which register is s8?


Next: Chapter 3 — adding and subtracting: add, addu, sub, subu.

Where this comes back

7 later sections build on this one.