Compare commits

...

2 commits

Author SHA1 Message Date
Ronald Caesar
5e93bb975a
temporary commit 2025-10-24 19:18:02 -04:00
Ronald Caesar
80832fc0fe
temp commit
Signed-off-by: Ronald Caesar <github43132@proton.me>
2025-10-24 17:46:51 -04:00
5 changed files with 143 additions and 43 deletions

View file

@ -97,7 +97,6 @@ add_executable(Pound
add_subdirectory(3rd_Party)
add_subdirectory(src/common)
add_subdirectory(src/frontend)
add_subdirectory(src/host)
add_subdirectory(src/jit)
add_subdirectory(src/pvm)
@ -110,7 +109,7 @@ add_subdirectory(src/targets/switch1/hardware)
include(TestBigEndian)
TEST_BIG_ENDIAN(WORDS_BIGENDIAN)
list(APPEND POUND_PROJECT_TARGETS common frontend host pvm)
list(APPEND POUND_PROJECT_TARGETS common host pvm)
foreach(TARGET ${POUND_PROJECT_TARGETS})
# Apply Endianness definitions to all our targets.
if(WORDS_BIGENDIAN)
@ -149,7 +148,6 @@ set_property(TARGET Pound PROPERTY CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TR
target_link_libraries(Pound PRIVATE
common
frontend
host
jit
pvm

View file

@ -9,7 +9,8 @@ namespace pound::jit::decoder
{
/* Increase value as more instructions get implemented */
#define INSTRUCTION_ARRAY_CAPACITY 4
arm32_decoder_t g_arm32_decoder = {};
#define HASH_TABLE_INVALID_INDEX 0xFFFF
/*
* ============================================================================
@ -18,9 +19,11 @@ arm32_decoder_t g_arm32_decoder = {};
*/
void arm32_add_instruction(arm32_decoder_t* decoder, const char* name, const char* bitstring, arm32_handler_fn handler);
void arm32_ADD_imm_handler(arm32_decoder_t* decoder, arm32_instruction_t instruction);
void arm32_SUB_imm_handler(arm32_decoder_t* decoder, arm32_instruction_t instruction);
void arm32_parse_bitstring(const char* bitstring, uint32_t* mask, uint32_t* expected);
void arm32_grow_instructions_array(arm32_decoder_t* decoder, size_t new_capacity);
uint32_t arm32_generate_hash_seed(uint32_t mask, uint32_t expected);
/*
* ============================================================================
@ -38,7 +41,7 @@ void arm32_init(pound::host::memory::arena_t allocator, arm32_decoder_t* decoder
/* Setup Instructions array.*/
size_t instructions_array_size = INSTRUCTION_ARRAY_CAPACITY * sizeof(arm32_instruction_info_t);
PVM_ASSERT(instructions_array_size <= decoder->allocator.capacity);
LOG_TRACE("Growing instructions array to %d bytes", instructions_array_size);
LOG_TRACE("Allocated %d bytes to instructions array", instructions_array_size);
void* new_ptr = pound::host::memory::arena_allocate(&decoder->allocator, instructions_array_size);
PVM_ASSERT(nullptr != new_ptr);
@ -50,8 +53,97 @@ void arm32_init(pound::host::memory::arena_t allocator, arm32_decoder_t* decoder
#define INST(fn, name, bitstring) arm32_add_instruction(decoder, name, bitstring, &arm32_##fn##_handler);
#include "./arm32.inc"
#undef INST
LOG_TRACE("Initializing perfect hash parameters for %zu instructions", decoder->instruction_count);
/* Start with table size as next power of 2 >= count * 2 */
uint16_t table_size = 1U;
while (table_size < decoder->instruction_count * 2)
{
table_size <<= 1U;
}
uint16_t mask = table_size - 1;
size_t slots_used_size = table_size * sizeof(bool);
bool* slots_used = (bool*)pound::host::memory::arena_allocate(&decoder->allocator, slots_used_size);
PVM_ASSERT(nullptr != slots_used);
LOG_TRACE("Allocated %d bytes to hash slots used array", slots_used_size);
uint16_t shift = 0;
bool perfect_hash_generated = false;
for (; shift < 16; ++shift)
{
bool collision_free = true;
(void)memset(slots_used, false, slots_used_size);
uint16_t hash;
for (size_t i = 0; i < decoder->instruction_count; ++i)
{
uint32_t hash_seed =
arm32_generate_hash_seed(decoder->instructions[i].mask, decoder->instructions[i].expected);
/*
* Now we need to convert the 32-bit hash seed into an index within our hash table.
* We right shift by `shift` bits. Different shift values produce different hash distributions. We found
* the shift value that ensures no two instructions map to the same hash table slot.
*/
hash = ((hash_seed >> shift) & mask);
if (true == slots_used[hash])
{
LOG_TRACE("Instruction %s: Collision detected at slot %u for shift %u", decoder->instructions[i].name,
hash, shift);
collision_free = false;
break;
}
slots_used[hash] = true;
LOG_TRACE("Instruction %s: Collision-free hash %u found for shift %u", decoder->instructions[i].name, hash,
shift);
}
if (true == collision_free)
{
perfect_hash_generated = true;
break;
}
}
PVM_ASSERT_MSG(true == perfect_hash_generated, "Failed to generate perfect hash - no collision-free hash found");
LOG_TRACE("Perfect hash parameters: shift=%u, mask=0x%04x, table_size=%u", shift, mask, table_size);
size_t hash_table_size = table_size * sizeof(uint16_t);
uint16_t* hash_table = (uint16_t*)pound::host::memory::arena_allocate(&decoder->allocator, hash_table_size);
PVM_ASSERT(nullptr != hash_table);
LOG_TRACE("Allocated %zu bytes to hash table", hash_table_size);
/* Initialize hash table with invalid indices */
for (size_t i = 0; i < table_size; ++i)
{
hash_table[i] = HASH_TABLE_INVALID_INDEX;
}
for (size_t i = 0; i < decoder->instruction_count; ++i)
{
uint32_t hash_seed = arm32_generate_hash_seed(decoder->instructions[i].mask, decoder->instructions[i].expected);
uint32_t hash = (hash_seed >> shift) & mask;
PVM_ASSERT_MSG(HASH_TABLE_INVALID_INDEX == hash_table[hash], "Hash collision detected");
PVM_ASSERT(i < 0xFFFF);
hash_table[hash] = (uint16_t)i;
LOG_TRACE("Instruction '%s' hashed to slot %u", decoder->instructions[i].name, hash);
}
decoder->perfect_hash.hash_shift = shift;
decoder->perfect_hash.hash_mask = mask;
decoder->perfect_hash.table_size = table_size;
decoder->perfect_hash.hash_table = hash_table;
}
/*
* ============================================================================
* Private Functions
* ============================================================================
*/
void arm32_add_instruction(arm32_decoder_t* decoder, const char* name, const char* bitstring, arm32_handler_fn handler)
{
PVM_ASSERT(nullptr != decoder);
@ -71,33 +163,17 @@ void arm32_add_instruction(arm32_decoder_t* decoder, const char* name, const cha
info->handler = handler;
/* Calculate priority based on number of fixed bits. */
info->priority = 0;
for (int i = 0; i < 32; ++i)
{
if ((mask >> i) & 1)
{
++info->priority;
}
}
info->priority = (uint8_t)__builtin_popcount(mask);
++decoder->instruction_count;
LOG_TRACE("========================================");
LOG_TRACE("Instruction Registered: %s", info->name);
LOG_TRACE("Mask: 0x%08X", info->mask);
LOG_TRACE("Expected: 0x%08X", info->expected);
LOG_TRACE("Priority: %d", info->priority);
LOG_TRACE("========================================");
/* TODO(GloriousTacoo:jit): Add instruction to lookup table. */
}
void arm32_ADD_imm_handler(arm32_decoder_t* decoder, arm32_instruction_t instruction) {}
/*
* ============================================================================
* Private Functions
* ============================================================================
*/
void arm32_ADD_imm_handler(arm32_decoder_t* decoder, arm32_instruction_t instruction) {}
void arm32_SUB_imm_handler(arm32_decoder_t* decoder, arm32_instruction_t instruction) {}
void arm32_parse_bitstring(const char* bitstring, uint32_t* mask, uint32_t* expected)
{
@ -109,7 +185,7 @@ void arm32_parse_bitstring(const char* bitstring, uint32_t* mask, uint32_t* expe
*mask = 0;
*expected = 0;
uint8_t instruction_size_bits = 32;
for (int i = 0; (i < instruction_size_bits) && (bitstring[i] != '\0'); ++i)
for (unsigned int i = 0; (i < instruction_size_bits) && (bitstring[i] != '\0'); ++i)
{
uint32_t bit_position = 31 - i;
switch (bitstring[i])
@ -134,5 +210,34 @@ void arm32_parse_bitstring(const char* bitstring, uint32_t* mask, uint32_t* expe
}
}
}
uint32_t arm32_generate_hash_seed(uint32_t mask, uint32_t expected)
{
/*
* XOR combines the mask (which bits matter) and expected value (what those btis should be)
* into a single 32-bit value that uniquely represents this instruction pattern.
* Example: ADD instruction might have mask=0x0FF00000, expected=0x00200000
* value = 0x0FF00000 ^ 0x00200000 = 0x0FD00000;
*/
uint32_t value = mask ^ expected;
/*
* First bit mixing round - break up patterns in the value.
* We need to eliminate patterns (like trailing zeroes) that cause collisions.
* We do this by mixing the high bits (16-31) with the low bits (0-15).
*
* We then multiply a magic number to further scrambles the bits to ensure good distribution.
*/
value = ((value >> 16) ^ value) * 0x45d9f3b;
/*
* Second bit mixing round to ensire thorough mixing
*/
value = ((value >> 16) ^ value) * 0x45d9f3b;
/*
* Final mixing. The result is our hash seed - a 32-bit number that uniquely represents this instruction.
*/
value = (value >> 16) ^ value;
return value;
}
} // namespace pound::jit::decoder

View file

@ -13,6 +13,14 @@ typedef uint32_t arm32_instruction_t;
typedef struct arm32_decoder arm32_decoder_t;
typedef void (*arm32_handler_fn)(arm32_decoder_t* decoder, arm32_instruction_t instruction);
typedef struct
{
uint16_t* hash_table;
uint16_t hash_shift;
uint16_t hash_mask;
uint16_t table_size;
} arm32_perfect_hash_t;
typedef struct
{
const char* name;
@ -27,27 +35,15 @@ typedef struct
struct arm32_decoder
{
arm32_perfect_hash_t perfect_hash;
pound::host::memory::arena_t allocator;
arm32_instruction_info_t* instructions;
size_t instruction_count;
size_t instruction_capacity;
struct
{
arm32_instruction_info_t** bucket;
size_t count;
size_t capacity;
} lookup_table[4096]; /* 2^12 entries. */
};
extern arm32_decoder_t g_arm32_decoder;
void arm32_init(pound::host::memory::arena_t allocator, arm32_decoder_t* decoder);
void arm32_add_instruction(arm32_decoder_t* decoder, const char* name, arm32_opcode_t mask, arm32_opcode_t expected,
arm32_handler_fn handler);
void arm32_ADD_imm_handler(arm32_decoder_t* decoder, arm32_instruction_t instruction);
} // namespace pound::jit::decoder
#endif // POUND_JIT_DECODER_ARM32_H
}
#endif // POUND_JIT_DECODER_ARM32_H

View file

@ -64,7 +64,7 @@ INST(ADD_imm, "ADD (imm)", "cccc0010100Snnnnddddrrrrvvvvvvvv") // v1
//INST(SBC_imm, "SBC (imm)", "cccc0010110Snnnnddddrrrrvvvvvvvv") // v1
//INST(SBC_reg, "SBC (reg)", "cccc0000110Snnnnddddvvvvvrr0mmmm") // v1
//INST(SBC_rsr, "SBC (rsr)", "cccc0000110Snnnnddddssss0rr1mmmm") // v1
//INST(SUB_imm, "SUB (imm)", "cccc0010010Snnnnddddrrrrvvvvvvvv") // v1
INST(SUB_imm, "SUB (imm)", "cccc0010010Snnnnddddrrrrvvvvvvvv") // v1
//INST(SUB_reg, "SUB (reg)", "cccc0000010Snnnnddddvvvvvrr0mmmm") // v1
//INST(SUB_rsr, "SUB (rsr)", "cccc0000010Snnnnddddssss0rr1mmmm") // v1
//INST(TEQ_imm, "TEQ (imm)", "cccc00110011nnnn0000rrrrvvvvvvvv") // v1

View file

@ -20,7 +20,8 @@
int main()
{
pound::host::memory::arena_t arena = pound::host::memory::arena_init(256);
pound::jit::decoder::arm32_init(arena, &pound::jit::decoder::g_arm32_decoder);
pound::jit::decoder::arm32_decoder_t decoder = {};
pound::jit::decoder::arm32_init(arena, &decoder);
#if 0
gui::window_t window = {.data = nullptr, .gl_context = nullptr};
(void)gui::window_init(&window, "Pound Emulator", 640, 480);