diff --git a/src/README.md b/src/README.md index daca945..7b37da5 100644 --- a/src/README.md +++ b/src/README.md @@ -2,7 +2,11 @@ Pound's current goal is to create a Task-Based Parallel JIT with Work-Stealing. This should hopefully be faster than Dynarmic's multithreading model. Wish me luck. ## Roadmap: - [ ] Interpreter: Runs code immediately while the JIT compiles a faster version in the background. + [ ] Interpreter: Runs code immediately while the JIT compiles a faster version in the background. I will be using LuaJIT's and Dolphin Emulator's Interpreter to guide me so that this wont take years to implement. + + 1. [x] Translator: Convert raw arm32 binary code into Intermediate Representation. + + 2. [ ] Register-Based Execution Engine: Run the converted binary code. [ ] Single Worker Pipeline Model: Basic asynchronous compilation. diff --git a/src/common/logging.c b/src/common/logging.c index daf9b17..98385a5 100644 --- a/src/common/logging.c +++ b/src/common/logging.c @@ -36,24 +36,16 @@ * Static strings to use when all else fails. * Its unique format makes it easy to search for in logs. */ -static const char* const FAILED_TIMESTAMP = "[TIMESTAMP_UNAVAILABLE]"; static const char* const FAILED_LOG_LEVEL = "[LOG_LEVEL_UNAVAILABLE]"; log_level_t runtime_log_level = LOG_LEVEL_NONE; -/* - * Pre-allocate a buffer for the timestamp string. - * Make it static so it's not constantly re-allocated on the stack. - */ -static char timestamp_buffer[TIMESTMP_BUFFER_LEN] = {0}; - const char* get_current_timestamp_str(void); void log_message(log_level_t level, const char* module_name, const char* file, int line, const char* message, ...) { assert(NULL != message); - const char* timestamp_str = get_current_timestamp_str(); const char* level_str = NULL; if (level < runtime_log_level) @@ -96,7 +88,7 @@ void log_message(log_level_t level, const char* module_name, const char* file, i /* Keep track of our position in the buffer */ size_t offset = 0; - offset += (size_t)snprintf(buffer + offset, LOG_LINE_BUFFER_SIZE - offset, "[%s] [%s] [%s] [%s:%d] ", timestamp_str, + offset += (size_t)snprintf(buffer + offset, LOG_LINE_BUFFER_SIZE - offset, "[%s] [%s] [%s:%d] ", level_str, module_name, file, line); /* Handle the user's variadic format string. */ @@ -123,44 +115,3 @@ void log_message(log_level_t level, const char* module_name, const char* file, i * unlock_logging_mutex(); */ } - -const char* get_current_timestamp_str(void) -{ - time_t now; - - /* time() can fail, though it's rare. */ - if (time(&now) == (time_t)-1) - { - return FAILED_TIMESTAMP; - } - - struct tm time_since_epoch = {0}; -#ifdef WIN32 - /* https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/gmtime-s-gmtime32-s-gmtime64-s?view=msvc-170 */ - (void) gmtime_r(&now, &time_since_epoch); - -#if 0 - TODO(GloriousTacoo:common): Someone fix this error handling. I have little - patience for anything Windows related. - if ((struct tm)-1 == &time_since_epoch) - { - return FAILED_TIMESTAMP; - } -#endif - -#else - if (NULL == gmtime_r(&now, &time_since_epoch)) - { - return FAILED_TIMESTAMP; - } -#endif // WIN32 - - size_t bytes_written = strftime(timestamp_buffer, TIMESTMP_BUFFER_LEN, "%Y-%m-%dT%H:%M:%SZ", &time_since_epoch); - - if (0 == bytes_written) - { - return FAILED_TIMESTAMP; - } - - return timestamp_buffer; -} diff --git a/src/common/pmath.h b/src/common/pmath.h new file mode 100644 index 0000000..44bfc1d --- /dev/null +++ b/src/common/pmath.h @@ -0,0 +1,67 @@ +#ifndef POUND_COMMON_MATH_H +#define POUND_COMMON_MATH_H +#include +#include + +/*! + * @brief Performs a checked multiplication of two `size_t` values. + * + * @details + * Calculates `a * b` and stores the result in the variable pointed to by `res`. + * + * @param[in] a + * The multiplicand. + * @param[in] b + * The multiplier. + * @param[out] res + * Pointer to the destination variable. + * **Precondition:** Must not be NULL. + * **Postcondition:** On success, contains `a * b`. On overflow, value is undefined. + * + * @retval false + * Success. The multiplication was performed safely. + * @retval true + * **Overflow Detected.** The result exceeds `SIZE_MAX`. + */ +static inline bool safe_multiply_size_t(size_t a, size_t b, size_t* res) +{ +#if defined(__GNUC__) || defined(__clang__) + return __builtin_mul_overflow(a, b, res); +#else + if (b > 0 && a > SIZE_MAX / b) return true; + *res = a * b; + return false; +#endif +} + +/*! + * @brief Performs a checked addition of two `size_t` values. + * + * @details + * Calculates `a + b` and stores the result in the variable pointed to by `res`. + * + * @param[in] a + * The first addend. + * @param[in] b + * The second addend. + * @param[out] res + * Pointer to the destination variable. + * **Precondition:** Must not be NULL. + * **Postcondition:** On success, contains `a + b`. On overflow, value is undefined. + * + * @retval false + * Success. The addition was performed safely. + * @retval true + * **Overflow Detected.** The result exceeds `SIZE_MAX`. + */ +static inline bool safe_add_size_t(size_t a, size_t b, size_t* res) +{ +#if defined(__GNUC__) || defined(__clang__) + return __builtin_add_overflow(a, b, res); +#else + if (a > SIZE_MAX - b) return true; + *res = a + b; + return false; +#endif +} +#endif // POUND_COMMON_MATH_H diff --git a/src/host/CMakeLists.txt b/src/host/CMakeLists.txt index 3e31653..05cbbf5 100644 --- a/src/host/CMakeLists.txt +++ b/src/host/CMakeLists.txt @@ -4,6 +4,8 @@ target_sources(host PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/memory/arena.c ) +target_link_libraries(host PRIVATE common) + target_include_directories(host PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/.. diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt index 1298894..8a5155d 100644 --- a/src/jit/CMakeLists.txt +++ b/src/jit/CMakeLists.txt @@ -1,4 +1,3 @@ - # Define all the files that will be generated set(GEN_OPCODES_H ${CMAKE_CURRENT_SOURCE_DIR}/frontend/decoder/arm32_opcodes.h) set(GEN_DECODER_C ${CMAKE_CURRENT_SOURCE_DIR}/frontend/decoder/arm32_table.c) @@ -25,6 +24,7 @@ target_sources(jit PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/frontend/decoder/arm32.c ${CMAKE_CURRENT_SOURCE_DIR}/frontend/decoder/arm32_table.c ${CMAKE_CURRENT_SOURCE_DIR}/interpreter/arm32/instruction.c + ${CMAKE_CURRENT_SOURCE_DIR}/interpreter/arm32/translator.c ${CMAKE_CURRENT_SOURCE_DIR}/ir/type.c ${CMAKE_CURRENT_SOURCE_DIR}/ir/value.c ${CMAKE_CURRENT_SOURCE_DIR}/ir/opcode.c @@ -40,7 +40,7 @@ set_source_files_properties( set_source_files_properties( ${CMAKE_CURRENT_SOURCE_DIR}/interpreter/arm32/instruction.c - PROPERTIES COMPILE_FLAGS "-Wno-gnu-label-as-value -Wno-unused-label" + PROPERTIES COMPILE_FLAGS "-Wno-gnu-label-as-value -Wno-unused-label -Wno-pedantic" ) target_link_libraries(jit PRIVATE common host) diff --git a/src/jit/interpreter/arm32/instruction.c b/src/jit/interpreter/arm32/instruction.c index a08c1eb..0af1a13 100644 --- a/src/jit/interpreter/arm32/instruction.c +++ b/src/jit/interpreter/arm32/instruction.c @@ -3,11 +3,10 @@ * opcodes. */ -#include "frontend/decoder/arm32_opcodes.h" +#include "instruction.h" #include "common/passert.h" #include #include -#include /* * Computed gotos are a GCC/Clang extension that significantly improves @@ -21,15 +20,10 @@ typedef struct { - uint32_t GPRs[16]; - uint32_t PSTATE; + uint32_t gpr[16]; + uint32_t pstate; } pvm_jit_interpreter_arm32_cpu_state_t; -typedef struct -{ - pvm_jit_decoder_arm32_opcode_t opcode; -} pvm_jit_interpreter_arm32_instruction_t; - void temp (void) { @@ -52,18 +46,16 @@ temp (void) * DISPATCH macro increments IP and jumps to the next handler. */ #define HANDLER(name) name -#define DISPATCH() \ - do \ - { \ - instr++; \ - goto *dispatch_table[instr->opcode]; \ -} while (0) - +#define DISPATCH() \ + do \ + { \ + instr++; \ + goto *dispatch_table[instr->opcode]; \ + } while (0) /* Must perform the initial jump to start the race. */ goto *dispatch_table[instr->opcode]; - /* Include the instruction logic */ #include "handlers.inc" diff --git a/src/jit/interpreter/arm32/instruction.h b/src/jit/interpreter/arm32/instruction.h index 0fbdede..bcf2467 100644 --- a/src/jit/interpreter/arm32/instruction.h +++ b/src/jit/interpreter/arm32/instruction.h @@ -1 +1,30 @@ +#ifndef POUND_JIT_INTERPRETER_INSTRUCTION_H +#define POUND_JIT_INTERPRETER_INSTRUCTION_H +#include "frontend/decoder/arm32_opcodes.h" +#include +#include + +/*! + * @brief Interpreter instruction format. + * @details + * We store the raw instruction alongside the opcode. This allows handlers + * to perform fast bit-shifting to extract operands on demand, avoiding the + * memory overhead of storing every possible operand field in this struct + * which would ruin CPU cache locality. + */ +typedef struct +{ + pvm_jit_decoder_arm32_opcode_t opcode; + uint32_t raw; +} pvm_jit_interpreter_arm32_instruction_t; + +typedef struct +{ + pvm_jit_interpreter_arm32_instruction_t* instructions; + size_t count; +} pvm_jit_interpreter_arm32_block_t; + void temp(void); + +#endif // POUND_JIT_INTERPRETER_INSTRUCTION_H + // h9hj diff --git a/src/jit/interpreter/arm32/translator.c b/src/jit/interpreter/arm32/translator.c index a557a31..b10e60a 100644 --- a/src/jit/interpreter/arm32/translator.c +++ b/src/jit/interpreter/arm32/translator.c @@ -1,3 +1,199 @@ /* * Reads ARM code and produces the an array of interpreter instructions. */ + +#include "translator.h" +#include "common/passert.h" +#include "common/pmath.h" +#include + +#define LOG_MODULE "jit::interpreter" +#include "common/logging.h" + + +pvm_jit_interpreter_arm32_translate_result_t +pvm_jit_interpreter_arm32_translate (pvm_host_memory_arena_t *restrict arena, + pvm_jit_interpreter_arm32_block_t *block, + const uint32_t *restrict guest_code, + const size_t max_instructions) +{ + PVM_ASSERT(NULL != arena); + PVM_ASSERT(NULL != arena->data); + PVM_ASSERT(NULL != block); + PVM_ASSERT(NULL != guest_code); + + LOG_TRACE("Starting translation: %p, Max: %zu", + (const void *)guest_code, + max_instructions); + + block->instructions = NULL; + block->count = 0; + + size_t count_plus_sentinel; + if (true == safe_add_size_t(max_instructions, 1, &count_plus_sentinel)) + { + LOG_ERROR("Integer overflow when calculating instruction count"); + return PVM_JIT_INTERPRETER_ARM32_TRANSLATE_ERROR_OVERFLOW; + } + + size_t allocation_size; + if (true + == safe_multiply_size_t(count_plus_sentinel, + sizeof(pvm_jit_interpreter_arm32_instruction_t), + &allocation_size)) + { + LOG_ERROR("Integer overflow whe calculating allocation byte size"); + return PVM_JIT_INTERPRETER_ARM32_TRANSLATE_ERROR_OVERFLOW; + } + + size_t required_capacity; + if (safe_add_size_t(arena->size, allocation_size, &required_capacity)) + { + LOG_ERROR("Integer overflow when checking arena capacity"); + return PVM_JIT_INTERPRETER_ARM32_TRANSLATE_ERROR_OVERFLOW; + } + + if (required_capacity > arena->capacity) + { + LOG_ERROR( + "JIT Translation OOM. Used: %zu, Capacity: %zu, Required: %zu", + arena->size, + arena->capacity, + allocation_size); + return PVM_JIT_INTERPRETER_ARM32_TRANSLATE_ERROR_OUT_OF_MEMORY; + } + + pvm_jit_interpreter_arm32_instruction_t *const instructions + = pvm_host_memory_arena_allocate(arena, allocation_size); + + if (NULL == instructions) + { + LOG_ERROR("Allocating instructions array failed"); + return PVM_JIT_INTERPRETER_ARM32_TRANSLATE_ERROR_MEMORY_ALLOCATION_FAILED; + } + + size_t instruction_count = 0; + bool stop_block = false; + for (size_t i = 0; i < max_instructions; ++i) + { + const uint32_t raw_instruction = guest_code[i]; + const pvm_jit_decoder_arm32_instruction_info_t *info + = pvm_jit_decoder_arm32_decode(raw_instruction); + PVM_ASSERT(NULL != info); + + pvm_jit_interpreter_arm32_instruction_t *current_emit + = &instructions[instruction_count]; + PVM_ASSERT(NULL != current_emit); + + current_emit->raw = raw_instruction; + + if (NULL != info) + { + current_emit->opcode = info->opcode; + + /* + * Optimization: Stop Translation on Branch. + * This prevents decoding dead code and ensures the interpreter + * updates the Program Counter (PC) correctly before executing the + * next block. + */ + switch (info->opcode) + { + case PVM_A32_OP_B: + case PVM_A32_OP_BL: + case PVM_A32_OP_BX: + case PVM_A32_OP_BLX_IMM: + case PVM_A32_OP_BLX_REG: + case PVM_A32_OP_BXJ: + LOG_TRACE( + "End of block detected: Direct Branch (Opcode: %s) at " + "index %zu", + info->name, + i); + stop_block = true; + break; + + /* ALU operations that write to PC (R15) are also branches */ + case PVM_A32_OP_ADC_IMM: + case PVM_A32_OP_ADC_REG: + case PVM_A32_OP_ADC_RSR: + case PVM_A32_OP_ADD_IMM: + case PVM_A32_OP_ADD_REG: + case PVM_A32_OP_ADD_RSR: + case PVM_A32_OP_AND_IMM: + case PVM_A32_OP_AND_REG: + case PVM_A32_OP_AND_RSR: + case PVM_A32_OP_BIC_IMM: + case PVM_A32_OP_BIC_REG: + case PVM_A32_OP_BIC_RSR: + case PVM_A32_OP_EOR_IMM: + case PVM_A32_OP_EOR_REG: + case PVM_A32_OP_EOR_RSR: + case PVM_A32_OP_MOV_IMM: + case PVM_A32_OP_MOV_REG: + case PVM_A32_OP_MOV_RSR: + case PVM_A32_OP_MVN_IMM: + case PVM_A32_OP_MVN_REG: + case PVM_A32_OP_MVN_RSR: + case PVM_A32_OP_ORR_IMM: + case PVM_A32_OP_ORR_REG: + case PVM_A32_OP_ORR_RSR: + case PVM_A32_OP_RSB_IMM: + case PVM_A32_OP_RSB_REG: + case PVM_A32_OP_RSB_RSR: + case PVM_A32_OP_RSC_IMM: + case PVM_A32_OP_RSC_REG: + case PVM_A32_OP_RSC_RSR: + case PVM_A32_OP_SBC_IMM: + case PVM_A32_OP_SBC_REG: + case PVM_A32_OP_SBC_RSR: + case PVM_A32_OP_SUB_IMM: + case PVM_A32_OP_SUB_REG: + case PVM_A32_OP_SUB_RSR: { + const uint32_t rd = (raw_instruction >> 12U) & 0xFU; + if (15U == rd) + { + LOG_TRACE( + "End of block detected: ALU Branch (Opcode: %s) at " + "index %zu", + info->name, + i); + stop_block = true; + } + break; + } + default: + break; + } + } + else + { + /* Decoding failed. Treat as STOP. */ + LOG_WARNING( + "Decode failed at index %zu: 0x%08X", i, raw_instruction); + current_emit->opcode = PVM_A32_OP_STOP; + stop_block = true; + } + instruction_count++; + + if (true == stop_block) + { + break; + } + } + + /* + * Always append a STOP instruction at the end of the block. + * This guarantees the interpreter loop (computed goto) never runs + * off the end of the buffer into garbage memory. + */ + instructions[instruction_count].opcode = PVM_A32_OP_STOP; + instructions[instruction_count].raw = 0; + instruction_count++; + + block->instructions = instructions; + block->count = instruction_count; + + LOG_TRACE("Translation complete. Generated %zu instructions.", instruction_count); + return PVM_JIT_INTERPRETER_ARM32_TRANSLATE_SUCCESS; +} diff --git a/src/jit/interpreter/arm32/translator.h b/src/jit/interpreter/arm32/translator.h new file mode 100644 index 0000000..f31c158 --- /dev/null +++ b/src/jit/interpreter/arm32/translator.h @@ -0,0 +1,65 @@ +#ifndef POUND_JIT_INTERPRETER_TRANSLATOR_H +#define POUND_JIT_INTERPRETER_TRANSLATOR_H + +#include "instruction.h" +#include "frontend/decoder/arm32.h" +#include "host/memory/arena.h" + +/*! + * @brief Result codes for the ARM32 interpreter translation process. + */ +typedef enum +{ + PVM_JIT_INTERPRETER_ARM32_TRANSLATE_SUCCESS = 0, + PVM_JIT_INTERPRETER_ARM32_TRANSLATE_ERROR_OUT_OF_MEMORY, + PVM_JIT_INTERPRETER_ARM32_TRANSLATE_ERROR_OVERFLOW, + PVM_JIT_INTERPRETER_ARM32_TRANSLATE_ERROR_MEMORY_ALLOCATION_FAILED, +} pvm_jit_interpreter_arm32_translate_result_t; + +/*! + * @brief Translates a sequence of raw ARM32 machine code into an executable interpreter block. + * + * @details + * This function performs a linear sweep of the provided `guest_code`, decoding + * each instruction and generating a corresponding internal interpreter format. + * + * **Translation Termination:** + * The translation process continues until one of the following conditions is met: + * 1. An explicit control flow instruction is encountered (e.g., `B`, `BL`, `BX`). + * 2. An ALU operation modifies the Program Counter (R15) (e.g., `ADD PC, ...`). + * 3. The `max_instructions` limit is reached. + * + * @param[in,out] arena + * The memory arena used to allocate the resulting instruction array. + * Must be initialized and have sufficient remaining capacity. + * + * @param[out] block + * Pointer to the block structure to populate. On `SUCCESS`: + * - `block->instructions` points to valid memory inside `arena`. + * - `block->count` contains the number of instructions plus the sentinel. + * On failure, `block` contents are undefined but safe (pointers set to NULL). + * + * @param[in] guest_code + * Pointer to the buffer containing raw Little Endian 32-bit ARM opcodes. + * Must not be NULL. + * + * @param[in] max_instructions + * The hard limit on the number of instructions to translate. + * + * @return pvm_jit_interpreter_arm32_translate_result_t + * - `SUCCESS`: Translation completed and memory allocated. + * - `ERROR_OUT_OF_MEMORY`: The `arena` capacity was insufficient. + * - `ERROR_OVERFLOW`: Internal size calculations resulted in integer overflow. + * - `ERROR_MEMORY_ALLOCATION_FAILED`: + * Allocated memory for instruction array returned NULL. + * + * @warning The lifetime of the returned `block->instructions` is tied to the + * lifetime of the `arena`. Resetting or freeing the arena invalidates + * the block. + */ +pvm_jit_interpreter_arm32_translate_result_t +pvm_jit_interpreter_arm32_translate (pvm_host_memory_arena_t *restrict arena, + pvm_jit_interpreter_arm32_block_t *block, + const uint32_t *restrict guest_code, + const size_t max_instructions); +#endif // POUND_JIT_INTERPRETER_TRANSLATOR_H diff --git a/src/main.c b/src/main.c index 31e5046..002dd5d 100644 --- a/src/main.c +++ b/src/main.c @@ -1,13 +1,252 @@ #define LOG_MODULE "main" + #include "common/logging.h" #include "common/passert.h" +#include "host/memory/arena.h" #include "jit/frontend/decoder/arm32.h" +#include "jit/interpreter/arm32/translator.h" -int main() +#include +#include +#include +#include + +#define SYSTEM_MEMORY_SIZE (64 * 1024 * 1024) // 64 MB +#define MAX_TRANSLATION_LIMIT 4096 + +#define EXIT_CODE_SUCCESS 0 +#define EXIT_CODE_ERROR_GENERAL 1 +#define EXIT_CODE_ERROR_IO 2 +#define EXIT_CODE_ERROR_MEMORY 3 + +static const char *const DEFAULT_ROM_PATH = "../src/test_long_alu_pc_write.bin"; + +static int load_binary_file(const char *filename, + pvm_host_memory_arena_t *arena, + uint32_t **out_ptr, + size_t *out_size); + +int +main (int argc, char **argv) { - /* Add r0, r0, #1 */ - pvm_jit_decoder_arm32_decode(0xE2800001); - /* Sub r0, r0, #1 */ - pvm_jit_decoder_arm32_decode(0xE2400001); - pvm_jit_decoder_arm32_decode(0x67A757B4); + int exit_code = EXIT_CODE_SUCCESS; + const char *target_file = DEFAULT_ROM_PATH; + + if (argc > 1) + { + target_file = argv[1]; + } + else + { + LOG_INFO("No input file specified. Defaulting to: %s", + DEFAULT_ROM_PATH); + } + + pvm_host_memory_arena_t arena + = pvm_host_memory_arena_init(SYSTEM_MEMORY_SIZE); + if (NULL == arena.data) + { + LOG_FATAL("Failed to initalize system memory arena (%d bytes)", + SYSTEM_MEMORY_SIZE); + exit_code = EXIT_CODE_ERROR_MEMORY; + goto cleanup; + } + + LOG_INFO("System memory initialized: %d MB", + SYSTEM_MEMORY_SIZE / (1024 * 1024)); + + uint32_t *guest_code = NULL; + size_t guest_code_size_bytes = 0; + if (0 + != load_binary_file( + target_file, &arena, &guest_code, &guest_code_size_bytes)) + { + LOG_FATAL("Failed to load guest binary. Aborting."); + exit_code = EXIT_CODE_ERROR_IO; + goto cleanup; + } + + LOG_INFO( + "Loaded payload: %s (%zu bytes)", target_file, guest_code_size_bytes); + + size_t instruction_count = guest_code_size_bytes / sizeof(uint32_t); + if (0 == instruction_count) + { + LOG_WARNING("File is empty or contains no full instructions."); + goto cleanup; + } + + /* + * Cap the translation to the safety limit or the actual size, + * whichever is smaller. + */ + size_t instructions_to_process = (instruction_count < MAX_TRANSLATION_LIMIT) + ? instruction_count + : MAX_TRANSLATION_LIMIT; + + if (instruction_count > MAX_TRANSLATION_LIMIT) + { + LOG_WARNING( + "Input exceeds safety limit. Only translating first %d " + "instructions.", + MAX_TRANSLATION_LIMIT); + } + + /* Execute JIT translation */ + pvm_jit_interpreter_arm32_block_t block = { 0 }; + pvm_jit_interpreter_arm32_translate_result_t result + = pvm_jit_interpreter_arm32_translate( + &arena, &block, guest_code, instructions_to_process); + + if (PVM_JIT_INTERPRETER_ARM32_TRANSLATE_SUCCESS != result) + { + LOG_ERROR("JIT Translation failed with error code: %d", result); + + switch (result) + { + case PVM_JIT_INTERPRETER_ARM32_TRANSLATE_ERROR_OUT_OF_MEMORY: + LOG_ERROR("Cause: Out of Memory"); + break; + case PVM_JIT_INTERPRETER_ARM32_TRANSLATE_ERROR_OVERFLOW: + LOG_ERROR("Cause: Integer Overflow in size calculation"); + break; + case PVM_JIT_INTERPRETER_ARM32_TRANSLATE_ERROR_MEMORY_ALLOCATION_FAILED: + LOG_ERROR("Cause: Internal Arena Allocation Failed"); + break; + default: + LOG_ERROR("Cause: Unknown"); + break; + } + + exit_code = EXIT_CODE_ERROR_GENERAL; + goto cleanup; + } + + LOG_INFO("Translation Successfull"); + LOG_INFO("Generated %zu interpreter micro-ops", block.count); + + if (NULL == block.instructions) + { + LOG_FATAL("Logic Error: Translator returned Success but block instructions are NULL"); + exit_code = EXIT_CODE_ERROR_GENERAL; + goto cleanup; + } + + /* Print translated block */ + for (size_t i = 0; i < block.count; ++i) + { + const pvm_jit_decoder_arm32_instruction_info_t *info = pvm_jit_decoder_arm32_decode(block.instructions[i].raw); + const char* instruction_name = "UNKNOWN"; + if (NULL != info) + { + instruction_name = info->name; + } + + LOG_DEBUG("Op[%03zu]: %s (Raw: 0x%08X)", i, instruction_name, block.instructions[i].raw); + } + +cleanup: + if (NULL != arena.data) + { + LOG_TRACE("Tearing down memory arena..."); + pvm_host_memory_arena_free(&arena); + } + + if (EXIT_CODE_SUCCESS == exit_code) + { + LOG_INFO("Pound terminated successfully."); + } + else + { + LOG_ERROR("Pound terminated with error code: %d", exit_code); + } + return exit_code; +} + +static int +load_binary_file (const char *filename, + pvm_host_memory_arena_t *arena, + uint32_t **out_ptr, + size_t *out_size) +{ + if ((NULL == filename) || (NULL == arena) || (NULL == out_ptr) + || (NULL == out_size)) + { + LOG_FATAL("Invalid arguments passed to load_binary_file"); + return -1; + } + + FILE *file = fopen(filename, "rb"); + if (NULL == file) + { + LOG_ERROR("Failed to open file: %s", filename); + return -1; + } + + long offset = 0; + if (0 != fseek(file, offset, SEEK_END)) + { + LOG_ERROR("fseek failed on file: %s", filename); + goto cleanup_file; + } + + long file_size_signed = ftell(file); + if (file_size_signed < 0) + { + LOG_ERROR("ftell returned invalid size for file: %s", filename); + goto cleanup_file; + } + + rewind(file); + size_t file_size = (size_t)file_size_signed; + if ((file_size % sizeof(uint32_t)) != 0) + { + LOG_WARNING( + "File size (%zu) is not a multiple of 4. Instructions may be " + "truncated.", + file_size); + } + + size_t arena_available_space = arena->capacity - arena->size; + if (file_size > arena_available_space) + { + LOG_FATAL("Insufficient memory in arena. Required %zu, Available: %zu", + file_size, + arena_available_space); + goto cleanup_file; + } + + void *data = pvm_host_memory_arena_allocate(arena, file_size); + if (NULL == data) + { + LOG_FATAL("Arena allocation returned NULL despite capacity check."); + goto cleanup_file; + } + + if (((uintptr_t)data % _Alignof(uint32_t)) != 0) + { + LOG_FATAL( + "Arena allocator returned misaligned memory. Cannot cast to " + "uint32_t* safely.."); + goto cleanup_file; + } + + size_t bytes_read = fread(data, 1, file_size, file); + if (bytes_read != file_size) + { + LOG_ERROR("Incomplete file read. Expected %zu bytes, got %zu.", + file_size, + bytes_read); + goto cleanup_file; + } + + *out_ptr = (uint32_t *)data; + *out_size = file_size; + +cleanup_file: + if (NULL != file) + { + (void)fclose(file); + } + return 0; } diff --git a/src/test_long_alu_loop.bin b/src/test_long_alu_loop.bin new file mode 100644 index 0000000..d3f1eb9 Binary files /dev/null and b/src/test_long_alu_loop.bin differ diff --git a/src/test_long_alu_pc_write.bin b/src/test_long_alu_pc_write.bin new file mode 100644 index 0000000..8184889 Binary files /dev/null and b/src/test_long_alu_pc_write.bin differ