A Larger Stackframe
Our first stackframe example had a stackframe with only one entry in it. Now we'll look at an example of
a larger stackframe. We'll make one with four entries.
You can click on the diagram to change its orientation to suit your taste. One case matches the memory
maps in the book: lower addresses at the bottom and higher addresses at the top. The other case
matches the official CSCI250 memory map that has lower addresses at the top and higher addresses
at the bottom.
The pictures here show a small area of memory just before and just after a call to a subroutine named subr.
The code between the pictures is what effects the changes between these pictures;
this code is BOFB for subr.
- We are doing this because four registers need to be saved: $ra, $a0, $s0, and $s1.
- The first instruction subtracts 16 from the stack pointer. We are making room for 4 new entries on the
stack. Each entry is a whole word, i.e. 4 bytes, so we are making room for 16 bytes. The stack
grows into lower addresses, so we have to subtract. You should be able to explain why subtracting 16 from
the stack pointer changes it from 0x7FFF-FF24 to 0x7FFF-FF14
- Placing an entry into a stackframe is copying from a register to memory, which is a store operation.
Stack entries are always whole words, so it's a store word.
- Four new entries are going into the stack, so there will be four store-word instructions.
- The registers that will be saved are shown in yellow. Their contents will be copied into the
stackframe by the code shown. You can see where they were copied to by comparing values between
the registers and the stackframe.
- The pictures show actual (made-up) addresses for the memory locations shown. These addresses are
near the top of user memory, so they are plausible. In a real situation, the values of these
addresses would be determined by the size of the stack when the program reached the point that
we are depicting. These actual addresses are irrelevant except that they allow you to verify
that the base-displacement address modes really do address the locations that we claim they do.
You have to know how base-displacement mode works to do this. In every case the address of the
location where a register's value is placed is the sum of the value in the stack pointer and
the displacement used in the instruction that copied it there. Here's an example of this: $s0
got copied to 0x7FFF-FF1C. The second sw is the instruction that did that. Its displacement is 8.
The value in the stack pointer is 0x7FFF-FF14. Add 8 to that, and you get 0x7FFF-FF1C. QED
- At this point there are two stackframes being shown at the top of the stack in different shades of green.
The one on top of the stack is darker green; the lighter green one is just under it.
Remember: the stack grows toward smaller addresses, so whether the stackframe goes up or down from
the position of the stack pointer in the picture depends on which of the two views you are looking at.
The thing that never varies is that the part of memory that is in use by the stackframes is green, and
the part that is not in use is lavender. Another point of consistency is that the green part is always at
positive offsets from the stack pointer, and the lavender part is at negative offsets.
- There are some restrictions on the offsets that you can use when accessing your stack frame:
- never use a negative offset
- always use a multiple of 4
- the offset must fall into an allowed range: 0 is at one end of the range; the other end
of the range is 4 less than the amount you subtracted from $sp to create the stackframe
- in the example on this page we subtracted 16 from $sp, so the allowed offsets are 0, 4, 8, and 12
- subtracting 16 creates a 16-byte stackframe, i.e. 4 words
- our 4 offsets 0, 4, 8, 12 access all of the words in the new stackframe
- you may be tempted to write 16($sp) in code using this stackframe;
yield not to temptation, for yielding is sin
16($sp) is in the previous stackframe and is not to be touched by us.
- It doesn't matter where a particular register's copy goes in the stackframe. In this example,
there are 4! different possibilities. I picked one of them: the one that matched the
order of the yellow boxes that show the registers. Anything else would have made
all this harder to understand. However, just for consistency, I like to save $ra at 0($sp).
- The order in which the sw instructions appear in the code does not matter either.
Again, I just matched the yellow boxes.
- It does matter that the place a register is restored from is the same as the
place that it was saved to.
-
Here's a procedure that I use to help get this right:
- copy the BOFB code and paste it at the end of the subroutine
- cut (not copy) the addi instruction from this copy and paste it back in at the end
- remove the label from the copy of the addi and remove the minus sign from its immediate operand
- change all the sw instructions to lw
- add "jr $ra" at the very end
- This keeps the registers and their correct offsets together.
- The last box shows the EOFB code that results.
- Once this code is executed the stack is back as it was in the first picture. (Some values in the
purple part will be different, but that is of no consequence: those locations will not be used
again until a new stackframe is pushed onto the stack. When that happens, new values will be stored
in those locations before they are read from. Another way to put this is that the actual values
in the purple section never matter. They will always be overwritten before the
are read.)