Background
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?
What I've tried so far
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)
//...
}
Bottom line
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?
Background
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?
What I've tried so far
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)
//...
}
Bottom line
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?
Share Improve this question asked Feb 5 at 12:49 Matteo BonaciniMatteo Bonacini 553 bronze badges 12 | Show 7 more comments4 Answers
Reset to default 5I 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.
Instead of switch
std::map<
Opcode,
std::function<void()>
> instruction = {
{LD_A_B, [&A,&B](){ A = B; }},
{LD_A_C, [&A,&C](){ A = C; }}
//...
};
and then afterwards
Opcode opcode = //...
instruction[opcode]();
The idea here is, since each instruction basically knows what to do, we let the instructions decide where to get their resources from instead of passing them as arguments.
I think you are looking for preprocessor token pasting:
switch (opcode) {
// ...
#define EXPAND_LD_x_y(X, Y)
case LD_ ## X ## _ ## Y: \
X = Y; \
break;
EXPAND_LD_x_y(A, B)
EXPAND_LD_x_y(A, C)
#undef EXPAND_LD_x_y
//...
}
To answer the question from the comments (a matrix of LD_
codes), you might write like this:
EXPAND_LD_X(X) \
EXPAND_LD_x_y(X,A) \
EXPAND_LD_x_y(X,B) \
EXPAND_LD_x_y(X,C)
EXPAND_LD_X(A)
EXPAND_LD_X(B)
EXPAND_LD_X(C)
You can achieve this with a map<Opcode, Register>
and then define your void Processor::LD_x_y(Register& x, Register y) { x = y };
method which you can use as:
LD_x_y(A, yourmap[opcode])
switch
into a separate function, which accepts arguments namedopcode
,a
andb
. For your first example, pass (from the caller)opcode
,A
, andB
to that function. For your second example, pass (from the caller)opcode
,A
, andC
. For other "registers" pass arguments corresponding to each register. Wash your mouth out with soap for considering macros - they are unnecessary in your problem, as described, and introduce a significant range of other problems. – Peter Commented Feb 5 at 13:06switch
, but you can easily populate amap
instance. Yes, programmatically. What's the problem? – Sergey A Kryukov Commented Feb 5 at 13:49