This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Parameters in sub routines

Parameters in sub routines
by on (#118446)
I tried to google 6502 "subroutine parameters", but nothing useful came up. So, I'm just asking the question here:

Is there a way in 6502 Assembly to use parameters for subroutines?

Something like this:
Code:
MySubRoutine:
  ; Parameter name: loopCounter
  LDX #$00
Loop:
  ; Do something here.
  INX
  CPX loopCounter
  BNE Loop
  RTS

And then you call the sub routine like one of these:
Code:
  JSR MySubRoutine #$10
  ; Calls MySubRoutine and sets loopCounter to 16.

  JSR MySubRoutine $0500
  ; Calls MySubRoutine and sets loopCounter to the value at address $0500.
Re: Parameters in sub routines
by on (#118447)
There's no standardized way to do this. Personally I came up with my own macro based solution, and I know others have done similar stuff.

My solution, called xparam, works like this:

1) In a header file, let's say foo.h, include xparam.h and declare the function(s):
Code:
.include "xparam.h"

xdecl myFunction
  ; Parameters:
  foo .word
  bar .byte
  xlocals ; Local variables
    xyzzy .byte
  endxlocals
endxdecl


2) In the implementation file, say foo.s, define the function:
Code:
.include "foo.h"

xproc myFunction
  ; Fetch an argument.
  lda param bar
  ; Store it in a local variable.
  sta local xyzzy
  rts
endxproc


3) Call the function from somewhere else, say main.s:
Code:
.include "foo.h"

.code

somethingElse: .byte $69

.proc reset
  xinvoke myFunction, foo: #12345, bar: somethingElse
  jmp *
.endproc


In my implementation, all parameters and local variables are stored in the zero page. It's the programmer's duty to make sure the parameter/local areas of two different functions don't overlap.

