11#include " arm32.h"
2- #include < string.h>
32#include " common/passert.h"
3+ #include < string.h>
44
55#define LOG_MODULE " jit"
66#include " common/logging.h"
@@ -9,18 +9,19 @@ namespace pound::jit::decoder
99{
1010/* Increase value as more instructions get implemented */
1111#define INSTRUCTION_ARRAY_CAPACITY 4
12- arm32_decoder_t g_arm32_decoder = {};
12+
13+ #define HASH_TABLE_INVALID_INDEX 0xFFFF
1314
1415/*
1516 * ============================================================================
1617 * Foward Declarations
1718 * ============================================================================
1819 */
19- void arm32_add_instruction (arm32_decoder_t * decoder, const char * name, const char * bitstring, arm32_handler_fn handler);
20- void arm32_ADD_imm_handler (arm32_decoder_t * decoder, arm32_instruction_t instruction);
20+ void arm32_add_instruction (arm32_decoder_t * decoder, const char * name, const char * bitstring);
2121
2222void arm32_parse_bitstring (const char * bitstring, uint32_t * mask, uint32_t * expected);
2323void arm32_grow_instructions_array (arm32_decoder_t * decoder, size_t new_capacity);
24+ uint32_t arm32_generate_hash_seed (uint32_t mask, uint32_t expected);
2425
2526/*
2627 * ============================================================================
@@ -38,7 +39,7 @@ void arm32_init(pound::host::memory::arena_t allocator, arm32_decoder_t* decoder
3839 /* Setup Instructions array.*/
3940 size_t instructions_array_size = INSTRUCTION_ARRAY_CAPACITY * sizeof (arm32_instruction_info_t );
4041 PVM_ASSERT (instructions_array_size <= decoder->allocator .capacity );
41- LOG_TRACE (" Growing instructions array to %d bytes " , instructions_array_size);
42+ LOG_TRACE (" Allocated %d bytes to instructions array " , instructions_array_size);
4243
4344 void * new_ptr = pound::host::memory::arena_allocate (&decoder->allocator , instructions_array_size);
4445 PVM_ASSERT (nullptr != new_ptr);
@@ -47,57 +48,131 @@ void arm32_init(pound::host::memory::arena_t allocator, arm32_decoder_t* decoder
4748 decoder->instruction_capacity = INSTRUCTION_ARRAY_CAPACITY;
4849
4950 /* Add all Arm32 instructions */
50- #define INST (fn, name, bitstring ) arm32_add_instruction(decoder, name, bitstring, &arm32_##fn##_handler );
51+ #define INST (fn, name, bitstring ) arm32_add_instruction(decoder, name, bitstring);
5152#include " ./arm32.inc"
5253#undef INST
54+
55+ LOG_TRACE (" Initializing perfect hash parameters for %zu instructions" , decoder->instruction_count );
56+
57+ /* Start with table size as next power of 2 >= count * 2 */
58+ uint16_t table_size = 1U ;
59+ while (table_size < decoder->instruction_count * 2 )
60+ {
61+ table_size <<= 1U ;
62+ }
63+
64+ uint16_t mask = table_size - 1 ;
65+ size_t slots_used_size = table_size * sizeof (bool );
66+ bool * slots_used = (bool *)pound::host::memory::arena_allocate (&decoder->allocator , slots_used_size);
67+ PVM_ASSERT (nullptr != slots_used);
68+ LOG_TRACE (" Allocated %d bytes to hash slots used array" , slots_used_size);
69+
70+ uint16_t shift = 0 ;
71+ bool perfect_hash_generated = false ;
72+ for (; shift < 16 ; ++shift)
73+ {
74+ bool collision_free = true ;
75+ (void )memset (slots_used, false , slots_used_size);
76+
77+ uint16_t hash;
78+ for (size_t i = 0 ; i < decoder->instruction_count ; ++i)
79+ {
80+ uint32_t hash_seed =
81+ arm32_generate_hash_seed (decoder->instructions [i].mask , decoder->instructions [i].expected );
82+
83+ /*
84+ * Now we need to convert the 32-bit hash seed into an index within our hash table.
85+ * We right shift by `shift` bits. Different shift values produce different hash distributions. We found
86+ * the shift value that ensures no two instructions map to the same hash table slot.
87+ */
88+ hash = ((hash_seed >> shift) & mask);
89+ if (true == slots_used[hash])
90+ {
91+ LOG_TRACE (" Instruction %s: Collision detected at slot %u for shift %u" , decoder->instructions [i].name ,
92+ hash, shift);
93+ collision_free = false ;
94+ break ;
95+ }
96+ slots_used[hash] = true ;
97+ LOG_TRACE (" Instruction %s: Collision-free hash %u found for shift %u" , decoder->instructions [i].name , hash,
98+ shift);
99+ }
100+ if (true == collision_free)
101+ {
102+ perfect_hash_generated = true ;
103+ break ;
104+ }
105+ }
106+
107+ PVM_ASSERT_MSG (true == perfect_hash_generated, " Failed to generate perfect hash - no collision-free hash found" );
108+ LOG_TRACE (" Perfect hash parameters: shift=%u, mask=0x%04x, table_size=%u" , shift, mask, table_size);
109+
110+ size_t hash_table_size = table_size * sizeof (uint16_t );
111+ uint16_t * hash_table = (uint16_t *)pound::host::memory::arena_allocate (&decoder->allocator , hash_table_size);
112+ PVM_ASSERT (nullptr != hash_table);
113+ LOG_TRACE (" Allocated %zu bytes to hash table" , hash_table_size);
114+
115+ /* Initialize hash table with invalid indices */
116+ for (size_t i = 0 ; i < table_size; ++i)
117+ {
118+ hash_table[i] = HASH_TABLE_INVALID_INDEX;
119+ }
120+
121+ for (size_t i = 0 ; i < decoder->instruction_count ; ++i)
122+ {
123+ uint32_t hash_seed = arm32_generate_hash_seed (decoder->instructions [i].mask , decoder->instructions [i].expected );
124+ uint32_t hash = (hash_seed >> shift) & mask;
125+ PVM_ASSERT_MSG (HASH_TABLE_INVALID_INDEX == hash_table[hash], " Hash collision detected" );
126+
127+ PVM_ASSERT (i < 0xFFFF );
128+ hash_table[hash] = (uint16_t )i;
129+
130+ LOG_TRACE (" Instruction '%s' hashed to slot %u" , decoder->instructions [i].name , hash);
131+ }
132+
133+ decoder->perfect_hash .hash_shift = shift;
134+ decoder->perfect_hash .hash_mask = mask;
135+ decoder->perfect_hash .table_size = table_size;
136+ decoder->perfect_hash .hash_table = hash_table;
137+ }
138+
139+ arm32_instruction_info_t * arm32_decode (arm32_decoder_t * decoder, uint32_t instruction)
140+ {
141+
53142}
143+ /*
144+ * ============================================================================
145+ * Private Functions
146+ * ============================================================================
147+ */
54148
55- void arm32_add_instruction (arm32_decoder_t * decoder, const char * name, const char * bitstring, arm32_handler_fn handler )
149+ void arm32_add_instruction (arm32_decoder_t * decoder, const char * name, const char * bitstring)
56150{
57151 PVM_ASSERT (nullptr != decoder);
58152 PVM_ASSERT (nullptr != decoder->allocator .data );
59153 PVM_ASSERT (nullptr != name);
60154 PVM_ASSERT (nullptr != bitstring);
61155 PVM_ASSERT (decoder->instruction_count < decoder->instruction_capacity );
62156
63- arm32_opcode_t mask = 0 ;
64- arm32_opcode_t expected = 0 ;
157+ uint32_t mask = 0 ;
158+ uint32_t expected = 0 ;
65159 arm32_parse_bitstring (bitstring, &mask, &expected);
66160
67161 arm32_instruction_info_t * info = &decoder->instructions [decoder->instruction_count ];
162+ PVM_ASSERT (nullptr != info);
68163 info->name = name;
69164 info->mask = mask;
70165 info->expected = expected;
71- info->handler = handler;
72166
73167 /* Calculate priority based on number of fixed bits. */
74- info->priority = 0 ;
75- for (int i = 0 ; i < 32 ; ++i)
76- {
77- if ((mask >> i) & 1 )
78- {
79- ++info->priority ;
80- }
81- }
82-
168+ info->priority = (uint8_t )__builtin_popcount (mask);
83169 ++decoder->instruction_count ;
84- LOG_TRACE ( " ======================================== " );
170+
85171 LOG_TRACE (" Instruction Registered: %s" , info->name );
86172 LOG_TRACE (" Mask: 0x%08X" , info->mask );
87173 LOG_TRACE (" Expected: 0x%08X" , info->expected );
88174 LOG_TRACE (" Priority: %d" , info->priority );
89- LOG_TRACE (" ========================================" );
90-
91-
92- /* TODO(GloriousTacoo:jit): Add instruction to lookup table. */
93175}
94- void arm32_ADD_imm_handler (arm32_decoder_t * decoder, arm32_instruction_t instruction) {}
95-
96- /*
97- * ============================================================================
98- * Private Functions
99- * ============================================================================
100- */
101176
102177void arm32_parse_bitstring (const char * bitstring, uint32_t * mask, uint32_t * expected)
103178{
@@ -109,7 +184,7 @@ void arm32_parse_bitstring(const char* bitstring, uint32_t* mask, uint32_t* expe
109184 *mask = 0 ;
110185 *expected = 0 ;
111186 uint8_t instruction_size_bits = 32 ;
112- for (int i = 0 ; (i < instruction_size_bits) && (bitstring[i] != ' \0 ' ); ++i)
187+ for (unsigned int i = 0 ; (i < instruction_size_bits) && (bitstring[i] != ' \0 ' ); ++i)
113188 {
114189 uint32_t bit_position = 31 - i;
115190 switch (bitstring[i])
@@ -135,4 +210,36 @@ void arm32_parse_bitstring(const char* bitstring, uint32_t* mask, uint32_t* expe
135210 }
136211}
137212
213+ uint32_t arm32_generate_hash_seed (uint32_t mask, uint32_t expected)
214+ {
215+ /*
216+ * XOR combines the mask (which bits matter) and expected value (what those btis should be)
217+ * into a single 32-bit value that uniquely represents this instruction pattern.
218+ * Example: ADD instruction might have mask=0x0FF00000, expected=0x00200000
219+ * value = 0x0FF00000 ^ 0x00200000 = 0x0FD00000;
220+ */
221+ uint32_t value = mask ^ expected;
222+
223+ /*
224+ * First bit mixing round - break up patterns in the value.
225+ * We need to eliminate patterns (like trailing zeroes) that cause collisions.
226+ * We do this by mixing the high bits (16-31) with the low bits (0-15).
227+ *
228+ * We then multiply a magic number to further scrambles the bits to ensure good distribution.
229+ */
230+ value = ((value >> 16 ) ^ value) * 0x45d9f3b ;
231+
232+ /*
233+ * Second bit mixing round to ensire thorough mixing
234+ */
235+ value = ((value >> 16 ) ^ value) * 0x45d9f3b ;
236+
237+ /*
238+ * Final mixing. The result is our hash seed - a 32-bit number that uniquely represents this instruction.
239+ */
240+ value = (value >> 16 ) ^ value;
241+ return value;
242+ }
243+
244+
138245} // namespace pound::jit::decoder
0 commit comments