Tuesday, October 15, 2024

ALPP 02-XX -- Backsliding -- Stack Frames

Botched start, keeping for notes

Here's where you want to go instead: https://joels-programming-fun.blogspot.com/2024/10/alpp-02-14-one-foot-on-beach-one-in-surf-frame-pointer-split-stack.html.

Balancing on Some Other Beach --
Stack Frames
and Debugging on the 6800

(Title Page/Index)

 [This was from the start to the split-stack frames.]

Didn't I say we weren't going to look at this yet?

Talking about balancing the stack when we have just begun using it wasn't boring enough?

I'm not going to complain if you skip ahead to getting binary output.

Oh, well.

There's a philosophy that you should allocate all the memory a function will need when you start a function, and deallocate it all when you finish. 

It's not a meaningless philosophy.

And it's not really limited to the single-stack run-times, but it is the only way to keep a single stack runtime sane when parameter lists become longer and local variables increase.

Considering the previous chapter, where we calculated a predicted value for the stack pointer at the end of the function. To do that, we had to know what parameters we were accepting, and what we were returning. If, in addition to that, we know what temporary variables we will be using, we can allocate it all at once and not worry about what is happening to the stack in odd corners of the code.

And if we can do that, we think we can dodge the return address.

There are a number of problems with this ideal. In practice, we have limited our return values to a single value that we can return in a register, or maybe in a few registers. Anything bigger than that, we tend to follow the practice of C, passing a pointer to the place we want the value returned to to the function, and defining the function to work through the pointer.

It's not too hard, to understand, conceptually, but it gets clumsy, and it creates a bottleneck.

If you're already thinking a stack frame sounds like a lot of work to set up by hand, you're right. But compilers can work out the details and hide them from us. 

And, to a certain extent, in languages like Pascal and Ada, prevent us from overwriting return addresses.

Enough of the sales job. Let's look at a "typical" stack frame on the 68000, since the 68000 is designed to handle them. 

There are several ways to set up stack frames, I'll pick two that will work okay on our four initial targets. 

Two?

Yeah. I'm going to show you a stack frame on a split stack runtime, as well.


 *******************

[The following was from a false start on the single-stack stack frames on the 6801, which still hasn't gotten anywhere real.]

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. 

It can be done. Sort-of.

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 work it. Your frame pointer points to the top of the frame, and the link to the caller's frame. The stack pointer points to the bottom.

 

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 they didn't do that. And it still would have been awkward.

Adjusting the frame pointer to point to the bottom instead of the top would be workable, but then you either need to have an explicit size to the frame, leading to more run-time calculations, or you need to track both the bottom and top. That might work better than providing low-level routines to add and subtract offsets from pointers.

A lot of times, a small number of INX or DEX instructions will do the job. If we limit the size of a stack frame to 16 bytes, utility subroutines of nothing but INX or DEX instructions and multiple entry points could do the job. And ADDDX instruction will be faster on the 6801 than signed ADDBX.

Using two frame pointers, a frame base pointer and a frame link pointer, should help a lot, but the stack allocation and deallocation will be weird. 

Oh, and using D and X all over the place to calculate offsets will make it impossible to put return values in D and X, so we're going to allocate 4 bytes of return value variable at the top of the stack.

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 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:

*******************

Dead code from concrete example, to cause me confusion later:

ADD32	MOVE.L	4(A6),D7
	ADD.L	(A6)+,D7
	MOVE.L	D7,(A6)
	RTS

* in single-stack stack frame
ADD32	LINK	A6,#0
	MOVE.L	4(A6),D7
	ADD.L	(A6),D7
	MOVE.L	D7,8(A6)
	UNLK	A6
	RTS


TEST	LINK	A6,#4
	LEA	-4(A7),A7
	MOVE.L	#$12345678,4(A7)
	MOVE.L	#$9ABCDEF0,0(A7)
	BSR.W	ADD32
	LEA	8(A7),A7
	MOVE.L	(A7)+,-4(A6)
  

 

 


(Title Page/Index)


 

No comments:

Post a Comment