It is currently Thu Dec 14, 2017 10:21 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 24 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: Push arguments to stack
PostPosted: Fri Apr 29, 2016 8:41 pm 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1868
Location: DIGDUG
Do you think this is a good idea? I was trying to think of a way to push a bunch of numbers (passed values) to the stack (before a jsr), but I wasn't sure how to return from subroutine without alot of stack manipulation. But then I thought of this strange idea...

Code:
  jsr Argumentpush
   Jmp Overarguments
Argumentpush:
   Lda #
   Pha
   Lda #
   Pha
   Lda #
   Pha
   Lda #
   Pha
   Jmp ToActualSubroutine ;rather than jsr
Overarguments:


So this would push the return address to stack first, then push all the arguments on top of that. Presumably, the actual subroutine would pull all the arguments, and use them, and then RTS would take you to the 'Jmp Overarguments' which would be the next bit of code.

I'm not sure if this is a good idea, but I have been thinking about this problem for a long time.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Fri Apr 29, 2016 9:14 pm 
Offline
User avatar

Joined: Thu Mar 31, 2016 11:15 am
Posts: 219
You don't need "Overarguments" if you lift "Argumentpush" out of the subroutine and put it somewhere else.

The stack manipulation technique doesn't seem too bad (see below), but I like your method better. Maybe there's a better way though. What does CC65 do for arguments?
Code:
; Push the arguments
lda #1
pha
lda #2
pha
lda #3
pha
jsr subroutine
; Pop the arguments
txa
axs #5
txs
rts

subroutine:
tsx
; Access the arguments without popping
lda $0100-3,x
lda $0100-4,x
lda $0100-5,x
rts


Last edited by pubby on Fri Apr 29, 2016 9:22 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Fri Apr 29, 2016 9:20 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 3192
Location: Mountain View, CA, USA
I've read this 3 times and I don't quite understand what the "problem" is that you're trying to solve (and the code doesn't really indicate to me anything either).

I keep wanting to say one of these things, but I don't know what's applicable:

a) it sounds like what you're wanting is a jump table (e.g. jmp ($addr))
b) it sounds like what you're wanting is to push the return address (minus 1 -- look up how JSR/RTS works in actual detail) of the next routine to go to so that you can go there using an RTS (i.e. avoids the use of JMP)
c) it sounds like what you're wanting is to not use the stack for storing this data at all, but use RAM or zero page instead.

