Compare commits

...

2 commits

Author SHA1 Message Date
Ronald Caesar
b138abb540
jit/interpreter: Implement translator and hardened entrypoint
Introduces the translation stage fod the ARM32 interpreter and
significantly hardens the application's core logic to meet my high
integrity standards.

- translator.c converts raw ARM32 guest code into an array of
  interpreter op-codes
- main.c was completly rewritten to be as safe as possible.
- pmath.h provides safe, checked arithmetic functions to prevent integer
  overflow.
- adds binary files in src/ for testing the interpreter.

Signed-off-by: Ronald Caesar <github43132@proton.me>
2025-12-06 19:48:58 -04:00
Ronald Caesar
b1d9a64866
common/logging: Remove timestamp from logs
Timestamp clutters the terminal and doesnt provide any tangible
benefits.

Signed-off-by: Ronald Caesar <github43132@proton.me>
2025-12-06 18:28:30 -04:00
12 changed files with 621 additions and 76 deletions

View file

@ -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.

View file

@ -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;
}

67
src/common/pmath.h Normal file
View file

@ -0,0 +1,67 @@
#ifndef POUND_COMMON_MATH_H
#define POUND_COMMON_MATH_H
#include <stddef.h>
#include <stdbool.h>
/*!
* @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

View file

@ -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}/..

View file

@ -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)

View file

@ -3,11 +3,10 @@
* opcodes.
*/
#include "frontend/decoder/arm32_opcodes.h"
#include "instruction.h"
#include "common/passert.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
/*
* 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"

View file

@ -1 +1,30 @@
#ifndef POUND_JIT_INTERPRETER_INSTRUCTION_H
#define POUND_JIT_INTERPRETER_INSTRUCTION_H
#include "frontend/decoder/arm32_opcodes.h"
#include <stdint.h>
#include <stddef.h>
/*!
* @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

View file

@ -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 <stdio.h>
#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;
}

View file

@ -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

View file

@ -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 <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#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;
}

BIN
src/test_long_alu_loop.bin Normal file

Binary file not shown.

Binary file not shown.