I'm writing an emulator of a custom ISA 16-bit processor that has instructions with operands of different sizes in C (C23 standard). In my code, I use enum members to indicate the size of the operands, and unions to model registers like the following code fragments show:
// Registers and other 8/16/(maybe 32/64 too) addressable areas.
typedef union Cell16 {
unsigned char sz8;
unsigned short sz16;
// unsigned long sz32;
// unsigned long long sz64;
} Cell16;
// Represents supported operand size.
typedef enum Op_sz {
Op_sz8 = 8,
Op_sz16 = 16,
// Op_sz32 = 32, // Unsupported (for now).
// Op_sz64 = 64 // Unsupported (for now).
} Op_sz;
I end up frequently using switches on operand size enum values like this (example from MOV handler):
switch (LAYOUT(d)) {
case Ops_layout_Reg2:
switch (SZ_L(d)) {
case Op_sz8: REG_L(s,d).sz8 = REG_R(s,d).sz8; break;
case Op_sz16: REG_L(s,d).sz16 = REG_R(s,d).sz16; break;
default: return EXEC_ERROR_INVALID_OP_SZ;
}
break;
case Ops_layout_RegImm:
switch (SZ_L(d)) {
case Op_sz8: REG_L(s,d).sz8 = IMM1(d).sz8; break;
case Op_sz16: REG_L(s,d).sz16 = IMM1(d).sz16; break;
default: return EXEC_ERROR_INVALID_OP_SZ;
}
break;
case Ops_layout_Imm2:
case Ops_layout_ImmReg:
default: return EXEC_ERROR_INVALID_3ADDR_OPS_LAYOUT;
}
I used macros just to make the code shorter; they are defined as follows and provide access to some fields of the decoder_output d
and state s
:
#define SZ_L(d) d->fmt3.sz_left // type is Op_sz
#define SZ_R(d) d->fmt3.sz_right // type is Op_sz
#define LAYOUT(d) d->fmt3.layout
#define REG_L(s,d) s->GP_regs.raw[d->reg_ids.left] // type is Cell16
#define REG_R(s,d) s->GP_regs.raw[d->reg_ids.right] // type is Cell16
#define IMM1(d) d->imm1 // type is Cell16
The problem is that using these switches is tedious and makes the code ugly. Is there some language feature that can map enum values to assignments of data members of the same sizes? Is there some way to use the preprocessor to encapsulate these switches into a macro?
I haven't found any comfortable way to use preprocessor.
I thought that maybe having some mapping array like static void (*map[])(void)
within the instruction handler function and use it like // pseudocode: map[log2(sz_left) - 3]
to access underlying functions that do what I want. Apparently, it's bad because the compiler cannot inline function pointer calls, and because there's static storage for every instruction handler function.
I would appreciate any other ideas to make the code clearer.
Here is a generic solution with fewer switches that you may test for efficiency:
uint8_t *dst;
const uint8_t *src;
switch (LAYOUT(d)) {
case Ops_layout_Reg2:
dst = ®_L(s,d);
src = ®_R(s,d);
break;
case Ops_layout_RegImm:
dst = ®_L(s,d);
src = &IMM1(d);
break;
case Ops_layout_Imm2:
case Ops_layout_ImmReg:
default: return EXEC_ERROR_INVALID_3ADDR_OPS_LAYOUT;
}
const uint8_t *end = src + SZ_L(d) / 8;
for (;;) {
*dst++ = *src++;
if (src >= end)
break;
}