ASM register management
Posted: Mon Jul 21, 2008 8:37 pm
This is more of a general M68000 question than a Genesis question, but I figured I might as well post it here. 
I decided to start coding a game entirely in ASM as a programming exercise of sorts. I've been well-acquainted with the general principles of ASM for probably over 10 years now, but I've rarely ever actually applied them (except for a small but awesome NES ROM hack that, sadly, I've never finished), so I thought it'd be a bit of fun, especially since the M68k is a dream compared to most CPUs. (I hate the 6502...)
I think one of the big problems with assembly language, other than of course the general low-levelness and nonportability, is keeping track of everything. You gotta remember this variable is 16-bit, that one is 8-bit, and you have to use the right instructions for them. And lord help you if you want to change the size of the variable later on! (Though, thankfully, I imagine it's rare that you have to.) And, of course, another is keeping track of what exactly is in your registers, hence this topic.
I'm thinking a good general policy for my routines is that, by default, any routine is allowed to clobber a0, a1, d0, and d1. (I'm going to use "clobber" to mean to overwrite for purposes of storing a temporary variable.) If the routine needs to use any other registers, it pushes them onto the stack at the start and pops them back at the end, so the caller can expect that a2-a7 and d2-d7 are untouched, unless of course values are returned through them. (Of course I realize sometimes it can be helpful to have a routine clobber more registers without preserving them; if so, they will be documented. I expect to do this only in special cases, though, like a routine called in a tight loop of some kind.)
If any of the clobberable registers are used to pass in parameters, and it would be really helpful for them not to change, they are specified in the function's heading as return values, like this:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; NameOfFunction
;
; Params:
; a0 - pointer to foobar
; d0 - some dumb value
;
; Returns:
; a0 - unchanged param
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(I know that's a rather big and ugly header -- I never use 'em in high-level languages -- but I think it does help readability; I can identify my functions more easily as I scroll.)
I kinda worry, though, that I'll forget my own system in the heat of the moment... it could be easy to forget that this function is allowed to clobber d0, for example, let alone a1 and d1, which aren't even mentioned. Then again, maybe if I work with it for a while, it'll internalize itself enough that I'll remember not to count on those registers being the same after a call any more than I'd count on the flags staying the same.
One alternative to this system is to have a list of clobbered registers for each function, but then that list would have to actually be maintained, and it'd make the function heading even more unwieldy. Not to mention that I'd have to consult the function heading more often: "Wait, does this routine clobber d2 or not?"
So how does my system sound? What do you guys do?
- Kef

I decided to start coding a game entirely in ASM as a programming exercise of sorts. I've been well-acquainted with the general principles of ASM for probably over 10 years now, but I've rarely ever actually applied them (except for a small but awesome NES ROM hack that, sadly, I've never finished), so I thought it'd be a bit of fun, especially since the M68k is a dream compared to most CPUs. (I hate the 6502...)
I think one of the big problems with assembly language, other than of course the general low-levelness and nonportability, is keeping track of everything. You gotta remember this variable is 16-bit, that one is 8-bit, and you have to use the right instructions for them. And lord help you if you want to change the size of the variable later on! (Though, thankfully, I imagine it's rare that you have to.) And, of course, another is keeping track of what exactly is in your registers, hence this topic.
I'm thinking a good general policy for my routines is that, by default, any routine is allowed to clobber a0, a1, d0, and d1. (I'm going to use "clobber" to mean to overwrite for purposes of storing a temporary variable.) If the routine needs to use any other registers, it pushes them onto the stack at the start and pops them back at the end, so the caller can expect that a2-a7 and d2-d7 are untouched, unless of course values are returned through them. (Of course I realize sometimes it can be helpful to have a routine clobber more registers without preserving them; if so, they will be documented. I expect to do this only in special cases, though, like a routine called in a tight loop of some kind.)
If any of the clobberable registers are used to pass in parameters, and it would be really helpful for them not to change, they are specified in the function's heading as return values, like this:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; NameOfFunction
;
; Params:
; a0 - pointer to foobar
; d0 - some dumb value
;
; Returns:
; a0 - unchanged param
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(I know that's a rather big and ugly header -- I never use 'em in high-level languages -- but I think it does help readability; I can identify my functions more easily as I scroll.)
I kinda worry, though, that I'll forget my own system in the heat of the moment... it could be easy to forget that this function is allowed to clobber d0, for example, let alone a1 and d1, which aren't even mentioned. Then again, maybe if I work with it for a while, it'll internalize itself enough that I'll remember not to count on those registers being the same after a call any more than I'd count on the flags staying the same.
One alternative to this system is to have a list of clobbered registers for each function, but then that list would have to actually be maintained, and it'd make the function heading even more unwieldy. Not to mention that I'd have to consult the function heading more often: "Wait, does this routine clobber d2 or not?"
So how does my system sound? What do you guys do?
- Kef