The stack and $sp

MIPS has no push and no pop: the stack is memory, $sp is an ordinary register, and a push is a subtraction and a store. Saving several registers with one adjustment, local room, and giving every byte of it back.

The stack lecture of Assembly basics pushed by hand, moving a stack pointer and then writing at it. That is what MIPS does, because MIPS has no push instruction and no pop instruction: $sp is one of the 32 ordinary registers, and everything about the stack is a convention plus two instructions you already know.

A push is a subtraction and a store

$sp starts at 0x7FFFEFFC, near the top of the address space, and the stack grows downwards. So:

  • a push is addi $sp, $sp, -4 and then sw at 0($sp),
  • a pop is lw from 0($sp) and then addi $sp, $sp, 4.

The 4 is the size of what you are putting there, and it has to keep $sp a multiple of 4, because a sw at an address that is not ends the run.

Step through it and watch $sp in the registers panel. Before the first push the stack is empty and $sp holds 7FFFEFFC (🟢 is the stack pointer, and untouched memory on this machine reads zero):

addressvalue
0x7FFFEFF400000000
0x7FFFEFF800000000
0x7FFFEFFC🟢 00000000

The addi drops $sp to 0x7FFFEFF8 and the sw writes there:

addressvalue
0x7FFFEFF400000000
0x7FFFEFF8🟢 11111111
0x7FFFEFFC00000000

The second pair drops it another 4 and writes underneath:

addressvalue
0x7FFFEFF4🟢 22222222
0x7FFFEFF811111111
0x7FFFEFFC00000000

Then the two pops read them back in the other order, so $t2 gets 22222222 and $t3 gets 11111111, and $sp climbs back to 7FFFEFFC. Last in, first out, and both values are still in memory afterwards: popping moves the pointer and erases nothing.

Type 7FFFEFF0 in the memory panel's address box after running and both words are still there. The Stack tab of the memory panel is already looking at that region.

One adjustment, several stores

Nothing says a push has to be one word. Move $sp once by as much as you need and reach the room with different offsets, which costs one instruction instead of one per value.

All three registers end at the values they started with. While they are saved, $sp is at 0x7FFFEFF0 and the stack holds:

addressvaluereached asregister
0x7FFFEFF0🟢 000000010($sp)$s0
0x7FFFEFF4000000024($sp)$s1
0x7FFFEFF8000000038($sp)$s2

The offsets are yours to choose and the only rule is that the same one is used to save and to restore. This is exactly what a subroutine does on entry and exit, and "jal, jr and the calling convention" writes that out.

Room of your own

The other use of the stack is space. addi $sp, $sp, -16 takes sixteen bytes, which you then reach as 0($sp), 4($sp), 8($sp) and 12($sp), and addi $sp, $sp, 16 gives them back. Nothing allocates it and nothing checks it: the stack is memory, and $sp is the only record of which part of it is yours.

$t4 comes out at 10 and $t5 at 13. While the loop is running the four words are at 0x7FFFEFEC to 0x7FFFEFF8, and after the last addi they are still there and no longer yours: the next thing that takes room gets the same addresses and writes over them.

That is what "the stack is memory and the pointer is the only record" means in practice. Reading below $sp reads whatever the code that used that room before you left behind.

Give every byte back

A subroutine that moves $sp and does not move it back leaves the caller's stack pointer somewhere else, and every offset the caller had into the stack is wrong from then on. Worse, the return address of a call is on the stack too, which the next lecture is about, so a mismatched adjustment makes a jr $ra jump to a number that was never an address.

The rule is one line: whatever a piece of code subtracts from $sp, it adds back before it hands control on. Write the two addi instructions at the same time, before you fill in what goes between them.

There is nothing to stop you breaking it. $sp is register $29 and add $sp, $sp, $t0 assembles as happily as any other addition. The Stack tab of the memory panel and the $sp row of the registers panel are how you check.

Your turn

The test starts $t0 at 0x11111111 and $t1 at 0x22222222, and wants them exchanged. Do it through the stack, without a third register.

Show solution

The second one starts $s0, $s1 and $s2 at 1, 2 and 3, and the three li lines in the middle are not yours to change. Save the three registers before them and put them back afterwards, moving $sp once each way.

Show solution