Thursday, October 17, 2024

ALPP 02-17 -- Unsteady Footing -- Stack Frame for Single Stack: 6801

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

(Title Page/Index)

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.

As I said, we really shouldn't be trying to tackle this problem this early in the tutorial, which is yet another reason I didn't want to show how to use stack frames.

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

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.


(Title Page/Index)


 

 

 

 

No comments:

Post a Comment