Commodore Free Magazine, Issue 81 - Part 14
From
Stephen Walsh@39:901/280 to
All on Sat Jul 5 17:35:01 2014
We're going to begin assembling a Function Library, along with a
stack-based function-calling engine tailored to the Library. It's an "out there" type of project for sure, yet many of us have wished for one at one
time or another in order to make things easier. I won't kid here, either - this turned out to be much harder to explain than I ever thought it would.
Much harder. So please accept my apology now for later - when you're
looking for a way out. Even though we won't get this all finished today,
we will get a good start on it. We'll start today by laying out the memory usage and perceived future needs (the ability to add more functions).
We are going to tackle this using both 6502 and 65816 assembly language,
and we will see how to manage the parts of a modern Stack from two distinct vantage points - 6502 and 65816. We use the term "modern" in the sense
that good examples of 6502 stack use are practically non-existent, and if available, are certainly not published in a very conspicuous place. Most
books have done a horrible job of explaining what the 6502 Stack is and
what it does. Yeah, I read the books, too! Invariably you're left with a
very poor understanding of whatitis, whatever thatsit.
Truthfully, it wasn't until I learned x86 assembly, which meant
understanding how two different stack systems worked, that it finally all started to sink in and make some sense. I then began to wonder why we (the
65x Community) weren't doing some of the really cool things with our stack
that x86 programmers were doing with theirs.
Traditionally, the 65x Stack had been used for an extremely limited list of circumstances, and rightly or not, many programmers "got by" knowing very little about how this highly efficient piece of hardware works. As I was pondering this - the "why not us too?" question - it soon became obvious:
It was just an information gap - and that's all. The information never
made from There to Here.
WHICH BRINGS US TO HERE.
Today our project has it all! - an elegant function-calling mechanism with parameter passing, return values, and local variables, or "locals." Best of all, we're going to use symbolic labels to make the concepts easier to understand and integrate into your own applications. As good assembly
language programmers, we should always be thinking in terms of symbolic
labels anyway. So, Note to hard-coders: Today we will be using Labels and Comments freely. Better strap yourselves in.
- - - - - - - - - - - - - - - - - - -
OUR PROBLEM:
We want to write a function library and need a good way to access the
functions within. Our solution should be something simple to operate (at
the user's end) and easily extendable (on our end). We can assume that,
prior to a JSR/JSL to a single address (C64 = $02A7, SCPU = $020000), the Caller pushed input parameters onto the Stack (in reverse order), loaded
the count (# of parameters) just pushed into .A, and loaded .X/.Y with a Function ID#.
At first glance the procedure to access a function seems fairly involved. However, if no parameters are required, the process boils down to simply loading A, X, and Y, and JSRing. Very little involvement, actually.
Parameter passing is the elegance we talked about earlier. But more
powerful functions often require more parameters. Our springboard will
make parameter usage convenient for the function.
We don't know yet what each function will do - we need a standard way to interact with the function once it is put in place. Our springboard must
pass parameters, return results to the Caller, and process errors. We also must ensure safe return to the Caller's environment. Springboard is just fancy-talk describing a mechanism to safely transport the Caller from its
home environment, interact within the Function environment, then travel
safely back to the Caller's original environment.
* First, our springboard must save any parts of the Caller's environment we intend to alter (for restoration later).
* Next, Function Locator searches the Function Address Table, looking for
an ID match.
* When a match is found, the offset to the function is retrieved from bytes
2-3 of the 4-byte function entry.
* The address is put into a documented address with a JSR (or JSL)
prefixed.
* The function executes, then performs a normal RTS.
* Our springboard restores any environment alterations, clears up the
stack, and returns to the Caller's environment.
Our springboard must act as a liason between the caller and function, so it needs to be easy to operate and powerful, yet fast. The way we approach
the job depends heavily on which processor we are writing for - 6502 or
65816.
- - - - - - - - - - - - - - - - - - -
COMMON WAYS OF PASSING PARAMETERS TO FUNCTIONS
Stack: Parameters are pushed onto the Stack by the Caller, usually in
reverse order, along with any other necessary information, before jumping
to the springboard address. We would like for our springboard to handle
the entire affair, including cleaning up properly afterward.
Inline: Parameters are placed in the code stream following the call to the springboard address. The function parses the in-line params and the springboard handles fixing the Return Address (on the Stack) before the
program resumes execution. Fast, good when memory is tight because of low memory overhead, but it mixes code and data, which can be a problem for ROM
or segmented memory.
Direct Page: An agreed-upon set of shared Zero/Direct Page addresses. Information is transferred using these locations. Overall the fastest
method, it generally (but doesn't have to) relies on the presence of a
rather large contiguous area of memory, a luxury which is not always
available in 6502. Passing parameters in 16-bit mode is easy because
Direct Page is relocatable.
Register: The easiest (and most limiting) method for passing function parameters. It's just A/X/Y and SR flags, such as Carry, for BOOL data.
With this method the function has to deal with parameters immediately
because they are (of course) being held in volatile processor registers.
ADDING DATA TO A STACK FRAME
The Stack is just memory used for very specific purposes. When the
computer first starts up the Stack Pointer will read as set to $FF (or
$01FF for 65816). This is a pointer to the next stack address which will
be "pushed" to. When dealing with the Stack we can calculate locations
using relative addressing, or as offsets from a known location. The known location is always the address SP points to (which means the known location
can and does change as data is added or removed from the Stack). It is
this fact which causes so much confusion.
Let's push three "fake" parameters to "dummy" and see what it does to stack alignment, assuming we began at $01FF.
8-Bit 6502 16-Bit 65816
PARAM1 = $0101 PARAM1 = 1
PARAM2 = $0102 PARAM2 = 3
PARAM3 = $0103 PARAM3 = 5
Stack Data Placement After Pushing
Three Parameters to "dummy"
8-Bit 6502
Addr SP Data Addressed As
$01FF $FF PARAM 3 PARAM3,X / $0103,x
$01FE $FE PARAM 2 PARAM2,X / $0102,x
$01FD $FD PARAM 1 PARAM1,X / $0101,x
$01FC $FC NEW SP $0100,X
$01FB $FB ... ...
$01FA $FA ... ...
16-Bit 65816
Addr SP Data Addressed As
$01FF $01FF PARAM 3 HI 6,S (PARAM3+1)
$01FE $01FE PARAM 3 LO 5,S (PARAM3,S)
$01FD $01FD PARAM 2 HI 4,S (PARAM2+1)
$01FC $01FC PARAM 2 LO 3,S (PARAM2,S)
$01FB $01FB PARAM 1 HI 2,S (PARAM1+1)
$01FA $01FA PARAM 1 LO 1,S (PARAM1,S)
$01F9 $01F9 NEW SP 0,S ($01F9)
$01F8 $01F8 ... ...
$01F7 $01F7 ... ...
In both cases, 6502 and 65816, we were able to turn a stack offset into a label, which means addressing data on the stack can be made easy to
understand.
6502
--- MBSE BBS v1.0.01 (GNU/Linux-i386)
* Origin: Dragon's Lair ---:- bbs.vk3heg.net -:--- (39:901/280)