mirror of
https://github.com/pound-emu/pound.git
synced 2025-12-11 07:36:57 +00:00
Compare commits
2 commits
0d957968df
...
b138abb540
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b138abb540 | ||
|
|
b1d9a64866 |
12 changed files with 621 additions and 76 deletions
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
67
src/common/pmath.h
Normal 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
|
||||
|
|
@ -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}/..
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
65
src/jit/interpreter/arm32/translator.h
Normal file
65
src/jit/interpreter/arm32/translator.h
Normal 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
|
||||
251
src/main.c
251
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 <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
BIN
src/test_long_alu_loop.bin
Normal file
Binary file not shown.
BIN
src/test_long_alu_pc_write.bin
Normal file
BIN
src/test_long_alu_pc_write.bin
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue