Strategy for managing register values

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

User avatar
battagline
Posts: 152
Joined: Wed Sep 05, 2018 11:13 am
Location: Colorado
Contact:

Strategy for managing register values

Post by battagline »

Ok, so this may be a really stupid question, but I just spent several hours debugging some weird behavior in the game I'm working on just to realize the problem was I have a macro that was clobbering the value in my x register.


My main question is what strategy do developers use to make sure they aren't calling procedures or macros that overwrite the values in their x, y, or a registers when they need that value to stay put?

Thoughts, ideas or suggestions?

Thanks
A few of my web games
https://www.embed.com
Or if you're bored at work
https://www.classicsolitaire.com
zzo38
Posts: 1096
Joined: Mon Feb 07, 2011 12:46 pm

Re: Strategy for managing register values

Post by zzo38 »

My idea is to mention in the comments what registers are being used, so that you can remember. In the case of macros, you can also require the registers to use as arguments to the macro. (I have done both of these things.)
(Free Hero Mesh - FOSS puzzle game engine)
User avatar
battagline
Posts: 152
Joined: Wed Sep 05, 2018 11:13 am
Location: Colorado
Contact:

Re: Strategy for managing register values

Post by battagline »

zzo38 wrote:My idea is to mention in the comments what registers are being used, so that you can remember. In the case of macros, you can also require the registers to use as arguments to the macro. (I have done both of these things.)
I've been doing the comment thing, but my problem is I don't always go back and reference the comments on the procedure or macro I'm calling. I had it in my comments above the macro that was clobbering my X, but I didn't go back and look at the definition when I made the call and had forgotten all about it. It took me several hours to track down where in my code the problem was happening, then as soon as I looked at the macro I knew immediately.

How do you go about requiring the register as an argument to the macro? I'm using CA65 and from what I can tell it's just doing a straight text replace with the arguments. like if I have a macro that takes argument arg, what I pass in could be a label or a value, but I couldn't say tell it to reference the x register (I don't think)

Thanks for your help
A few of my web games
https://www.embed.com
Or if you're bored at work
https://www.classicsolitaire.com
User avatar
pubby
Posts: 583
Joined: Thu Mar 31, 2016 11:15 am

Re: Strategy for managing register values

Post by pubby »

The foolproof way to never clobber registers is to use an actual programming language :P Other than that, it really comes down to diligence. You have to be completely cognizant of everything your code is doing.
How do you go about requiring the register as an argument to the macro?
You have to do dumb conditional stuff like:

Code: Select all

.if useXRegister
  dex
.else
  dey
.endif
And have useXRegister be a parameter to the macro.
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Strategy for managing register values

Post by rainwarrior »

On most functions I put a three lines of comments at the start to list inputs (registers or other variables), outputs, and clobbers. If I ever forget or need to know I can refer to these.

Code: Select all

; example function comment

; input: ih:i,j,kh:k,l = bounding box (preserved)
; result in A/z -> 0=no,1=right,2=left
; clobbers n,o,p
do_push:
;   ... body of function

; sometimes I make a more compact summary version, especially in import blocks for quicker reference:

.import sprite_add           ; sprite A, X=x, Y=y, sprite_center (clobbers A,X,Y,i,j,k)
.import sprite_add_flipped   ; sprite A, X=x, Y=y, sprite_center (clobbers A,X,Y,i,j,k)
.import sprite_tile_add      ; A=tile, X=x, Y=y, i=attribute (clobbers A,X)
These aren't rigorous or machine parsable (e.g. Javadoc), I only care that I can look it up when I need to. The main thing here is just to keep a consistent habit of updating the comment if I ever add new affected variables.

If I don't know, or don't want to look up whether a variable is safe to clobber, I often just push it to the stack temporarily to make it not an issue. The resulting code takes a few more cycles, but save some digging (or refactoring), which is very often a worthwhile trade IMO.

In general, I expect A to be clobbered by any function. X and Y are normally fair game, but I often have sections/categories of code where one or the other index is expected to be preserved (e.g. working with game objects, they might commonly use an index register throughout).
User avatar
battagline
Posts: 152
Joined: Wed Sep 05, 2018 11:13 am
Location: Colorado
Contact:

Re: Strategy for managing register values

Post by battagline »

pubby wrote:The foolproof way to never clobber registers is to use an actual programming language :P
ahh, but 6502 assembly is part of the charm of NES development ;-)
A few of my web games
https://www.embed.com
Or if you're bored at work
https://www.classicsolitaire.com
User avatar
dougeff
Posts: 3078
Joined: Fri May 08, 2015 7:17 pm