(b) and (c) seem most likely. The negative to (b) might be that depending on how you do this, you may have to deal with handling 16-bit numbers (e.g. a CLC/SBC #1 might not be enough; consider addresses that are right at a page boundary, e.g. $9000-1 = $8FFF). The negative to (c) is that a bunch of STAs take more time (cycles) than a bunch of PHAs (speaking generally here), and STAs are also larger (PHA is 1 byte, STA zp is 2 bytes, STA abs is 3 bytes).

Possibly an amalgamation of one or more of the above proposals will suffice.

P.S. -- Be aware pubby's above explanation uses unofficial opcodes (axs #5).

What you're dealing with (again, I'm not sure what all the issue here is so I'm making some assumptions) is a lot easier on 65816. There are addressing modes that can help with this (stack-indexed, and things like jsr ($addr,x)), and opcodes that can help with this (PEA/PEI). Even the 65c02 has some things that can make one of those easier (e.g. jmp ($addr,x)).


Top
 Profile  
 
PostPosted: Fri Apr 29, 2016 9:25 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10164
Location: Rio de Janeiro - Brazil
pubby wrote:
You don't need "Overarguments" if you lift "Argumentpush" out of the subroutine and put it somewhere else.

That could hurt readability though, since the values would be written somewhere else than place where they're used. In ca65 you could solve this by creating a segment for these "trampolines", and temporarily switch to them just to setup the parameters.


Top
 Profile  
 
PostPosted: Fri Apr 29, 2016 9:27 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 6520
Location: Seattle
pubby wrote:
What does CC65 do for arguments?
CC65 uses two bytes of zeropage for a stack pointer in software, that is used for all the normal C stack-shaped things, while subroutine calls still use JSR and RTS


Top
 Profile  
 
PostPosted: Fri Apr 29, 2016 9:31 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10164
Location: Rio de Janeiro - Brazil
koitsu wrote:
I've read this 3 times and I don't quite understand what the "problem" is that you're trying to solve

He's talking about passing arguments to subroutines through the stack, but when you push the arguments first and then JSR, you can't simply PLA the parameters within the subroutine because the return address is in the way. What he did was find a way to push the return address first, and then the arguments. It's pretty clever, and some may prefer this over manipulating the stack pointer, which is a more advanced and dangerous technique.


Top
 Profile  
 
PostPosted: Fri Apr 29, 2016 9:36 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 3192
Location: Mountain View, CA, USA
tokumaru wrote:
koitsu wrote:
I've read this 3 times and I don't quite understand what the "problem" is that you're trying to solve

He's talking about passing arguments to subroutines through the stack, but when you push the arguments first and then JSR, you can't simply PLA the parameters within the subroutine because the return address is in the way. What he did was find a way to push the return address first, and then the arguments. It's pretty clever, and some may prefer this over manipulating the stack pointer, which is a more advanced and dangerous technique.

Thank you -- this makes sense now! In this case, since we're sharing approaches/solutions: I tend to just use some ZP or RAM space for this situation (i.e. option (c) in my examples).


Top
 Profile  
 
PostPosted: Fri Apr 29, 2016 9:37 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5898
Location: Canada
I don't see anything particularly wrong with the style.

PLA is 4 cycles, LDA $100+N, X is also 4, but also has the overhead of TSX and consuming the X register. PLA is also 2 bytes smaller. I think these are valid but minor reasons to consider this technique, iff you need your parameters on the stack and you can consume them in a specific non-overlapping order. If you've got to store any to RAM, you might as well have just used that RAM to pass the argument.

I find the stack most useful for recursive functions, or inefficiently resolving RAM-overlap between functions. I would not normally be passing parameters on the stack, and usually it wouldn't be in performance sensitive code. The fastest way to pass parameters not in a register is the zero page, as koitsu suggested. This is also what I mean about RAM-overlap; if due to lack of foresight two functions need to use the same RAM variables for parameters and one needs to call the other, I might use the stack to temporarily save them around the call.

I don't think the "RTS trick" is applicable here, it is maybe a similar idea but isn't the same problem, or really applicable to this one.

Anyhow, I don't see anything wrong with it, but I can't think of any good application for it either. A C compiler might have a use for more efficient stack usage, if you were writing one. I don't really see accessing variables on the stack as dangerous or error prone though.


Last edited by rainwarrior on Sat Apr 30, 2016 12:15 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Fri Apr 29, 2016 9:54 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5898
Location: Canada
Something I've considered, that's sort of a similar kind of optimization, is finding a way to minimize the size of argument preamble for repetitive function calls with immediate arguments. This is a size over speed optimization, though, and you really need a large number of arguments and high repetitive usage to make this worth doing. As such, I've yet to actually find it useful- every time I've considered it, it was not a desirable trade.

Code:
.macro QUICK_BLOO a,b,c,d,e,f
jmp :+
.byte a,b,c,d,e,f
:
jsr bloo
.endmacro

; ...

bloo:
    ; fetch return value from stack, find the arguments 6 bytes back (+2 for the jsr instruction minus increment)
    tsx
    lda $100+1, X
    sec
    sbc #<(6+2)
    sta ptr+1
    lda $100+2, X
    sbc #>(6+2)
    sta ptr+0
    ; now we can access the inline arguments like:
    ldy #3
    lda (ptr), Y ; fetch argument 3
    ; ...
    rts


Last edited by rainwarrior on Sat Apr 30, 2016 12:16 pm, edited 2 times in total.

Top
 Profile  
 
PostPosted: Fri Apr 29, 2016 9:55 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10164
Location: Rio de Janeiro - Brazil
I agree with koitsu and rainwarrior, for most NES applications, using ZP for arguments is the simplest and fastest solution, since NES games hardly ever need recursion. Recursion isn't even just about arguments, local variables also have to be unique to each function instance, which is another good reason to avoid it.

RAM collisions between routines that call one another is a real issue though, that you could probably solve by increasing the amount of RAM for arguments and local variables and remapping some things, or by using the stack through techniques such as this.


Top
 Profile  
 
PostPosted: Sat Apr 30, 2016 7:20 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1868
Location: DIGDUG
Quote:
I don't quite understand what the "problem" is


I guess the problem is...would push/pull from the stack be a good way to pass arguments. How could that be done? And, I feel like I finally thought of a way to do it.

(On a side note... when I first read about a 'stack', back in 1990 or so [how to program Apple IIGS book], I thought that programmers would use it to store variables and pass arguments, but I don't find that to be true)

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.

But, I also like the idea of having a set of reused/generic zero-page variables, just for passing variables back and forth from subroutines.

The only time this seems to come up is collision checks.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Sat Apr 30, 2016 7:39 am 
Offline
User avatar

Joined: Sun Sep 19, 2004 10:59 pm
Posts: 1393
pubby wrote:
Code:
; Access the arguments without popping
lda $0100-3,x
lda $0100-4,x
lda $0100-5,x

I'm pretty sure that's wrong - on the 6502, the stack grows downward.

In other words, $0100,X points to where the next byte will be pushed, $0101,X and $0102,X are the return address, and $0103,X / $0104,X / $0105,X are the parameters you pushed.

rainwarrior wrote:
LDA $100-N, X is going to be 5 (unfortunately always a page crossing)

Thus, the above mentioned page crossing is never a concern.

_________________
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.


Top
 Profile  
 
PostPosted: Sat Apr 30, 2016 8:12 am 
Offline
User avatar

Joined: Thu Mar 31, 2016 11:15 am
Posts: 219
Quietust wrote:
pubby wrote:
Code:
; Access the arguments without popping
lda $0100-3,x
lda $0100-4,x
lda $0100-5,x

I'm pretty sure that's wrong - on the 6502, the stack grows downward.

In other words, $0100,X points to where the next byte will be pushed, $0101,X and $0102,X are the return address, and $0103,X / $0104,X / $0105,X are the parameters you pushed.

The axs intsruction should be axs #-4 then... I think. (I obviously didn't test that code!)


Top
 Profile  
 
PostPosted: Sat Apr 30, 2016 10:59 am 
Offline

Joined: Sun May 03, 2015 8:19 pm
Posts: 92
As long as you don't get close to running out of stack space its a reasonable way to do it. What I am describing below only applies if the function you are jumping to has multiple possible return address. Usually it will or it wouldn't be a function.

Push return address - 1 onto stack
Push variables onto stack
Push function address - 1 onto stack
Use RTS to pull the function address off the stack

At function
Pull variables off stack
do function
Use RTS to pull the function address off the stack and return

However like others have said its usually easier to just have a set of temporary "scratch pad variables" in zero page to store the arguments.

The game I've been working on disassembling almost always uses the scratchpad technique.


Top
 Profile  
 
PostPosted: Sat Apr 30, 2016 12:14 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5898
Location: Canada
Quietust wrote:
on the 6502, the stack grows downward.

Wow, sorry, I must have been in a fever dream yesterday. I'll fix my examples...


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 24 posts ]  Go to page 1, 2  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 7 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron
Powered by phpBB® Forum Software © phpBB Group