Typically, when programming an emulator one of the most tricky parts to get down is the efficient decoding of operations.
This can sometimes represent one of the biggest overheads of an emulator and it’s important to be able to write an efficient and easily maintable decoder.
While I was writing my Chip 8 emulator, I came across many different ways of decoding, and through trying a lot of them I got a good feel of the advantages and disadvantages of some of the more common methods.
Switch statements Link to heading
One common approach, found especially often in emulators for small systems like Chip-8, and emulators wrote by those new to emulator development is a massive switch statement.
The way it works is pretty simple: switch on some bit or byte of an instruction, and run the instruction corresponding to it.
Most compilers should turn such a switch statement into a jump table so long as enough cases are covered.
Optimisations like these make switch statements performant enough for simple emulation, and I initially set out to use one, but came across one major downside: clarity.
A massive switch statement becomes a pain to sort through, and it’s often quite unwieldy and inelegant to fix any bugs with it.
Performance was also a factor, while as I mentioned, a jump table would be perfectly fast and efficient enough for my needs, some basic investigation reveals that it’s possible to get better performance, and better clarity, using a method such as :
Function Pointers Link to heading
This involves creating an array of pointers to functions that implement the instructions of the emulator.
The main point of consideration is what you are going to use as the index into the array, analogous to choosing which bits to switch on in the prior method.
This is both more performant, and more elegant that a giant switch statement, and is the method I personally chose for my Chip 8 emulator.
Overall takeaway Link to heading
One interesting overall takeaway is that the specific performance of both these methods are very dependent on other, external factors like the toolchain / compiler used.
When considering performance, specific testing and profiling should be done
This means that a higher priority should be given to ease of expansion, and clarity, as these are metrics that are immediately obvious and won’t change very much from toolchain to toolchain.