|
6 | 6 | #include "common/logging.h" |
7 | 7 |
|
8 | 8 | namespace pound::jit::decoder { |
9 | | -#define INSTRUCTION_ARRAY_CAPACITY 261 |
| 9 | + |
| 10 | +/*! @brief Maximum number of instructions allowed in a single hash bucket. */ |
| 11 | +#define LOOKUP_TABLE_MAX_BUCKET_SIZE 8 |
| 12 | + |
| 13 | +/*! @brief Size of the lookup table (12-bit index). */ |
| 14 | +#define LOOKUP_TABLE_INDEX_MASK 0xFFF |
| 15 | + |
| 16 | +/*! @brief Expected length of the bitstring representation in the .inc file. */ |
10 | 17 | #define INSTRUCTION_BITSTRING_LENGTH 32 |
11 | 18 |
|
12 | | -/* |
13 | | - * ============================================================================ |
14 | | - * Foward Declarations |
15 | | - * ============================================================================ |
| 19 | +/*! |
| 20 | + * @brief A bucket within the decoding hash table. |
| 21 | + * @details Stores a list of instructions that collide on the same hash index. |
16 | 22 | */ |
17 | | -static void arm32_add_instruction(arm32_decoder_t *p_decoder, |
18 | | - const char *p_name, |
19 | | - const char *p_bitstring); |
20 | | -static void arm32_parse_bitstring(const char *p_bitstring, |
21 | | - uint32_t *p_mask, |
22 | | - uint32_t *p_expected); |
| 23 | +typedef struct |
| 24 | +{ |
| 25 | + /*! @brief Array of pointers to instruction definitions. */ |
| 26 | + const arm32_instruction_info_t *instructions[LOOKUP_TABLE_MAX_BUCKET_SIZE]; |
| 27 | + |
| 28 | + /*! @brief Current number of instructions in this bucket. */ |
| 29 | + size_t count; |
| 30 | +} decode_bucket_t; |
| 31 | + |
| 32 | +/*! @brief The internal state of the decoder. */ |
| 33 | +typedef struct |
| 34 | +{ |
| 35 | + /*! |
| 36 | + * @brief The main lookup table. |
| 37 | + * @details Index constructed from bits [27:20] and [7:4] of the |
| 38 | + * instruction. |
| 39 | + */ |
| 40 | + decode_bucket_t lookup_table[LOOKUP_TABLE_INDEX_MASK + 1]; |
| 41 | + |
| 42 | + /*! @brief Initialization guard flag. */ |
| 43 | + bool is_initialized; |
| 44 | +} decoder_t; |
| 45 | + |
| 46 | +/*! @brief Global decoder instance. */ |
| 47 | +static decoder_t g_decoder = {}; |
23 | 48 |
|
24 | 49 | /* |
25 | 50 | * ============================================================================ |
26 | | - * Public Functions |
| 51 | + * Compile-Time Functions |
27 | 52 | * ============================================================================ |
28 | 53 | */ |
29 | | -void |
30 | | -arm32_init (pound::host::memory::arena_t allocator, arm32_decoder_t *p_decoder) |
31 | | -{ |
32 | | - PVM_ASSERT(nullptr != p_decoder); |
33 | | - PVM_ASSERT(nullptr != allocator.data); |
34 | 54 |
|
35 | | - (void)memset(p_decoder, 0, sizeof(arm32_decoder_t)); |
36 | | - p_decoder->allocator = allocator; |
| 55 | +/*! @brief Prototype for a non-constexpr function to force build failure. */ |
| 56 | +void BUILD_ERROR_ARM32_BITSTRING_MUST_BE_32_CHARS(void); |
37 | 57 |
|
38 | | - /* Setup Instructions array.*/ |
39 | | - size_t instructions_array_size |
40 | | - = INSTRUCTION_ARRAY_CAPACITY * sizeof(arm32_instruction_info_t); |
41 | | - PVM_ASSERT(instructions_array_size <= p_decoder->allocator.capacity); |
42 | | - LOG_TRACE("Allocated %d bytes to instructions array", |
43 | | - instructions_array_size); |
| 58 | +/*! |
| 59 | + * @brief Parses a binary string literal into a bitmask at compile time. |
| 60 | + * |
| 61 | + * @param[in] string The null-terminated string literal. |
| 62 | + * @return The calculated uint32_t mask. |
| 63 | + */ |
| 64 | +consteval uint32_t |
| 65 | +parse_mask (const char *string) |
| 66 | +{ |
| 67 | + size_t length = 0; |
44 | 68 |
|
45 | | - void *p_memory = pound::host::memory::arena_allocate( |
46 | | - &p_decoder->allocator, instructions_array_size); |
47 | | - PVM_ASSERT(nullptr != p_memory); |
| 69 | + for (; string[length] != '\0'; ++length) |
| 70 | + { |
| 71 | + } |
48 | 72 |
|
49 | | - p_decoder->p_instructions = (arm32_instruction_info_t *)p_memory; |
50 | | - p_decoder->instruction_capacity = INSTRUCTION_ARRAY_CAPACITY; |
| 73 | + if (length != 32) |
| 74 | + { |
| 75 | + BUILD_ERROR_ARM32_BITSTRING_MUST_BE_32_CHARS(); |
| 76 | + } |
51 | 77 |
|
52 | | - /* Add all Arm32 instructions */ |
53 | | -#define INST(fn, name, bitstring) \ |
54 | | - arm32_add_instruction(p_decoder, name, bitstring); |
55 | | -#include "./arm32.inc" |
56 | | -#undef INST |
| 78 | + uint32_t mask = 0; |
| 79 | + for (int i = 0; i < 32; ++i) |
| 80 | + { |
| 81 | + const uint32_t bit = 1U << (31 - i); |
| 82 | + if ('0' == string[i] || string[i] == '1') |
| 83 | + { |
| 84 | + mask |= bit; |
| 85 | + } |
| 86 | + } |
| 87 | + return mask; |
57 | 88 | } |
58 | 89 |
|
59 | | -arm32_instruction_info_t * |
60 | | -arm32_decode (arm32_decoder_t *p_decoder, uint32_t instruction) |
| 90 | +/*! |
| 91 | + * @brief Parses a binary string literal into an expected value at compile time. |
| 92 | + * |
| 93 | + * @param[in] string The null-terminated string literal. |
| 94 | + * @return The calculated uint32_t expected value. |
| 95 | + */ |
| 96 | +consteval uint32_t |
| 97 | +parse_expected (const char *string) |
61 | 98 | { |
62 | | - for (size_t i = 0; i < p_decoder->instruction_count; ++i) |
| 99 | + uint32_t expected = 0; |
| 100 | + for (int i = 0; i < 32; ++i) |
63 | 101 | { |
64 | | - arm32_instruction_info_t *p_info = &p_decoder->p_instructions[i]; |
65 | | - if ((instruction & p_info->mask) == p_info->expected) |
| 102 | + const uint32_t bit = 1U << (31 - i); |
| 103 | + if (string[i] == '1') |
66 | 104 | { |
67 | | - LOG_TRACE("Instruction found for 0x%08X: %s", |
68 | | - instruction, |
69 | | - p_info->p_name); |
70 | | - return p_info; |
| 105 | + expected |= bit; |
71 | 106 | } |
72 | 107 | } |
73 | | - PVM_ASSERT_MSG(false, "No instruction found for 0x%08X", instruction); |
| 108 | + return expected; |
74 | 109 | } |
75 | 110 |
|
| 111 | +/*! @brief List of all supported ARM32 instructions. */ |
| 112 | +static const arm32_instruction_info_t g_instructions[] = { |
| 113 | +#define INST(fn, name, bitstring) \ |
| 114 | + { name, parse_mask(bitstring), parse_expected(bitstring) }, |
| 115 | +#include "arm32.inc" |
| 116 | +#undef INST |
| 117 | +}; |
| 118 | + |
| 119 | +/*! @brief The total number of defined instructions. */ |
| 120 | +#define INSTRUCTION_ARRAY_CAPACITY \ |
| 121 | + (sizeof(g_instructions) / sizeof(g_instructions[0])) |
| 122 | + |
76 | 123 | /* |
77 | 124 | * ============================================================================ |
78 | | - * Private Functions |
| 125 | + * Public Functions |
79 | 126 | * ============================================================================ |
80 | 127 | */ |
81 | 128 |
|
82 | | -static void |
83 | | -arm32_add_instruction (arm32_decoder_t *p_decoder, |
84 | | - const char *p_name, |
85 | | - const char *p_bitstring) |
| 129 | +void |
| 130 | +arm32_init (void) |
86 | 131 | { |
87 | | - PVM_ASSERT(nullptr != p_decoder); |
88 | | - PVM_ASSERT(nullptr != p_decoder->allocator.data); |
89 | | - PVM_ASSERT(nullptr != p_name); |
90 | | - PVM_ASSERT(nullptr != p_bitstring); |
91 | | - PVM_ASSERT(p_decoder->instruction_count < p_decoder->instruction_capacity); |
| 132 | + PVM_ASSERT_MSG(false == g_decoder.is_initialized, |
| 133 | + "Decoder already initialized."); |
92 | 134 |
|
93 | | - uint32_t mask = 0; |
94 | | - uint32_t expected = 0; |
95 | | - arm32_parse_bitstring(p_bitstring, &mask, &expected); |
| 135 | + (void)memset(g_decoder.lookup_table, 0, sizeof(g_decoder.lookup_table)); |
| 136 | + |
| 137 | + // Populate the hash table. |
| 138 | + for (uint32_t i = 0; i <= LOOKUP_TABLE_INDEX_MASK; ++i) |
| 139 | + { |
| 140 | + decode_bucket_t *bucket = &g_decoder.lookup_table[i]; |
96 | 141 |
|
97 | | - arm32_instruction_info_t *p_info |
98 | | - = &p_decoder->p_instructions[p_decoder->instruction_count]; |
99 | | - PVM_ASSERT(nullptr != p_info); |
100 | | - p_info->p_name = p_name; |
101 | | - p_info->mask = mask; |
102 | | - p_info->expected = expected; |
| 142 | + // Reconstruct the instruction bits that correspond to this hash index. |
| 143 | + // Bits [27:20] and [7:4]. |
| 144 | + const uint32_t synthetic_instruction |
| 145 | + = ((i & 0xFF0) << 16) | ((i & 0xF) << 4); |
103 | 146 |
|
104 | | - ++p_decoder->instruction_count; |
| 147 | + for (size_t ii = 0; ii < INSTRUCTION_ARRAY_CAPACITY; ++ii) |
| 148 | + { |
| 149 | + const arm32_instruction_info_t *info = &g_instructions[ii]; |
| 150 | + |
| 151 | + /* Mask corresponding to the hash bits: 0x0FF000F0 */ |
| 152 | + const uint32_t index_bits_mask = 0x0FF000F0; |
| 153 | + const uint32_t relevant_mask = index_bits_mask | info->mask; |
| 154 | + |
| 155 | + if ((synthetic_instruction & relevant_mask) |
| 156 | + == (info->expected & relevant_mask)) |
| 157 | + { |
| 158 | + LOG_TRACE("Mapping instruction '%s' to LUT Index 0x%03X", |
| 159 | + info->name, |
| 160 | + i); |
| 161 | + |
| 162 | + if (bucket->count >= LOOKUP_TABLE_MAX_BUCKET_SIZE) |
| 163 | + { |
| 164 | + PVM_ASSERT_MSG( |
| 165 | + false, |
| 166 | + "ARM32 LUT Collision Overflow at index 0x%03X. " |
| 167 | + "Increase MAX_LUT_BUCKET_SIZE.", |
| 168 | + i); |
| 169 | + } |
| 170 | + |
| 171 | + if (bucket->count >= (LOOKUP_TABLE_MAX_BUCKET_SIZE / 2)) |
| 172 | + { |
| 173 | + LOG_WARNING( |
| 174 | + "High collision density at Index 0x%03X (Count: %zu).", |
| 175 | + i, |
| 176 | + bucket->count + 1); |
| 177 | + } |
| 178 | + bucket->instructions[bucket->count] = info; |
| 179 | + ++bucket->count; |
| 180 | + } |
| 181 | + } |
| 182 | + } |
105 | 183 |
|
106 | | - LOG_TRACE("Instruction Registered: %s", p_info->p_name); |
107 | | - LOG_TRACE("Mask: 0x%08X", p_info->mask); |
108 | | - LOG_TRACE("Expected: 0x%08X", p_info->expected); |
| 184 | + g_decoder.is_initialized = true; |
| 185 | + LOG_INFO("ARM32 Decoder initialized with %zu instructions", |
| 186 | + INSTRUCTION_ARRAY_CAPACITY); |
109 | 187 | } |
110 | 188 |
|
111 | | -static void |
112 | | -arm32_parse_bitstring (const char *p_bitstring, |
113 | | - uint32_t *p_mask, |
114 | | - uint32_t *p_expected) |
| 189 | +const arm32_instruction_info_t * |
| 190 | +arm32_decode (const uint32_t instruction) |
115 | 191 | { |
116 | | - PVM_ASSERT(nullptr != p_bitstring); |
117 | | - PVM_ASSERT(nullptr != p_mask); |
118 | | - PVM_ASSERT(nullptr != p_expected); |
119 | | - PVM_ASSERT(INSTRUCTION_BITSTRING_LENGTH == strlen(p_bitstring)); |
120 | | - |
121 | | - *p_mask = 0; |
122 | | - *p_expected = 0; |
123 | | - uint8_t instruction_size_bits = 32; |
124 | | - for (unsigned int i = 0; |
125 | | - (i < instruction_size_bits) && (p_bitstring[i] != '\0'); |
126 | | - ++i) |
| 192 | + PVM_ASSERT_MSG(true == g_decoder.is_initialized, |
| 193 | + "Decoder needs to initialize."); |
| 194 | + |
| 195 | + /* Extract hash key: Bits [27:20] and [7:4] */ |
| 196 | + const uint32_t major = (instruction >> 20) & 0xFF; |
| 197 | + const uint32_t minor = (instruction >> 4) & 0xF; |
| 198 | + |
| 199 | + const uint16_t index = (uint16_t)((major << 4) | minor); |
| 200 | + |
| 201 | + const decode_bucket_t *bucket = &g_decoder.lookup_table[index]; |
| 202 | + |
| 203 | + for (size_t i = 0; i < bucket->count; ++i) |
127 | 204 | { |
128 | | - uint32_t bit_position = 31 - i; |
129 | | - switch (p_bitstring[i]) |
| 205 | + const arm32_instruction_info_t *info = bucket->instructions[i]; |
| 206 | + |
| 207 | + if ((instruction & info->mask) == info->expected) |
130 | 208 | { |
131 | | - case '0': |
132 | | - *p_mask |= (1U << bit_position); |
133 | | - break; |
134 | | - case '1': |
135 | | - *p_mask |= (1U << bit_position); |
136 | | - *p_expected |= (1U << bit_position); |
137 | | - default: |
138 | | - break; |
| 209 | + |
| 210 | + return info; |
139 | 211 | } |
140 | 212 | } |
| 213 | + |
| 214 | + return nullptr; |
141 | 215 | } |
142 | | -} // namespace pound::jit::p_decoder |
| 216 | + |
| 217 | +} // namespace pound::jit::decoder |
0 commit comments