And just to be clear, to use this stuff you need the xparam.h support header which hasn't been released (at least not yet).
Re: Parameters in sub routines
by on (#118449)
Alternatively, 8-bit values could simply be pushed onto the stack, loaded in the subroutine using TSX : LDA $010#,X ($0103,X for the last thing you pushed, $0104,X for the second-last, and so on), then popped once you're done (caller cleanup, of course).
Re: Parameters in sub routines
by on (#118451)
That looks cool thefox. I'm not sure how you are doing that, I'm going to have to think about it. Looks like you are abusing some of the macro facilities a bit.
I am not completely happy with my macro parameter system, but it is similar to thefox's, and zeropage is the way to go. I have a constant amount of zeropage reserved for parameters and local variables. Using the stack is slow, and if you send a pointer, you can access it directly with zeropage. I find the 6502 hardware stack useful for saving and loading address only (jsr/rts).

For nested functions, I do have runtime verification that local variables/parameters are not overwritten via Lua with NintendulatorDX.
Re: Parameters in sub routines
by on (#118452)
Since there's no built-in solution, I guess the one with the stack is the right one for me.

So, please tell me if the following code snippets would be correct:
Code:
FillSpecificMemoryWithValue:
; Parameters (put them to the stack in this order):
; 1. Length
; 2. Value
  TSX ; Loads the parameter "value" into X.
  TXA ; Copies X (i.e. parameter "value") into A.
  TSX ; Loads the parameter "length" into X.
Loop:
  STA $1000, X ; Stores "value" at address $1000 + X.
  DEX
  BNE Loop ; If X isn't 0 yet, repeat, otherwise, leave function.
  RTS

Code:
  LDX #$10 ; The parameter "length".
  TXS
  LDX #$FF ; The parameter "value".
  TXS
  JSR FillSpecificMemoryWithValue

I still haven't really understood the stack, though: Where is it located? I thought that all variables are put into RAM. You write STA $0500 and the variable is put into that specific location. If some other function overwrites it, bad luck, you should have declared placeholder names with .res. But for some reason, you can push variables to a region called the stack and when you retreive it, it's automatically deleted from the stack again. Where does this happen in memory? And can I just directly write to that location, screwing up the stack?

And what do I have to do if there are more than three parameters that I can't just put into A, X and Y all at once? Wouldn't this again require some global variables to temporarily store those values?
Re: Parameters in sub routines
by on (#118453)
DRW wrote:
So, please tell me if the following code snippets would be correct:
Code:
  TSX ; Loads the parameter "value" into X.

This is wrong. TSX simply transfers the stack pointer to register X. You need to use LDA $0103,X etc to actually access the stack (like Quietust said).

Quote:
I still haven't really understood the stack, though: Where is it located?

It's located at $100-$1FF in RAM. The stack pointer is 8 bits, OR it with $100 to get the address in RAM.
Re: Parameters in sub routines
by on (#118454)
I generally either put the parameters in the registers A/X/Y or where that's not appropriate, I use a ZP variable. These places are also good for return values. This makes a function call something like:

Code:
    LDX #3
    LDY #7
    JSR multiply_xy
    ; at this line A contains 21


Yes, you can use the stack as mentioned, but I find having to move the stack pointer into X to retrieve them makes it a little inconvenient/inefficient. You don't really need to use the stack unless you have a recursive/re-entrant subroutine.

Most of my functions take a single parameter in A and put the return value in A.


If I misunderstood the question and you're looking for a simplified syntax, which seems to be what the other replies in this thread are about, once you've written your function it is easy to write an accompanying macro to set up the appropriate registers in a one-line statement:

Code:
.macro MULTIPLY_XY a, b
    LDX a
    LDY b
    JSR multiply_xy
.endmacro

; called like this:
    MULTIPLY_XY #3, #7
Re: Parameters in sub routines
by on (#118461)
In the latest version of my 6502 Forth (which is subroutine-threaded, and I wasn't going to talk about it until my game was done but, hey...) I have a standardized parameter passing system. The X reg is an 8-bit pointer into a data stack on the zeropage, with A containing the top item. I use two macros, "dup" and "drop" to manipulate the data stack for passing parameters, consuming them, and returning them.

The beauty of this arrangement is it can be used completely divorced from the rest of the Forth language.

The stack grows downward, allowing you to put pointers on it intuitively (push high first, low second - additionally, 8-bit add/sub with no carry is straightforward). 0,x normally contains the 2nd item on the stack. Here are the macros:

Code:
macro drop
   lda 0,x
   inx
endm

macro dup
   dex
   sta 0,x
endm


To push something on the stack, you should first "dup" to effectively push A onto the stack freeing it to be LDA'd with something else. When a routine is done with a value, it should "drop" it. Or you can not "drop" it, changing the value of A to return something. Or push more values to return multiple values if desired.
Re: Parameters in sub routines
by on (#118465)
I use the A, X, and Y registers first, but also have zero page variables that simulate MIPS registers:

Code:
   .org $0000
a0:   .db 0   ; argument registers
a1:   .db 0   ;
a2:   .db 0   ;
a3:   .db 0   ;
a4:   .db 0   ;
a5:   .db 0   ;
t0:   .db 0   ; temporary registers
t1:   .db 0   ;
t2:   .db 0   ;
t3:   .db 0   ;
t4:   .db 0   ;
t5:   .db 0   ;
t6:   .db 0   ;
t7:   .db 0   ;
t8:   .db 0   ;
t9:   .db 0   ;
s0:   .db 0   ; save registers
s1:   .db 0   ;
s2:   .db 0   ;
s3:   .db 0   ;
s4:   .db 0   ;
s5:   .db 0   ;
v0:   .db 0   ; return value registers
v1:   .db 0   ;  (or more arguments)
i0:   .db 0   ; temporary registers
i1:   .db 0   ;  (for interrupts)
Re: Parameters in sub routines
by on (#118468)
DRW wrote:
Since there's no built-in solution, I guess the one with the stack is the right one for me.

So, please tell me if the following code snippets would be correct:
Code:
FillSpecificMemoryWithValue:
; Parameters (put them to the stack in this order):
; 1. Length
; 2. Value
  TSX ; Loads the parameter "value" into X.
  TXA ; Copies X (i.e. parameter "value") into A.
  TSX ; Loads the parameter "length" into X.
Loop:
  STA $1000, X ; Stores "value" at address $1000 + X.
  DEX
  BNE Loop ; If X isn't 0 yet, repeat, otherwise, leave function.
  RTS

Code:
  LDX #$10 ; The parameter "length".
  TXS
  LDX #$FF ; The parameter "value".
  TXS
  JSR FillSpecificMemoryWithValue

I still haven't really understood the stack, though: Where is it located? I thought that all variables are put into RAM. You write STA $0500 and the variable is put into that specific location. If some other function overwrites it, bad luck, you should have declared placeholder names with .res. But for some reason, you can push variables to a region called the stack and when you retreive it, it's automatically deleted from the stack again. Where does this happen in memory? And can I just directly write to that location, screwing up the stack?

And what do I have to do if there are more than three parameters that I can't just put into A, X and Y all at once? Wouldn't this again require some global variables to temporarily store those values?


I only use the 6502 stack for subroutines and temporarily saving values. It's located at $100-$1ff. But because it grows downward, and usually never ever gets close to filling up, you can safely store as many variables there as you feel comfortable. Check out the technique I posted above, let me know if you have any questions and I'll be happy to explain :)
Re: Parameters in sub routines
by on (#118469)
Stacks (hardware or software) are cool and all, but most of the time not necessary in NES/6502 games. I often just use a ZP section for parameters... say, 16 bytes under the label "Scratchpad". Then, before each subroutine I have the following:
Code:
.enum Scratchpad
   LocalVariable1 .dsb 1
   LocalVariable2 .dsb 2
   LocalVariable3 .dsb 1
.ende

And I just use these local variables for passing parameters. If a subroutine is called from another subroutine, I have to do something like this:
Code:
.enum Scratchpad
   Subroutine1.LocalVariable1 .dsb 1
   Subroutine1.LocalVariable2 .dsb 2
   Subroutine1.LocalVariable3 .dsb 1
   Subroutine1.LocalEnd
.ende
Subroutine1:

(...)

.enum Subroutine1.LocalEnd
   Subroutine2.LocalVariable1 .dsb 1
   Subroutine2.LocalVariable2 .dsb 1
.ende
Subroutine2:

Now, if a subroutine is called from different subroutines, I'll either define it's locals after the last variable of the subroutine that uses the most scratchpad bytes or I'll say "fuck it" and use the stack if there's no other way.
Re: Parameters in sub routines
by on (#118481)
tokumaru wrote:
Now, if a subroutine is called from different subroutines, I'll either define it's locals after the last variable of the subroutine that uses the most scratchpad bytes

Yeah that's an interesting problem I didn't want to think/worry about too much.

Code is my subs looks like:

Code:
func myFunction

    params 3        ; start at offset 3, bytes 0..2 are in use
        foo .byte
        bar .byte
    endparams

    locals
        baz .byte
    endlocals

    ; code
    lda param::foo
    sta local::baz

    ; declare baz safe to overwrite:
    release local::baz
    call someOtherFunction

    ; mark it as in use again
    reserve local::baz
    ; do some stuff

    ; exit with baz in A, and release all local/param memory used
    return local::baz

endfunc


Any overwritten locations display an error at runtime.
Re: Parameters in sub routines
by on (#118482)
thefox wrote:
This is wrong. TSX simply transfers the stack pointer to register X. You need to use LDA $0103,X etc to actually access the stack (like Quietust said).

Alright, now I understand. Even better. This means I can access these variables at will.
So, next try:
Code:
FillSpecificMemoryWithValue:
; Parameters (put them to the stack in this order):
; 1. Length
; 2. Value

  TSX          ; Loads the current stack pointer.
  LDA $0103, X ; Loads "value" into A.
  LDX $0104, X ; Loads "length" into X.

Loop:
  STA $1000, X ; Stores "value" at address $1000 + X.
  DEX
  BNE Loop     ; If X isn't 0 yet, repeat, otherwise, leave function.

  PLA          ; Removes "value" from stack.
  PLA          ; Removes "length" from stack.

  RTS

Code:
  LDA #$10 ; The parameter "length".
  PSA      ; "Length" is put into the stack.
  LDA #$FF ; The parameter "value".
  PSA      ; "Value" is put into the stack.
  JSR FillSpecificMemoryWithValue

Correct now?

If the stack starts at $0100, why do I have to get the most recent stack value with $0103 + X? I assume that the first value in the stack might be the stack pointer itself, so that the actual stack variables start at $0101. But why $0103?

rainwarrior wrote:
Yes, you can use the stack as mentioned, but I find having to move the stack pointer into X to retrieve them makes it a little inconvenient/inefficient. You don't really need to use the stack unless you have a recursive/re-entrant subroutine.

Yeah, I guess in a real game I will have to see for each function individually which version I'll take. But in the moment, I'll try the stack just to get it to know.

rainwarrior wrote:
If I misunderstood the question and you're looking for a simplified syntax

No, you understood it correctly. I was just looking for the technique itself, not for syntax tricks.

But thank you all for the example anyway.
Re: Parameters in sub routines
by on (#118483)
never-obsolete wrote:
zero page variables that simulate MIPS registers

I do something similar: $0000-$0007 for arguments and temporaries and $0008-$000F for caller-saved registers.

As for $0103: The top two values are the return address.
Re: Parameters in sub routines
by on (#118486)
DRW wrote:
[code]
LDX $0104, X ; Loads "length" into X.

This addressing mode doesn't exist, see http://www.obelisk.demon.co.uk/6502/reference.html#LDX

Quote:
If the stack starts at $0100, why do I have to get the most recent stack value with $0103 + X? I assume that the first value in the stack might be the stack pointer itself, so that the actual stack variables start at $0101. But why $0103?

Stack pointer is not at $100, it's a register inside the CPU itself. As for why $103, consider this example:

Let's say stack pointer starts out as $FF, i.e. SP = $FF, i.e. it points to $1FF. Now let's say the caller pushes two values on the stack, so the bytes are placed at $1FF and $1FE and SP = $FD after. When you call a function, two more bytes (the return value) get pushed on the stack, so SP becomes $FB. And how do we get from the stack pointer value, $FB, to the parameter at $1FE? By adding $1FE - $FB = $103.
Re: Parameters in sub routines
by on (#118489)
thefox wrote:
DRW wrote:
Code:
  LDX $0104, X ; Loads "length" into X.

This addressing mode doesn't exist, see http://www.obelisk.demon.co.uk/6502/reference.html#LDX

I already feared that this won't work. Alright, last try:
Code:
FillSpecificMemoryWithValue:
; Parameters (put them to the stack in this order):
; 1. Length
; 2. Value

  TSX          ; Loads the current stack pointer.
  LDA $0103, X ; Loads "value" into A.
  LDY $0104, X ; Loads "length" into Y.

Loop:
  STA $1000, Y ; Stores "value" at address $1000 + Y.
  DEY
  BNE Loop     ; If X isn't 0 yet, repeat, otherwise, leave function.

  PLA          ; Removes "value" from stack.
  PLA          ; Removes "length" from stack.

  RTS

Code:
  LDA #$10 ; The parameter "length".
  PSA      ; "Length" is put into the stack.
  LDA #$FF ; The parameter "value".
  PSA      ; "Value" is put into the stack.
  JSR FillSpecificMemoryWithValue


thefox wrote:
As for why $103, consider this example:
[/quote][/quote]
O.k., now I understand.
Re: Parameters in sub routines
by on (#118497)
DRW wrote:
thefox wrote:
DRW wrote:
Code:
  LDX $0104, X ; Loads "length" into X.

This addressing mode doesn't exist, see http://www.obelisk.demon.co.uk/6502/reference.html#LDX

I already feared that this won't work. Alright, last try:
Code:
FillSpecificMemoryWithValue:
; Parameters (put them to the stack in this order):
; 1. Length
; 2. Value

  TSX          ; Loads the current stack pointer.
  LDA $0103, X ; Loads "value" into A.
  LDY $0104, X ; Loads "length" into Y.

Loop:
  STA $1000, Y ; Stores "value" at address $1000 + Y.
  DEY
  BNE Loop     ; If X isn't 0 yet, repeat, otherwise, leave function.

  PLA          ; Removes "value" from stack.
  PLA          ; Removes "length" from stack.

  RTS

Code:
  LDA #$10 ; The parameter "length".
  PSA      ; "Length" is put into the stack.
  LDA #$FF ; The parameter "value".
  PSA      ; "Value" is put into the stack.
  JSR FillSpecificMemoryWithValue


thefox wrote:
As for why $103, consider this example:
[/quote]
O.k., now I understand.[/quote]

This will not work. Because you are pulling the return address off the stack, leaving the parameters you pushed to be treated as the return address, the program will crash. If you reserve X as a data stack pointer as I do, you can avoid this specific issue while still keeping the same simple and easy methodology.

Code:
fillstuff:   ; value length --
  tay
  lda 0,x
-
  sta $1000,y
  dey
  bne -
  inx
  drop
  rts

caller:
  dup
  lda #0
  dup
  lda #length
  jsr fillstuff
 

Re: Parameters in sub routines
by on (#118498)
You can't really remove the parameters from the stack from inside the subroutine though, because the return address is the last thing on the stack. If you take the return address off the stack, the RTS command that follows will crash the program (it will "return" to an invalid location). If it as that easy, nothing would stop you from using PLA right at the beginning to read your parameters instead of using LDA $0103, X, right?

the correct thing to do here would be to pop the parameters out of the stack after the subroutine has returned (i.e. after the JSR), which is not as nice and might trash any return values you have in A. If you really really want to clean up the stack from inside the subroutine, you need to move the return address to the correct place. This is why not many people like to use the stack for passing parameters in 6502 assembly, it's not very efficient.
Re: Parameters in sub routines
by on (#118499)
Scratchpad RAM?
Re: Parameters in sub routines
by on (#118503)
tokumaru wrote:
You can't really remove the parameters from the stack from inside the subroutine though, because the return address is the last thing on the stack. If you take the return address off the stack, the RTS command that follows will crash the program (it will "return" to an invalid location). If it as that easy, nothing would stop you from using PLA right at the beginning to read your parameters...


That is what I used to do before I abandoned the idea of using the stack for parameters. I had a macro that manually pushed the return address, pushed the parameters, then did a JMP to the subroutine. Not too horrible, but not great.
Re: Parameters in sub routines
by on (#118516)
O.k., I guess I'll ignore the stack for now and use global variables or the registers directly. Thanks for your answers.