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?
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.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.)
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
You have to do dumb conditional stuff like:How do you go about requiring the register as an argument to the macro?
Code: Select all
.if useXRegister dex .else dey .endif
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)
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).
ahh, but 6502 assembly is part of the charm of NES developmentpubby wrote:The foolproof way to never clobber registers is to use an actual programming language
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.
...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.
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.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'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?
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.
I feel like this is more common practice in SNES programming.doing pha / phx / phy and ply / plx / pla
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.
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.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.
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.
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.dougeff wrote:I feel like this is more common practice in SNES programming.doing pha / phx / phy and ply / plx / pla
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.
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.)koitsu wrote:I think this is more about experience and the habits that develop as you get more of it.
Well, it'd be fine on FDS, or C64, or code otherwise copied to RAM.battagline wrote:I keep forgetting about the hardware details.pubby wrote: You can't allocate variables in ROM.