I am in the process of writing a GameBoy emulator in C++. In my code, the processor is implemented with a switch statement like the following:
class Processor {
// ...
void executeOpcode(Opcode opcode) {
switch (opcode) {
// ...
case LD_A_B:
A = B;
break;
case LD_A_C:
A = C;
break;
case LD_A_D:
A = D;
break;
// ...
}
}
// ...
}
Here, Opcode
is just a typedef enum
where the name of each instruction corresponds to its 1-byte opcode. In this example, there is a series of instructions that perform the same operation, but using different registers. This pattern happens for a lot of different instructions, some performing more complex operations than just a simple copy.
What I am asking is: is there a way to programmatically write switch clauses for all the registers of the processor?
I know I could implement a series of functions like this:
void Processor::LD_x_y(Register& x, Register y) { x = y };
so that the switch becomes like:
switch (opcode) {
// ...
case LD_A_B:
LD_x_y(A, B);
break;
case LD_A_C:
LD_x_y(A, C):
break;
//...
}
But repeating this pattern for a lot of instructions would expose me to mistakes like:
switch (opcode) {
// ...
case LD_A_B:
LD_x_y(A, B);
break;
case LD_A_C:
LD_x_y(A, B): // <-- forgot to change the register in the function call!
break;
//...
}
I am not very familiar with using preprocessor directives, but I know that in principle I should be able to write something along the lines of:
#define EXPAND_LD_x_y(X, Y) case LD_X_Y: X=Y; break;
// ...
switch (opcode) {
// ...
EXPAND_LD_x_y(A, B)
EXPAND_LD_x_y(A, C)
//...
}
I believe this last approach is good enough but I would really like to have something that automatically expands the switch cases for each combination of registers. Is there a way to do that with preprocessor directives? Is there any better way to achieve what I'm trying to do?
I don't know at all GameBoy specific stuff, but if they have been smart when designing opcodes and registers, you should be able to use arrays and bitwise operations, as they are the fastest possible ones.
Let's say we have:
enum Opcode {
LD_A_B = 0b0000_00_01,
LD_A_C = 0b0000_00_10,
LD_A_D = 0b0000_00_11,
LD_B_A = 0b0000_01_00,
LD_B_C = 0b0000_01_10,
...
};
Then it means that we have exactly 4 available registers and that we can extract the destination register in bits 6-7, and source in bits 4-5.
int registers[4];
...
switch(opcode) {
case LD_A_B: case LD_A_C: case LD_A_D: case LD_B_A: case LD_B_C:
registers[opcode&0b11] = registers[(opcode>>2)&0b11]
...
}
I'm pretty sure they were especially smart at that time, when every tiny single operations could make everything slow. For sure they thought about such tricks.