Re: Strategy for managing register values

Post by dougeff »

One mistake I made was to give a temp variable a pseudonym (like local_X = temp3). I thought it would make it clearer about what the temp variable was doing. But then I used another function which clobbered the same variable, and I couldn't figure out the problem because of the alternate name for the same RAM address added confusion.

So... don't do that.


In the old days, when there was 2+ different programmers on the same project. Each programmer would be given a range of RAM addresses that they were allowed to use. This was before modern "namespaces" and OOP principles. Otherwise, you would get 100 bugs that neither would know how to fix.
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Strategy for managing register values

Post by rainwarrior »

I don't know what you mean about "old days", but why RAM would need to be divided by programmer? Label names by themselves solve almost all problems of overlap. Whenever RAM is limited, a coherent plan for allocating it is needed, but "per programmer" seems a bit removed from whatever the needs the program's actual function has. (That's a bit curious to me, is this from a specific story you've heard/read?)

...but it's pretty normal now and past to budget memory, and that's usually divided up by system or type of content, which does tend to align with various departments at least. E.g. sound gets 128MB, character mesh and texture gets 256MB each, a level's textures get 2GB, physics collision mesh gets 64MB, etc. Though, this kind of organization is generally necessary whether your company is 1 person or 100, unless the available memory greatly exceeds your needs.

In a lot of cases one person is given responsibility for the memory budget, and any changes to it involve a conversation with that person. That's a common practice which I've seen in modern development, and heard stories about in older development too.
User avatar
battagline
Posts: 152
Joined: Wed Sep 05, 2018 11:13 am
Location: Colorado
Contact:

Re: Strategy for managing register values

Post by battagline »

dougeff wrote:One mistake I made was to give a temp variable a pseudonym (like local_X = temp3). I thought it would make it clearer about what the temp variable was doing. But then I used another function which clobbered the same variable, and I couldn't figure out the problem because of the alternate name for the same RAM address added confusion.
I'm doing something similar to this. I haven't been hit by a terrible bug yet, but I've been thinking about the many ways it can go wrong. It seemed like it was a good idea at first but now I have a "this won't end well" kinda feeling.

I've also been wondering if I could use some space at the bottom of a macro to jump over for pseudo local variables within a macro.

something like this:

Code: Select all

.macro somemacro
  .local @endofmacro:
  .local @macrovar1
  .local @macrovar2

  ; code in here doesn't really matter
  sta @macrovar1
  lda some_global
  clc
  adc #28
  sta some_global
  lda @macrovar1

  ; end of main macro code

  jmp @endofmacro
  ; start of macro local variables
  @macrovar1: .byte #$00
  @macrovar2: .byte #$00

  @endofmacro:
.endmacro

I haven't tested that out yet in ca65 compiled code, but Easy 6502 let's me do something kind of like that, but without the ca65 specific control commands

Anyone know if that would work in theory?

Thanks
A few of my web games
https://www.embed.com
Or if you're bored at work
https://www.classicsolitaire.com
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: Strategy for managing register values

Post by koitsu »

A habit I was taught when learning assembly / starting out: get in the habit of doing pha / phx / phy and ply / plx / pla at the start and ends of your subroutines and macros.

Readers should read my above sentence and focus on the underlined portion before chastising me or saying how it's bad advice.

This model acts as a "safety net" -- you can go back later and optimise things. Stack operations are "costly" cycle-wise (3 cycles each), but as I just said, you can go back later and remove them during an optimisation phase, or when you might need to (e.g. running out of CPU time in NMI/VBlank -- that's a whole other subject for another thread).

I'm one of those programmers who scarcely uses macros. They're powerful and incredibly useful for situations that warrant them (a good example: generating large sums of unrolled code), but when watching newcomers enter into the world of assembly I often find that they cause more chaos than they do good. I'm more of a subroutine kind of guy in general.

I'm also one of those "old school" programmers who doesn't bother getting all hung up on a bunch of things like... well... like what you just posted above, actually. I'm one of those people who has a gigantic list of equates that represents memory from $0000 to $07FF and makes new variables by doing varname = previousvarname+1 (or +2 as needed). I've been chastised many times over the years for this, and still to this day just smile and say "mmhmm". ;-) The reality is that the entire memory space on 65xxx is accessible directly, so worrying about all of this "organisation" of what not, IMO, gets you absolutely nothing but headaches (especially during debugging), not to mention makes it nearly impossible for collaboration with others when encountering a problem. I can promise you that Bill Mensch wasn't thinking about any of that stuff when designing the 65xxx processors.

