Push arguments to stack

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

User avatar
Bregalad
Posts: 8056
Joined: Fri Nov 12, 2004 2:49 pm
Location: Divonne-les-bains, France

Re: Push arguments to stack

Post by Bregalad »

Actually the more I'm thinking about it, the more I think dougeff's idea is clever and desserves a second thought. When there is 3 bytes or less worth of argument there's no need to discuss and it's obvious they should be passed by registers. But when there's more? Passing by zero-page is logical, but the "lda value / sta zeropage" is long and tedious. "lda value pha" takes less bytes for the same thing.

We also have to consider what will stay in the function. In many case, an argument is used only at once specific place, and then it can be used with a simple "pla" instead of "lda value", saving one byte.

There is however a overhead, we need a "jsr" and a "jmp" to call a function instead of just a "jsr". This creates a 3 bytes overhead. So even when no "C" or re-entrency or whathever is considered, using stack as argument passing can be a way to save bytes, even if it doesn't save time.

As a such it will save 2 bytes/argument (one in the caller, and one in the callee) but waste an overhead of 3 bytes. So it's only worth it if 2 or more arguments bytes are passed on the stack. This is assuming arguments are used once in the function. If they're used at many places, then there's no fixed rules, but they're definitely better in a ZP variable anyway. The methods are not exclusive, it could use stack passing as ZP passing.
The alternative, for me, would be to load #1 in A, #2 in X, and #3 in Y, and further arguments into zero-page variables.
This is, indeed, not an "alternative" but the most standard way to do things. Also don't underestimate the carry flag, extremely useful as both an input and output parameter :) A good 1/2 of my assembly function use the carry flag for something as either input, output or both.
Garth
Posts: 246
Joined: Wed Nov 30, 2016 4:45 pm
Location: Southern California
Contact:

Re: Push arguments to stack

Post by Garth »

I know this topic is a year old, but the material is always valuable.

If you need more than just A, X, and Y, passing parameters via the page-1 hardware stack or a virtual stack in ZP or elsewhere in memory can work very well and have major benefits over using static variables. The 6502 stacks treatise particularly addresses these things in detail in the following sections:

See also:
http://WilsonMinesCo.com/ lots of 6502 resources
User avatar
dougeff
Posts: 3079
Joined: Fri May 08, 2015 7:17 pm

Re: Push arguments to stack

Post by dougeff »

I came up with a better method for pushing arguments to the hardware stack.

Basically, you have an indirect address in the zero page, the high byte is always 1.

Let's say, you push 5 variables LDA PHA, then get the stack pointer TSX, and store that as the low byte of an indirect address STX. Now, when you JSR, you can access the argument any time, just load Y with it's relative position, and LDA (zp), Y.

Just before the subroutine, you can define these as constants, and reference them by name inside the subroutine...

xPosition = 0
yPosition = 1

....later...

LDY #xPosition
LDA (stackPointer), y

After you RTS, you should pop all values off the stack, just in case it's burying a return address.

