ASM register strategy

Go To Last Post
3 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am going to try my luck at coding an 8080 emulator in assembly.  I have one in C and we have tried dedicating some registers to improve performance, but this is the type of thing that I think C isn't as well suited for as assembly.  One can take advantage of copying processor flags in assembly easily for example.  My C code was running about 700 kHz emulation at 18.432 MHz clock speed, so about 26 times slower.

 

I've read AT1886 mixing ASM with C with AVRGCC and it talks about which registers are call-used vs which are call-saved and this makes me wonder what a good strategy is for register usage.  My goal is to just code the part that is fast in assembly only, which is the bulk of it.  For example, before calling the ASM function to execute, the C function will check to see if any breakpoints are enabled and if so, it will send a parameter to the assembly function that tells it to check the breakpoints or not.  That is a single test then.  I was hoping to make the ASM function not have to call back to C, but it will have to call in() and out() functions, so it will have to.

 

Given that it is both called from C and has to call a couple of C functions, what is the best strategy for which registers to use.  I know that some of them might be best used because they are part of the X, Y, or Z., but if you have a choice of whether to use a call-used or call-saved register, do you start with one bank and then move to the other?  Getting in and out of the ASM function isn't a huge deal because it is only the running speed I want to optimize, so a bunch of push's/pop's surrounding it don't affect performance because if it is entering/exiting, it is going back to the non running state.  It is the inner loop where it is processing instructions that needs to be made the fastest it can be.

 

If I use call saved, then I need to push/pop them in the ASM function, but no worries when calling in/out.

If I use call used, then it is the inverse.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If pushes and pops really do not matter, neither does your register strategy.

 

For a C function, I expect the usual is to start with call-used registers.

Its callers will assume they are clobbered regardless of reality.

The function will also have to assume its callees, if any, clobber call-used registers.

That could make the optimal strategy less obvious.

 

For an asm function that is called by C, but does not call anything else,

use the call-used registers first:

the caller will assume they are clobbered regardless, so no harm done to the caller.

 

If the asm function calls another of your asm functions,

that call is not bound by avr-gcc conventions.

You might do their register allocations as a unit.

 

In any case, a reason for calling conventions is to

avoid messing up things like register allocation.

Having a fixed set of call-used and call-saved registers makes it

easy to ensure function calls do not fail from an unsaved register.

Moderation in all things. -- ancient proverb

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Perhaps there are some ideas here:

https://www.avrfreaks.net/commen...

Just a short example how you could run AVR code on an AVR

It's just a test so it actually run the code from flash, and because the AVR's instructions is at least 16bit you have to make 2 loads, where you for a 8080 only need one load first, now I haven't looked at 8080 code for a long time, but my guess is that there is a easy way to find out the size of an instruction just by looking at the first byte in it. (I remember that there are for a 6502).

 

Simple instructions should take in the ballpark of 10 clk, so about 2 mips, or an 8MHz 8080.

 

Add:

Now I remember one of the hard things for an AVR is calculation of the parity flag. (one way is not to do the calculation, but save the result it come from, so when needed then you can make the calculation.)

Last Edited: Thu. Mar 11, 2021 - 10:48 PM