I have an enum type with 1223 elements. I had a function with 1222 cases and a default case in a switch block. If I want to modify some elements in the enum type, I also need to modify that function. Worse, I may have more than one function with a big switch block. So I tried to solve it through a big array of functions, the each of which applies the right action according to the element. Because I also want to have minimal changes to do, I want the function pointer assignment done implicitly, so I use a template trick by letting the array of 1223 elements be viewed as a list of 1223 contiguous sub-arrays of 1 element to do the implicit function pointer assignment through constructors per element.
Macros are forbidden. External libraries including Boost are forbidden as well.
Here comes a simplified code (compilable and runnable if I_LAST_INSTRUCTION
value is much lower) :
#include <iostream>
#include <vector>
using namespace std;
typedef size_t Instr; // dummy one for simplified code
enum
{
I_INVALID = 0,
I_LAST_INSTRUCTION = 1223
};
template< size_t id >
static void Test$(std::vector< Instr > &, bool)
{
cout << "testing instruction #" << id << endl;
}
template< typename Derived, size_t start_id, size_t end_id >
struct Tester$ : Tester$ < Derived, start_id, end_id - 1 >
{
Tester$()
{
static_cast<Derived *>(this)->array[end_id - 1] = Test$< end_id - 1 >;
}
};
template< typename Derived >
struct Tester$ < Derived, 0, 0 >
{
};
struct Tester : Tester$< Tester, I_INVALID, I_LAST_INSTRUCTION >
{
void(*array[size_t(I_LAST_INSTRUCTION)])(std::vector< Instr > & list, bool is64);
void operator()(size_t id, std::vector< Instr > & list, bool is64)
{
if (id < I_LAST_INSTRUCTION)
{
(array[size_t(id)])(list, is64);
}
else
{
// to do nothing
}
}
};
int main()
{
Tester tester;
std::vector< Instr > list;
tester(0, list, true); // display testing instruction #0
tester(1, list, true); // display testing instruction #1
tester(2, list, true); // display testing instruction #2
tester(8, list, true); // display testing instruction #8
tester(1222, list, true); // display testing instruction #1222
tester(1223, list, true); // invalid instruction number - do nothing
}
Because I_LAST_INSTRUCTION
is too high, I got this error with VC2013:
fatal error C1202: recursive type or function dependency context too complex
The compiler appears to accept no more than 499 nested class template instantiations.
The solution I can see is to define the nested class template instantiations as a binary tree so its max depth is close to log2(n) instead of a list (its max depth being n).
So my question is how to implement that meta-list into a meta-binary-tree efficiently in order to make the compiler happy?
Edit: another solution can be to use a list with more elements per sub-array to divide the depth list by the max number of element in a sub-array. Using 4 elements per sub-array solve the issue I had.
Edit 2: more details about why I chose this way
My instructions are described through template classes composition:
namespace x86
{
namespace encoder
{
// Group 8086+
template<> struct Opcode$< I_AAA > : Opcode < I_AAA, 0x00000037, Gw < RW >, DummyRw< 0, AX >, i64 > {};
template<> struct Opcode$< I_AAD > : Opcode < I_AAD, 0x000000D5, Gw_Ib < RW >, DummyRw< 0, AX >, i64 > {};
template<> struct Opcode$< I_AAM > : Opcode < I_AAM, 0x000000D4, Gw_Ib < RW >, DummyRw< 0, AX >, i64 > {};
template<> struct Opcode$< I_AAS > : Opcode < I_AAS, 0x0000003F, Gw < RW >, DummyRw< 0, AX >, i64 > {};
template<> struct Opcode$< I_ADC > :
Switch
<
/**/ Opcode < I_ADC, 0x00000012, Gb_Eb < RW, R >, OSb >,
/**/ Opcode < I_ADC, 0x00000013, Gw_Ew < RW, R >, OSw >,
/**/ Opcode < I_ADC, 0x00000013, Gd_Ed < RW, R >, OSd >,
/**/ Opcode < I_ADC, 0x00000013, Gq_Eq < RW, R >, OSq >,
/**/ Opcode < I_ADC, 0x00000010, Eb_Gb < RW, R >, OSb >,
/**/ Opcode < I_ADC, 0x00000011, Ew_Gw < RW, R >, OSw >,
/**/ Opcode < I_ADC, 0x00000011, Ed_Gd < RW, R >, OSd >,
/**/ Opcode < I_ADC, 0x00000011, Eq_Gq < RW, R >, OSq >,
/**/ Opcode < I_ADC, 0x00000014, AL_Ib < RW > >,
/**/ Opcode < I_ADC, 0x00000080, Eb_Ib < RW >, OSb, Group1 <2> >,
/**/ Opcode < I_ADC, 0x00000083, Ew_Ib < RW >, OSw, Group1 <2> >,
/**/ Opcode < I_ADC, 0x00000083, Ed_Ib < RW >, OSd, Group1 <2> >,
/**/ Opcode < I_ADC, 0x00000083, Eq_Ib < RW >, OSq, Group1 <2> >,
/**/ Opcode < I_ADC, 0x00000015, AX_Iw < RW >, OSw >,
/**/ Opcode < I_ADC, 0x00000015, EAX_Id < RW >, OSd >,
/**/ Opcode < I_ADC, 0x00000015, RAX_Id < RW >, OSq >,
/**/ Opcode < I_ADC, 0x00000081, Ew_Iw < RW >, OSw, Group1 <2> >,
/**/ Opcode < I_ADC, 0x00000081, Ed_Id < RW >, OSd, Group1 <2> >,
/**/ Opcode < I_ADC, 0x00000081, Eq_Id < RW >, OSq, Group1 <2> >
> {};
...
template arguments after the second in Opcode are used for both instruction operands matching for a valid Instr(id, opd1, opd2, ...)
and instruction encoding when an Opcode description matches.
I had a big switch block which is a big pain:
void Backend::EncodeInstr(Instr & instr)
{
switch (instr.id_)
{
case I_AAA: JITASM_ASSERT(encoder::Opcode$< I_AAA >::Encode(instr, is64_)); break;
case I_AAD: JITASM_ASSERT(encoder::Opcode$< I_AAD >::Encode(instr, is64_)); break;
case I_AAM: JITASM_ASSERT(encoder::Opcode$< I_AAM >::Encode(instr, is64_)); break;
case I_AAS: JITASM_ASSERT(encoder::Opcode$< I_AAS >::Encode(instr, is64_)); break;
case I_ADC: JITASM_ASSERT(encoder::Opcode$< I_ADC >::Encode(instr, is64_)); break;
...
And the same for Testinstr (its purpose is to generate a list of instructions matching all opcodes to check the encoder is correct). For instance, TestInstr(I_XOR) will give:
0x10000000( 2): DA32 xor bl, dl
0x10000002( 6): 555555551D32 xor bl, byte ptr [0x55555555]
0x10000008( 3): DA3366 xor bx, dx
0x1000000B( 7): 555555551D3366 xor bx, word ptr [0x55555555]
0x10000012( 2): DA33 xor ebx, edx
0x10000014( 6): 555555551D33 xor ebx, dword ptr [0x55555555]
0x1000001A( 2): DA32 xor bl, dl
0x1000001C( 6): 555555551530 xor byte ptr [0x55555555], dl
0x10000022( 3): DA3366 xor bx, dx
0x10000025( 7): 55555555153166 xor word ptr [0x55555555], dx
0x1000002C( 6): 555555551531 xor dword ptr [0x55555555], edx
0x10000032( 2): 5534 xor al, 0x55
0x10000034( 3): 55F380 xor bl, 0x55
0x10000037( 7): 55555555553580 xor byte ptr [0x55555555], 0x55
0x1000003E( 4): 55F38366 xor bx, 0x55
0x10000042( 8): 5555555555358366 xor word ptr [0x55555555], 0x55
0x1000004A( 3): 55F383 xor ebx, 0x55
0x1000004D( 7): 55555555553583 xor dword ptr [0x55555555], 0x55
0x10000054( 4): 55553566 xor ax, 0x5555
0x10000058( 5): 5555555535 xor eax, 0x55555555
0x1000005D( 5): 5555F38166 xor bx, 0x5555
0x10000062( 9): 555555555555358166 xor word ptr [0x55555555], 0x5555
0x1000006B( 6): 55555555F381 xor ebx, 0x55555555
0x10000071(10): 55555555555555553581 xor dword ptr [0x55555555], 0x55555555
So I only need to define the enum type of intruction id and define the matching opcodes for each instruction id. Everything else is done under the hood, except for the two big switch blocks in both EncodeInstr and TestInstr I had to explicit.