(I haven't tested this, I might be off by 1 or something).

This would also work, for managing your own stack in the RAM (similar to how cc65 does it).
nesdoug.com -- blog/tutorial on programming for the NES
Garth
Posts: 246
Joined: Wed Nov 30, 2016 4:45 pm
Location: Southern California
Contact:

Re: Push arguments to stack

Post by Garth »

That works; but note that if you do that, nesting routines requires that you push the ZP indirect address onto the stack and restore it at the end (in addition to pushing and pulling the index register). LDA abs,X does not require any variable space, and it takes one less cycle than LDA (ZP),Y. In the "LDA abs,X" (or other instruction with that addressing mode), the "abs" will typically be $101, $102, $103, etc..

Be sure to look at the pages mentioned about using a separate data stack in ZP too. It avoids certain complications that you'll run up against when using the hardware stack for passing and manipulating temporary data. It may sound like a sacrilege to use precious ZP for a separate stack for data; but it's surprising how little ZP space you actually need for it (I've measured it in a stack-intensive application), and keep in mind that it reduces the need for other ZP variables too, thus paying for itself. ZP offers more addressing modes, a valuable one being (ZP,X) when you have an address on the data stack.

Then of course there's also the technique of having the subroutine use the return address to find data that immediately follows the JSR, and adjust that return address so the processor skips over the data and doesn't try to execute it as if it were instructions. The data space following the JSR is usually a constant (like a string for example), but if it's in RAM, it can be variable data as well. It's all discussed in the pages linked above.
http://WilsonMinesCo.com/ lots of 6502 resources
User avatar
dougeff
Posts: 3079
Joined: Fri May 08, 2015 7:17 pm

Re: Push arguments to stack

Post by dougeff »

LDA abs,X. Interesting. Let me see if I can pseudo code that...

LDA #var 3
PHA
LDA #var 2
PHA
LDA #var 1
PHA
TSX
STX stackP ;stack pointer
JSR subR
PLA
PLA
PLA ;restore original stack position


;blah

var1 = $0100
var2 = $0101
var3 = $0102

subR:
LDX stackP
LDA var1, X ;to access var1
...
LDA var2, X ;to access var 2
...
LDA var3, X ;to access var 3
RTS


I'm not sure this is preferred to any other method. Personally, I've been using zp variables (called temp1, temp2, etc) to pass to subroutines.
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
rainwarrior
Posts: 8734
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Push arguments to stack

Post by rainwarrior »

dougeff wrote:I'm not sure this is preferred to any other method.
I thought abs, X was the standard way to access stack variables on 6502.

Also didn't pubby already suggest it in the second post in this thread? (Maybe it's a bit distant now, because of the 1 year bump...)
User avatar
dougeff
Posts: 3079
Joined: Fri May 08, 2015 7:17 pm

Re: Push arguments to stack

Post by dougeff »

There's not a lot of discussion on passing variables on the stack.

I see this topic, from 2009

viewtopic.php?f=2&t=5558

(Bregalad says "lda $100 + wathever,X")

Which references this...

viewtopic.php?t=5491

(Disch says "tsx
lda $101,X "

And this...(also 2009)

viewtopic.php?f=2&t=5038

It seems abs, x...is popular.

Another here...(2013)

https://forums.nesdev.com/viewtopic.php?f=2&t=10521
nesdoug.com -- blog/tutorial on programming for the NES
Garth
Posts: 246
Joined: Wed Nov 30, 2016 4:45 pm
Location: Southern California
Contact:

Re: Push arguments to stack

Post by Garth »

dougeff wrote:LDA abs,X. Interesting. Let me see if I can pseudo code that...

LDA #var 3
PHA
LDA #var 2
PHA
LDA #var 1
PHA
TSX
STX stackP ;stack pointer
JSR subR
Leave the TSX for the subroutine, so it is only done there and not in every place that calls it. The $1XX numbers will be a little different because of stepping over the return address, but that doesn't add any overhead. If you need to save and restore X, that can be done in the subroutine too. You'd normally push it onto the stack before doing the TSX, which will again change the $1XX numbers (but it's still no problem).
var1 = $0100
var2 = $0101
var3 = $0102

subR:
LDX stackP
LDA var1, X ;to access var1
...
LDA var2, X ;to access var 2
...
LDA var3, X ;to access var 3
Yes; but var1, var2, and var3 in the subroutine will be local variables, separate (and for clarity, probably deserving different names) from var1, var2, and var3 above in the calling routine. You can do all the instructions on them that have the abs,X addressing mode, like ADC, ROR, ORA, STA, INC, LDY, etc..
I'm not sure this is preferred to any other method. Personally, I've been using zp variables (called temp1, temp2, etc) to pass to subroutines.
Then you have to be awfully careful where they get used so that one routine doesn't use one of the temporary variables and overwrite a value that another pending routine still needs. Been there, done that. The better use you can make of the stack(s), the more you can stay out of that kind of trouble, and the fewer global variables (ZP or otherwise) you'll need.

In the book "Thinking Forth" by Leo Brodie (which is really more about programming philosophy, and its material is not just for applying Forth), there's a section called, "The Problem With Variables." On page 211 there's a cute cartoon I'm tempted to scan and post here (although I'm sure it would be a copyright violation) showing a young man in a body cast in a hospital bed, and a young, slim, curvy woman explaining to him, "Shot from a cannon on a fast-moving train, hurtling between the blades of a windmill, and expecting to grab a trapeze dangling from a hot-air balloon...I told you Ace, there were too many variables!"

Another good reason to keep it on the stack and avoid excessive variable usage is re-entrancy. If the subroutine gets interrupted by an IRQ or NMI, and the ISR needs that subroutine, keeping it re-entrant by having the stack-based local variables will keep you out of trouble. Recursion is possible too (where the subroutine calls itself, over and over until the exit condition is met so it can unwind its way out), but most users here are pretty unlikely to even use recursion.
http://WilsonMinesCo.com/ lots of 6502 resources
Garth
Posts: 246
Joined: Wed Nov 30, 2016 4:45 pm
Location: Southern California
Contact:

Re: Push arguments to stack

Post by Garth »

dougeff wrote:Let me see if I can pseudo code that...

LDA #var 3
PHA
LDA #var 2
PHA
LDA #var 1
PHA
For conciseness (since I'm a macro junkie), you could make macros to synthesize the 65816's PEA, PEI, and PER instructions (which stand for "Push Effective Absolute," "Indirect," and "Relative." (This is covered also in the 6502 stacks treatise, chapter 13, "65816's instructions and capabilities relevant to stacks, and 65c02 code which partially synthesizes some of them.") For the above, PEA is the closest. The 65816's PEA instruction always pushes a 16-bit quantity (and does not affect the processor registers); but you could write a macro that does nearly the 8-bit 6502 equivalent, and call it perhaps PEA8, so you would have:

Code: Select all

        PEA8  var3
        PEA8  var2
        PEA8  var1
and each PEA8 would assemble LDA#, PHA. (If you want it to save and restore A and status, that's extra. The '816 does the whole 16-bit PEA job in five cycles, without affecting processor registers. The closest the '02 could come to doing the same thing with 16 bits is 22 cycles, including saving and restoring A and P.)

Depending on your assembler, you might be able to write a macro to take in an unpredetermined number of parameters and push them all, like this:

Code: Select all

        PEA8   var3, var2, var1    ; Push these ZP variable addresses onto the stack for the subroutine to use.
Now we're down from six lines to one, making the source code more manageable.
http://WilsonMinesCo.com/ lots of 6502 resources
Post Reply