ASM register management

Ask anything your want about Megadrive/Genesis programming.

Moderator: BigEvilCorporation

furrykef
Interested
Posts: 30
Joined: Mon Jul 21, 2008 7:28 pm

ASM register management

Post by furrykef » 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

TmEE co.(TM)
Very interested
Posts: 2440
Joined: Tue Dec 05, 2006 1:37 pm
Location: Estonia, Rapla City
Contact:

Post by TmEE co.(TM) » Mon Jul 21, 2008 9:41 pm

the registers you pass get trashed (read: clobbered) anyway, and if you want to preserve something, you could always use stack... I don't think that there should be regs that need to be kept untouched in routine calls. I use registers as much as possible, but as little different ones as possible, and if I have to use all the regs, I use all the regs.
Keeping track of 8/16/32 bit things shouldn't be a problem... you have B/W/L to tell you what are you dealing with.
You could dedicate certain regs to certain things though... I use such way in my Z80 stuff (my MD sound engine) and its very convenient.
Mida sa loed ? Nagunii aru ei saa ;)
http://www.tmeeco.eu
Files of all broken links and images of mine are found here : http://www.tmeeco.eu/FileDen

furrykef
Interested
Posts: 30
Joined: Mon Jul 21, 2008 7:28 pm

Post by furrykef » Mon Jul 21, 2008 9:45 pm

TmEE co.(TM) wrote:the registers you pass get trashed (read: clobbered) anyway
Eh? Not if you don't change 'em, or if you preserve 'em.
TmEE co.(TM) wrote:Keeping track of 8/16/32 bit things shouldn't be a problem... you have B/W/L to tell you what are you dealing with.
I was thinking more along the lines of remembering whether you should put a B, W, or L when working with a particular variable. It's kind of as if in C you had different operators for each data type, and if you slip and use the wrong operator type, the compiler won't tell you.

- Kef

tomaitheous
Very interested
Posts: 256
Joined: Tue Sep 11, 2007 9:10 pm

Post by tomaitheous » Tue Jul 22, 2008 1:13 am

I don't think you really need a list of destroyed registers unless you're making these subroutines for some public library or such. Just keep track if a certain subroutine is going to be called in a nested layer or not.

If you know that routine is going to be as such, then save the registers used for that subroutine to the stack inside the subroutine. If your using a loop in the main code and have a subroutine call inside it, either reserved a set of registers for X subroutines to used freely or manually push the important registers in the loop, before calling the subroutine.

You could also use setup macros for subroutine calls as normal or nested.

I've only seen such a comment block was for subroutines in library sets, for other programmers. But it's totally up to you.

About the data elements, yeah be careful in keeping track of size. Also, I'd probably keep all 8bit values padded as 16bit. It's quicker for a couple of reasons on the 68k. Also, 16 and 32bit data types are easier to keep track of. You probably won't (and shouldn't) be using 32bit types very much.

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Tue Jul 22, 2008 7:56 am

I did 100% assembly programming on the Amiga for a decade. My rules:

NEVER put anything on the stack if you have a free register. The more you keep stuff in registers, the faster it will be. That's the ENTIRE REASON to do anything in assembly. If you're going to use the stack, you might as well just use C.

NEVER arbitrarily save/restore registers. This goes back to using the stack. If you're going to just push/pop all the registers in every routine, you might as well be using C. You're an ASSEMBLY LANGUAGE programmer - act like it. Use any registers you can, NEVER save/restore unless FORCED to by the need to put something else in a register when no more are free.

NEVER make "prologues" or "epilogues". Again, that goes back to arbitrarily using the stack. Again, if you're going to use them in your functions, might as well be using C.

I normally put comments at the start of the program outlining the overall register usage for the program, then put special register usage in front of the functions that had special usage. Also write all these in a notebook that you keep with you as you write your program. Consulting the notes is usually quicker than trying to find the routine in the editor just to look at the comments. I have boxes of books from my work on the Amiga.

furrykef
Interested
Posts: 30
Joined: Mon Jul 21, 2008 7:28 pm

Post by furrykef » Tue Jul 22, 2008 8:22 am

