This is going to sit here for a while, maybe a long while. Maybe I'll get up the energy and interest to work it through sometime in the future, and make it useful. For now, it's just going to sit here like a rubber brick on the bottom of the pool.
Unsteady Footing --
Stack Frame for Single Stack:
6801
Single-stack stack frames on the 6809 and 68000
seem almost reasonable. Almost.
But with the conflicts between uses of the stack, index, accumulator, offset calculations, and so on, it's tempting to just punt on the 6800 and 6801.
I'm sure it can be done. Sort-of.
But you'd be better off just moving ahead to numeric output in binary on the 6800, rather than getting lost down this rabbit hole.
But sometimes it's okay to go down rabbit holes, with a bit of scratch paper and a pencil and a big eraser. If you proceed in this chapter, keep that in mind. (Also, bear in mind that this whole thing is an exercise in techniques I do not recommend.)
Negative index offsets are a problem with the 6800 and 6801. The instruction set provides for positive constant offsets, but not negative.
This means you have to waste time calculating addresses at run time to get to things in the stack frame that are below the frame pointer. And that's the way the 68000's LINK and UNLK instructions construct the stack frame.
It's possible to have the frame pointer point to the bottom of the frame, but
then you need a frame size variable, and run-time address calculation, to get
to the top. Or you need to track the top of the frame with a separate
top-of-frame pointer, and that also gets tricky.
So your frame pointer points to the top of the frame, and to the link to the caller's frame. The stack pointer points to the bottom.
And you usually don't offset the stack pointer. But you can.
That still leaves allocating the new stack frame as a problem.
But we did that with the ADDBX low-level utility subroutine for the
split-stack case.
Including an SBX (subtract B from X) instruction to parallel the ABX (add B to X) in the 6801's extended instruction set would have helped, but Motorola didn't do that. And it still would have been awkward.
Signed ADDBX is too slow to use for indexing every variable.
So adjusting to indexing into the stack frame from the stack pointer seems it might
be the best option. Maybe. It gets tricky when you are pushing parameters before a
call, but we can figure something out for that.
Thinking about speeding up ADDBX, when the adjustment is just two bytes, two
DEX or INX in a row is two bytes and 8 cycles, so direct increment or decrement is not bad for that. And a list of
INX or DEX instructions that you can jump into the middle of could be a
reasonable trade-off for up to 8 bytes of offset. And adding D instead of just
B on the 6801 is going to be faster on the 6801.
So I really should punt for now, and pick this back up waaaaay down the road.
But I'm feeling obstinate. Here goes nothing:
We are going to add a BFP bottom of frame pointer. This will allow us to not
worry about how many parameters have been pushed. Maybe. The calling routine
will have to restore it after a call.
Now, with all the way we are thrashing D and X, we are going to have to do
something about the return value. The calling routine will allocate space for
two or four bytes of return value just above the parameters, and it will grab what has
been stored there and store it in D and X just after the return. I think that
will work.
With this protocol, here's how the stack looks before the first routine pushes parameters for the first call:
nnnn |
||
RA_0 | ||
FA_0 | <= | _FP_ |
T1_1 | ||
T1_2 | <= | _RSP_,_BFP_ |
???? |
And here's how it looks after it pushes parameters, including the return value parameters, V2_1 and V2_2:
nnnn
RA_0
FA_0
<=
_FP_
T1_1
T1_2
<=
_BFP_
V2_1
V2_2
P2_1
P2_2
<=
_RSP_
????
????
And after the call, but before executing the protocol:
nnnn
RA_0
FA_0
<=
_FP_
T1_1
T1_2
<=
_BFP_
V2_1
V2_2
P2_1
P2_2
RA_1
<=
_RSP_
????
And here's how it looks after it executes the protocol:
nnnn |
||
RA_0 | ||
FA_0 | <= | _FA_1 |
T1_1 | ||
T1_2 | ||
V2_1 | ||
V2_2 | ||
P2_1 | ||
P2_2 | ||
RA_1 | ||
FA_1 | <= | _FP_ |
T2_1 | ||
T2_2 | ||
T2_3 | <= | _RSP_,_BFP_ |
???? |
So BFP will mostly be just tracking the return stack pointer, and basically just be used to access the current stack frame.
Still easy to walk.
Still easy for a rogue routine to walk all over the frame links and return addresses.
BFP will be another direct page variable that we will have to save and restore on task switch, along with FP and XWORK.
The code below should be considered scratch work and a lot of hand-waving. It's too abstract and will not work as advertised. It might be useful as a guide to implementing stack frames on the 6801 if they absolutely must be implemented, but it might not.
* calling protocol for single-stack stack frames on 6801
* with FP (in the direct page) as the frame pointer (no PSP)
* BFP (in the direct page) to track the bottom of the stack,
* and routines saving the frame pointer
* and allocating the new frame on entry.
* The return value will be allocated above the parameters,
* because we have to use D and X all over the place,
* and the calling routine will get the return values and restore BFP.
*
* More variables in DP which must be save and restored on task switch
* ORG SOMEWHERE_IN_DP
* BFP RMB 2
* RETADR RMB 2
*
* LINK general, size in D, negative for allocation
* Uses RETADR in the direct page, which must be saved and restored on task switch
LINKG PULX ; grab the return address
STX RETADR
LDX FP
PSHX ; old frame pointer saved
TSX ; do not save S directly
STX FP ; we want to use TOS as a pointer
ADDD FP ; allocate
PSHB
PSHA
PULX
TXS ; allocation complete
STX BFP
LDX RETADR
JMP 0,X ; return
*
INX8 INX
INX
INX6 INX
INX
INX4 INX
INX
INX
INX
RTS
*
DEX8 DEX
DEX
DEX6 DEX
DEX
DEX4 DEX
DEX
DEX
DEX
RTS
*
ADDDX STX XWORK
ADDD XWORK
STD XWORK
LDX XWORK
RTS
*
FRAME_SIZE SET SOMETHING ; ROUTINE1's frame size
* Could use a faster entry if FRAMESIZE <= 8 bytes:
ROUTINE1
LDD #-FRAMESIZE
JSR LINKG
* ...
LDX PARAMETER1
PSHX
LDD PARAMETER2
PSHB
PSHA
JSR ROUTINE2
* If 1 parameter:
* INS
* INS
* TSX
* IF 2 to 4 parameters:
* TSX
* JSR INXN ; 4, 6, or 8
* TXS
* if greater:
TSX
LDD #PARAMETER_SIZE
JSR ADDDX
TXS
* ; still has return values between TOS and BFP
* if 2 bytes:
* PULB
* PULA
* STD RESULTVAR_OFFSET,X ; low part
* if 4 bytes:
PULB
PULA
STD RESULTVAR_OFFSET+2,X ; in the case of 4 bytes, high part
PULB
PULA
STD RESULTVAR_OFFSET+2,X ; low part
* ; SP is what BFP should be
TSX
STX BFP
* ...
LDX FP ; restore/deallocate
TXS
PULX
STX FP
RTS
* and the caller moves results where they go in its own frame
* immediately after dropping the parameters that are no longer in use.
* ...
*
*
* called routine entry protocol for single-stack stack frames
* with U as the frame pointer,
* and routines saving the frame pointer
* and allocating the new frame on entry.
* (Caller, of course, pushes call parameters before call,
* and pops return parameters to where they go on return.):
FRAME_SIZE SET SOMETHING ; ROUTINE2's frame size
* Showing faster entry for FRAMESIZE <= 8
ROUTINE2
LDX FP
PSHX ; old frame pointer saved
TSX ; do not save S directly
STX FP ; we want to use TOS as a pointer
JSR DEXN ; 8, 6, or 4; DEX2 would be in-line
TXS ; allocation complete
STX BFP
* ...
* No code for dealing with large return values. Can't do that.
* Large return values have to be dealt with outside of the stack frame.
* ...
LDX BFP
LDD PARAMETER2OFFSET+FRAMESIZE,X ; access the second parameter
* ...
SUBD PARAMETER1OFFSET+FRAMESIZE,X ; access the first parameter
* ...
LDX FP ; get the caller's frame pointer
LDX 0,X
ADDD CALLER.PARAMETERNOFFSET+CALLER.FRAMESIZE,X ; or something -- positive offset
* ...
LDX FP ; restore/deallocate
TXS
PULX
STX FP
RTS
*
No. I'm not going to claim this is good code, or even good theorizing.
Maybe, after I let it sit for a year or two, I'll come back to it. For now, it's abandoned. We didn't need to go down this path anyway.
It took well less than a year. A couple of weeks or so? I've put together a concrete example of how this could actually work in some easier cases, starting with the
- 68000 (next chapter),
- 6809,
- 6801, and
- (I'll get there) 6800.
You may prefer to get some practice with address math before you take that on, however.
Or, if you find that puts you to sleep, and have to come back to it, go ahead and look at getting numbers output in binary for now.
No comments:
Post a Comment