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 overview of this topic is in Assembly basics. The same topic in M68K, Z80.
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, -4and thenswat0($sp), - a pop is
lwfrom0($sp)and thenaddi $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):
| address | value |
|---|---|
0x7FFFEFF4 | 00000000 |
0x7FFFEFF8 | 00000000 |
0x7FFFEFFC | 🟢 00000000 |
The addi drops $sp to 0x7FFFEFF8 and the sw writes there:
| address | value |
|---|---|
0x7FFFEFF4 | 00000000 |
0x7FFFEFF8 | 🟢 11111111 |
0x7FFFEFFC | 00000000 |
The second pair drops it another 4 and writes underneath:
| address | value |
|---|---|
0x7FFFEFF4 | 🟢 22222222 |
0x7FFFEFF8 | 11111111 |
0x7FFFEFFC | 00000000 |
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:
| address | value | reached as | register |
|---|---|---|---|
0x7FFFEFF0 | 🟢 00000001 | 0($sp) | $s0 |
0x7FFFEFF4 | 00000002 | 4($sp) | $s1 |
0x7FFFEFF8 | 00000003 | 8($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.