OK, I can see my line of thinking is not popular and it's not the "ASM way", so I will probably abandon this line of thinking. But for the sake of argument, I'll defend it anyway.
Chilly Willy wrote:NEVER put anything on the stack if you have a free register. The more you keep stuff in registers, the faster it will be. That's the ENTIRE REASON to do anything in assembly. If you're going to use the stack, you might as well just use C.
Actually, I'm doing this in assembly because it's fun, challenging, and something new for me. Performance and smaller size is just a side benefit. I could easily code this game in C if I wanted to (and in fact I did originally start coding this game in C).

Of course, "You're an ASM programmer, act like it" is a perfectly reasonable counterargument -- if I'm going to explore assembly language, I might as well do it the way I'm "supposed" to do it. I'm just pointing out that performance isn't really the "entire reason".

There's also the point that, just as with coding in a high-level language, 90% of the time is spent in 10% of the code. When you're not in a performance-critical section, maintainability, understandability, and reducing the chance of shooting yourself in the foot is more important than performance.
NEVER arbitrarily save/restore registers. This goes back to using the stack. If you're going to just push/pop all the registers in every routine, you might as well be using C.
Well, it's not quite the same as using C: in C, your variables are accessed through the stack. If you access a variable a zillion times without marking it "register", you get a zillion memory accesses. Here you just get two: pushing the register before using it and popping it when you're done with it.
Consulting the notes is usually quicker than trying to find the routine in the editor just to look at the comments.
I dunno, CTRL+F can be pretty darn quick. ;)

- Kef
Last edited by furrykef on Sun Jul 27, 2008 3:15 am, edited 1 time in total.

HardWareMan
Very interested
Posts: 745
Joined: Sat Dec 15, 2007 7:49 am
Location: Kazakhstan, Pavlodar

Post by HardWareMan » Tue Jul 22, 2008 8:40 am

I always adhered to one rule: subroutine should always restore registers that are not participating in the return result. For example, subroutine uses a0, d0 and d1, but the result is returned only in d0, then registers a0 and d1 must be restored. This will rid of a headache when in big program difficult to track what and how is called. Also, it provides some convenience: for example subroutine can be called several times with the same parameters. For save/restore registers in M68K ASM I use MOVEM opcode with a7 (or SP) as address source, otherwise PUSH/POP.
Last edited by HardWareMan on Tue Jul 22, 2008 8:45 am, edited 1 time in total.

furrykef
Interested
Posts: 30
Joined: Mon Jul 21, 2008 7:28 pm

Post by furrykef » Tue Jul 22, 2008 8:43 am

Hmm, that's even stricter than the system I devised.

HardWareMan
Very interested
Posts: 745
Joined: Sat Dec 15, 2007 7:49 am
Location: Kazakhstan, Pavlodar

Post by HardWareMan » Tue Jul 22, 2008 8:47 am

furrykef wrote:Hmm, that's even stricter than the system I devised.
But conveniently. ;) For example (subroutine has no result):

Code: Select all

*Print number $nX XX XX XX (d0) at address (a0)
PrintNum:  dc.w        $48E7,$8380     *MOVEM.L	 D0,D6,D7/A0 at -(A7) save registers
           move.l      d0,d7           *
           ror.l       #8,d7           *
           ror.l       #8,d7           *
           ror.l       #8,d7           *
           ror.l       #4,d7           *
           and.l       #$0000000F,d7   *d7 - digit counter
           and.l       #$0FFFFFFF,d0   *d0 - source number
           clr.l       d1              *d1 - dozens count
           move.l      d7,d6           *
PrintND:   cmp.l       #10,d0          *
           blt         PrintNE         *
           sub.l       #10,d0          *
           add.l       #1,d1           *
           bra         PrintND         *
PrintNE:   add.b       #$30,d0         *
           move.w      d0,-(sp)        *
           move.l      d1,d0           *
           clr.l       d1              *
           dbra        d6,PrintND      *
           move.l      d7,d6           *
PrintNS:   move.w      (sp)+,d0        *
           move.w      d0,(a0)+        *
           dbra        d6,PrintNS      *
           dc.w        $4CDF,$01C1     *MOVEM.L	 from (A7)+ D0,D6,D7/A0 - restore registers
           rts                         *
With regard to the register size in bits - in M68K I always use the entire register, even if only lower byte is the result. Result size is described in the remarks of subroutine.

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Tue Jul 22, 2008 8:42 pm

furrykef wrote:
Consulting the notes is usually quicker than trying to find the routine in the editor just to look at the comments.
I dunno, CTRL+F can be pretty darn quick. ;)

- Kef
Many editors these days have "split screen" or something similar. You can keep the notes part of the file on one part and the part you're editing on the other.

@HardWareMan - I wrote programs with several hundred thousand lines of assembly and never got a headache keeping track of the registers. It's about proper design of the app - in THIS section, registers are all scratch and you never worry about save/restore, while in THIS section, the global definition of registers is in use and those particular registers need to be maintained... that sort of thing. If you can't keep it all straight in your head, write it down in a notebook like I suggested. If you aren't sure, consult the notebook. I never had a problem. 8)

TmEE co.(TM)
Very interested
Posts: 2440
Joined: Tue Dec 05, 2006 1:37 pm
Location: Estonia, Rapla City
Contact:

Post by TmEE co.(TM) » Wed Jul 23, 2008 3:52 pm

The note(book) method is very very helpful... I've got pages of notes regarding my MD sound engine... a notebook is better, as pages tend to get lost :P
Several hundred thousand lines of ASM in a single program :shock:
My MD sound engine is only 3000 lines of Z80 code...
Mida sa loed ? Nagunii aru ei saa ;)
http://www.tmeeco.eu
Files of all broken links and images of mine are found here : http://www.tmeeco.eu/FileDen

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Wed Jul 23, 2008 6:15 pm

TmEE co.(TM) wrote:The note(book) method is very very helpful... I've got pages of notes regarding my MD sound engine... a notebook is better, as pages tend to get lost :P
Several hundred thousand lines of ASM in a single program :shock:
My MD sound engine is only 3000 lines of Z80 code...
I use the multi-subject college-ruled spiral notebooks. :D

A program with several hundred thousand lines is just like one with a few thousand... just longer. :lol:

tomaitheous
Very interested
Posts: 256
Joined: Tue Sep 11, 2007 9:10 pm

Post by tomaitheous » Wed Jul 23, 2008 6:40 pm

An IDE with support of tagging notes to a subroutine would be nice. Maybe even have it follow the cursor of a main window and in the sub window it'd bring up the notes for that subroutine on the fly. No manual looking it up or large text blocks cluttering up the source.

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Thu Jul 24, 2008 1:21 am

tomaitheous wrote:An IDE with support of tagging notes to a subroutine would be nice. Maybe even have it follow the cursor of a main window and in the sub window it'd bring up the notes for that subroutine on the fly. No manual looking it up or large text blocks cluttering up the source.
Yeah, I'd use something like that. That would be handy for just about anything actually.

EDIT: corrected boo-boo in last paragraph. :oops:
Last edited by Chilly Willy on Sat Jul 26, 2008 8:59 pm, edited 1 time in total.

furrykef
Interested
Posts: 30
Joined: Mon Jul 21, 2008 7:28 pm

Post by furrykef » Sat Jul 26, 2008 8:50 pm

tomaitheous wrote:I'd probably keep all 8bit values padded as 16bit. It's quicker for a couple of reasons on the 68k.
I don't think it's quicker, because, looking at the instruction counts, there are no instructions where a word is processed faster than a byte. But neither are there any instructions where a word is processed slower than a byte, except in the lone case of a branch not being taken -- byte-length branch instructions are 2 cycles faster in that case.

However, it may be better to take your advice anyway because RAM is plentiful (I don't expect to use anywhere near all of it for my project), so I'd never have to bother with remembering whether a particular value is a byte or a word.

Speaking of words versus bytes, there's a quirk in the M68k that I never realized until I thought about it this morning. Suppose I want to deliberately modify only half of a word:

Code: Select all

    move.b #0, d0               ; Overwrites LSB
    move.b #0, TwoByteVariable  ; Overwrites MSB!
How counterintuitive! If you always use 16-bit writes, you avoid the problem.
Chilly Willy wrote:Yeah, I use something like that.
What do you use?

- Kef
Last edited by furrykef on Sat Jul 26, 2008 9:04 pm, edited 1 time in total.

Post Reply