I think this is more about experience and the habits that develop as you get more of it. No offense intended, but you're new to 6502 and assembly in general (but not programming); the "micro-management" aspect of assembly, combined with a "raw" system that has no operating system or BIOS, will cause you some grief until you have a good amount of experience with it. And even then the most experienced assembly programmers still make mistakes like this. There are many mistakes in assembly that can cost you hours of time chasing down an obscure bug (another common one are things like lda $10 when you meant lda #$10, or vice-versa; or lda var,y when you meant lda var,x). That's just the nature of the beast, I'm sorry to say. In short: it's OK! You aren't alone! :-)

Remember this mantra: computers always do what they're told. There's no way for the tooling, or the CPU, to know what you "wanted". Higher-level PLs and OSes often hide this mantra from the user/programmer in exchange for some convenience. The costs are always the same: speed and space.
User avatar
pubby
Posts: 583
Joined: Thu Mar 31, 2016 11:15 am

Re: Strategy for managing register values

Post by pubby »

Anyone know if that would work in theory?
You can't allocate variables in ROM.
User avatar
dougeff
Posts: 3078
Joined: Fri May 08, 2015 7:17 pm

Re: Strategy for managing register values

Post by dougeff »

doing pha / phx / phy and ply / plx / pla
I feel like this is more common practice in SNES programming.

Another thing to consider is mappers with movable PRG banks. You might want to save the calling function's bank to a "stack" of bank #s, then jumping to a fixed bank, before calling a function in a different movable bank.

And popping the bank on return.
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
Myask
Posts: 965
Joined: Sat Jul 12, 2014 3:04 pm

Re: Strategy for managing register values

Post by Myask »

Well, yes, phy/ply/phx/plx are 65c02 on up…
User avatar
battagline
Posts: 152
Joined: Wed Sep 05, 2018 11:13 am
Location: Colorado
Contact:

Re: Strategy for managing register values

Post by battagline »

pubby wrote: You can't allocate variables in ROM.
*forehead slap*
I keep forgetting about the hardware details.

Thanks
A few of my web games
https://www.embed.com
Or if you're bored at work
https://www.classicsolitaire.com
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Strategy for managing register values

Post by rainwarrior »

dougeff wrote:One mistake I made was to give a temp variable a pseudonym (like local_X = temp3). I thought it would make it clearer about what the temp variable was doing. But then I used another function which clobbered the same variable, and I couldn't figure out the problem because of the alternate name for the same RAM address added confusion.

So... don't do that.
I actually think it's a very good idea to use local aliases for temporary variables, I would definitely not advise against it. Doing this makes it much easier to reassign them (changing 1 line vs. finding and changing every instance) when they need to be moved.

Like I was suggesting earlier, keeping a clobber list for each function in a comment is pretty useful, but local assignments at the top of the function are like a built-in automatic clobber list, at least if you keep them in one place where you're used to looking to find them. It complements/augments/facilitates that strategy of documenting temporary usage.
dougeff wrote:
doing pha / phx / phy and ply / plx / pla
I feel like this is more common practice in SNES programming.
It depends on the size and scope of your subroutines. Given how frequently people here seem to complain about the temporary variable management problem, I think people probably don't consider the stack option often enough.

Save your temporaries to the stack, and you don't have to worry about the management problem at all. Later on if you're really hurting for an extra 10 cycles on that function, maybe consider optimizing it away then, but don't bother with it up front. (This is also why I advocate using local aliases; makes that optimization reorganization quicker and easier.)

Though, registers like A/X/Y in general shouldn't even need to be saved for a function call, IMO. In most cases I wouldn't expect any of them to be preserved across a call. A at least should most commonly be both the main input and output for a function, so there's almost never a reason to preserve that. Indices... maybe sometimes there's reason to preserve them, if you're in a region of code that has a shared index. Most of the time I wouldn't consider the registers something that need to be preserved. Static variables (especially on ZP) though, are often worth preserving/using.
koitsu wrote:I think this is more about experience and the habits that develop as you get more of it.
Absolutely agree with this. General advice has very limited application. The hard work of programming is thinking critically about which option to use for any specific case, and there's no rule of thumb that works for everything. Experience is the only way to really gain an intuition for it. (...and experienced people will often disagree on how to read the same situation anyway.)
battagline wrote:
pubby wrote: You can't allocate variables in ROM.
I keep forgetting about the hardware details.
Well, it'd be fine on FDS, or C64, or code otherwise copied to RAM. ;)
Post Reply