A diversion into programming
Before we get too deep into the design, it's perhaps worth looking at how we might expect to use the code.Many languages use something similar to the way C works to encapsulate data locally and to pass and return items on the stack. I'm not proposing (yet) any languages beyond an assembler but there's no reason not to, and plenty of reasons to write assembler in a similar way. Here's how it might work (and remember that the software has to agree internally how it's going to implement this):
A (hypothetical) routine is passed a couple of sixteen-bit parameters and passes one sixteen bit parameter back. It uses three sixteen-bit variables for local storage. Before calling the routine, the caller has to save those variables on the stack:
[param1]
[param2]
[ ] <-- stack pointer points here
Once the routine is called, that changes to include the return address:
[param1]
[param2]
[return]
[ ] <-- stack pointer points here
So if we want to access param1, we need to add four to the stack pointer's value; to access param2 we need to add two. So we might envisage a stack-pointer plus constant addressing mode.
It works to access local variables, too, provided we don't move the stack:
[param1]
[param2]
[return]
[local1] <-- stack pointer points here
[local2]
[local3]
And now we can access the parameters by *adding* to the stack pointer, and access the local variables by subtracting from it. Which is fine, but there's a problem: what if we want to push some temporary value on the stack? Two things happen - the first being that the stack pointer moves one byte lower - so the offsets to the parameters and local variables all need adjusting by one - and worse, the byte we just saved has been thumped. Not helpful...
It's worse if we do something really radical, like calling a subroutine. In that case, the first thing we do is save any parameters, and then call the routine, leaving a return address. What the routine does we don't know but whatever happens, our local variables have been zapped.
So how do we resolve that? Let's have a look at a slightly more sophisticated option that addresses both of these issues. We call the routine as before, pushing the parameters on the stack:
[param1]
[param2]
[return]
[ ] <-- stack pointer points here
Now we save the XY register.
[param1]
[param2]
[return]
[XY ]
[ ] <-- stack pointer points here
Now we copy the stack pointer to the XY pair, and subtract from the stack pointer so it moves past our local variables
[param1]
[param2]
[return]
[XY ]
[local1] <-- XY points here
[local2]
[local3]
[ ] <-- stack pointer points here
Now it's clear that whatever we do with the stack can't affect our local variables. We can push new data onto the stack if we need to, pop it off again, and call subroutines. Here are the addresses, relative to the XY pointer:
[param1] <-- XY+8
[param2] <-- XY+6
[return] <-- XY+4
[XY ] <-- XY+2
[local1] <-- XY
[local2] <-- XY-2
[local3] <-- XY-4
[ ] <-- stack pointer points here
So rather than a SP + constant addressing mode, it's more useful to have an XY + constant addressing mode. Why save XY? If we adopt this programming model we need to make sure we don't thump XY which might have been set by the calling routine - and if we call a subroutine ourself, we expect XY to remain constant afterwards. So, we save it.
But what happens when we need to return to the caller? At the moment, our stack pointer isn't pointing at the return address so we have to add to it so it's pointing to the right place. We also need to restore XY. But there's one other thing: we need to sort out our return parameter, and we need to do that before we restore XY since that's the only reference we have.
There are two choices which can be made here: either the caller or the callee cleans up the stack. It's *cleaner* if the callee cleans things up, but it needs a lot of juggling to get things in the right place;
- the return data is written over the first parameter at XY+8
- the return address is copied from XY+4 and stored over the second parameter at XY+6
- XY is restored from XY-2
- 10 is added to the SP so it points at the return address
- and finally, we return
- the return data is written over the first parameter at XY+8
- SP has 6 added to it so it points at XY
- XY restored by popping the stack; now the SP points to the return address
- and return
We don't have to decide which way to go at the moment, so we won't.
No comments:
Post a Comment