jit/ir: Implement IR instruction management

Introduces IR instruction management, including instruction_t and
instruction_list_t definitions and their implementations. It also added
const-correctness to the value_t API.

Signed-off-by: Ronald Caesar <github43132@proton.me>
This commit is contained in:
Ronald Caesar 2025-11-22 22:12:22 -04:00
parent ffea35bc8f
commit bd0bf1deba
No known key found for this signature in database
GPG key ID: 04307C401999C596
8 changed files with 375 additions and 28 deletions

View file

@ -111,7 +111,7 @@ add_subdirectory(src/targets/switch1/hardware)
include(TestBigEndian)
TEST_BIG_ENDIAN(WORDS_BIGENDIAN)
list(APPEND POUND_PROJECT_TARGETS common host pvm)
list(APPEND POUND_PROJECT_TARGETS common host pvm jit)
foreach(TARGET ${POUND_PROJECT_TARGETS})
# Apply Endianness definitions to all our targets.
if(WORDS_BIGENDIAN)

View file

@ -5,6 +5,7 @@ target_sources(jit PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/ir/type.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ir/value.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ir/opcode.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ir/instruction.cpp
)
target_link_libraries(jit PRIVATE common host)

163
src/jit/ir/instruction.cpp Normal file
View file

@ -0,0 +1,163 @@
#include "instruction.h"
#include "common/passert.h"
#include <stddef.h>
#define LOG_MODULE "jit"
#include "common/logging.h"
namespace pound::jit::ir {
const value_t*
instruction_get_arg (const instruction_t *instruction, const size_t arg_index)
{
PVM_ASSERT(nullptr != instruction);
PVM_ASSERT(arg_index < MAX_IR_ARGS);
const value_t *arg = &instruction->args[arg_index];
PVM_ASSERT(nullptr != arg);
return arg;
}
const uint64_t
instruction_get_arg_u64(const instruction_t *instruction, const size_t arg_index)
{
PVM_ASSERT(nullptr != instruction);
PVM_ASSERT(arg_index < MAX_IR_ARGS);
const value_t* arg = instruction_get_arg(instruction, arg_index);
PVM_ASSERT(nullptr != arg);
PVM_ASSERT(IR_TYPE_U64 == arg->type);
const uint64_t value = value_get_u64(arg);
return value;
}
const uint32_t
instruction_get_arg_u32(const instruction_t *instruction, const size_t arg_index)
{
PVM_ASSERT(nullptr != instruction);
PVM_ASSERT(arg_index < MAX_IR_ARGS);
const value_t* arg = instruction_get_arg(instruction, arg_index);
PVM_ASSERT(nullptr != arg);
PVM_ASSERT(IR_TYPE_U32 == arg->type);
const uint32_t value = value_get_u32(arg);
return value;
}
const uint8_t
instruction_get_arg_u8(const instruction_t *instruction, const size_t arg_index)
{
PVM_ASSERT(nullptr != instruction);
PVM_ASSERT(arg_index < MAX_IR_ARGS);
const value_t* arg = instruction_get_arg(instruction, arg_index);
PVM_ASSERT(nullptr != arg);
PVM_ASSERT(IR_TYPE_U8 == arg->type);
const uint8_t value = value_get_u8(arg);
return value;
}
const bool
instruction_get_arg_u1(const instruction_t *instruction, const size_t arg_index)
{
PVM_ASSERT(nullptr != instruction);
PVM_ASSERT(arg_index < MAX_IR_ARGS);
const value_t* arg = instruction_get_arg(instruction, arg_index);
PVM_ASSERT(nullptr != arg);
PVM_ASSERT(IR_TYPE_U1 == arg->type);
const uint8_t value = value_get_u1(arg);
return value;
}
const pound::jit::a32_register_t
instruction_get_arg_a32_register(const instruction_t *instruction, const size_t arg_index)
{
PVM_ASSERT(nullptr != instruction);
PVM_ASSERT(arg_index < MAX_IR_ARGS);
const value_t* arg = instruction_get_arg(instruction, arg_index);
PVM_ASSERT(nullptr != arg);
PVM_ASSERT(IR_TYPE_A32_REGISTER == arg->type);
const pound::jit::a32_register_t value = value_get_a32_register(arg);
return value;
}
const type_t
instruction_get_return_type (const instruction_t *instruction)
{
PVM_ASSERT(nullptr != instruction);
PVM_ASSERT(instruction->opcode < NUM_OPCODE);
const decoded_opcode_t *decoded_opcode = &g_opcodes[instruction->opcode];
PVM_ASSERT(nullptr != decoded_opcode);
return decoded_opcode->type;
}
const char*
instruction_get_opcode_name(const instruction_t *instruction)
{
PVM_ASSERT(nullptr != instruction);
const decoded_opcode_t *decoded_opcode = &g_opcodes[instruction->opcode];
PVM_ASSERT(nullptr != decoded_opcode);
const char *name = decoded_opcode->name;
PVM_ASSERT(nullptr != name);
return name;
}
void
instruction_list_append (instruction_list_t *list, instruction_t *instruction)
{
PVM_ASSERT(nullptr != list);
PVM_ASSERT(nullptr != instruction);
instruction->next = nullptr;
instruction->previous = list->tail;
if (nullptr != list->tail)
{
list->tail->next = instruction;
}
else
{
list->head = instruction;
}
list->tail = instruction;
}
void
instruction_list_remove (instruction_list_t *list, instruction_t *instruction)
{
PVM_ASSERT(nullptr != list);
PVM_ASSERT(nullptr != instruction);
if (nullptr != instruction->previous)
{
instruction->previous->next = instruction->next;
}
else
{
list->head = instruction->next;
}
if (nullptr != instruction->next)
{
instruction->next->previous = instruction->previous;
}
else
{
list->tail = instruction->previous;
}
instruction->next = nullptr;
instruction->previous = nullptr;
}
}

175
src/jit/ir/instruction.h Normal file
View file

@ -0,0 +1,175 @@
#ifndef POUND_JIT_IR_INSTRUCTION_H
#define POUND_JIT_IR_INSTRUCTION_H
#include "opcode.h"
#include "value.h"
#include <stddef.h>
namespace pound::jit::ir {
// Maximum number of arguments an IR instruction can have.
#define MAX_IR_ARGS 4
/*!
* Represents a single instruction in the IR layer.
*
* Each instruction node encapsulates an opcode, its arguments, and pointers to
* form an intrusive double-linked list.
*/
typedef struct instruction_t
{
// The opcode for this instruction.
opcode_t opcode;
// An array of arguments for this instruction.
value_t args[MAX_IR_ARGS];
// Pointer to the next instruction in the intrusive list.
struct instruction_t *next;
// Pointer to the previous instruction the intrusive list.
struct instruction_t *previous;
} instruction_t;
/*!
* @brief Represents a double-linked list of IR instructions.
*
* This structure holds the head and tail pointers of an intrusive list
* composed of `instruction_t` nodes. It is used to store sequences
*/
typedef struct
{
// Pointer to the first instruction in the list.
instruction_t *head;
// Pointer to the last instruction in the list.
instruction_t *tail;
} instruction_list_t;
/*!
* @brief Gets a pointer to the argument at a specific index.
*
* @param instruction Pointer to the IR instruction.
* @param arg_index The index of the argument to retrieve.
*
* @return A constant pointer to the argument at the specified index.
* @pre `instruction` must not be NULL
* @pre `arg_index` must be less than `MAX_IR_ARGS`.
*/
const value_t* instruction_get_arg (const instruction_t *instruction, const size_t arg_index);
/*!
* Retrieves a U64 argument from an instruction.
*
* @param instruction Pointer to the IR instruction.
* @apram arg_index The index of the argument to retrieve.
*
* @return The U64 value of the argument.
* @pre `instruction` must not be NULL.
* @pre `arg_index` must be less than `MAX_IR_ARGS`.
* @pre The argument at `arg_index` must be of type `IR_TYPE_U64`.
*/
const uint64_t instruction_get_arg_u64(const instruction_t *instruction, const size_t arg_index);
/*!
* @brief Retrieves a U32 argument from an instruction.
*
* @param instruction Pointer to the IR instruction.
* @param arg_index The index of the argument to retrieve.
*
* @return The U32 value of the argument.
* @pre `instruction` must not be NULL.
* @pre `arg_index` must be less than `MAX_IR_ARGS`.
* @pre The argument at `arg_index` must be of type `IR_TYPE_U32`.
*/
const uint32_t instruction_get_arg_u32(const instruction_t *instruction, const size_t arg_index);
/*!
* Retrives a U8 argument from an instruction.
*
* @param instruction Pointer to the IR instruction.
* @param arg_index The index of the argument to retrieve.
*
* @return The U8 value of the argument.
* @pre `instruction` must not be NULL.
* @pre `arg_index` must be less than `MAX_IR_ARGS`.
* @pre The argument at `arg_index` must be of type `IR_TYPE_U8`.
*/
const uint8_t instruction_get_arg_u8(const instruction_t *instruction, const size_t arg_index);
/*!
* @brief Retrieves a U1 (boolean) argument from an instruction.
*
* @param instruction Pointer to the IR instruction.
* @param arg_index The index of the argument to retrieve.
*
* @return The boolean value of the argument.
* @pre `instruction` must not be NULL.
* @pre `arg_index` must be less than `MAX_IR_ARGS`.
* @pre The argument at `arg_index` must be of type `IR_TYPE_U1`.
*/
const bool instruction_get_arg_u1(const instruction_t *instruction, const size_t arg_index);
/*!
* @brief Retrieves an A32 register identifier argument from an instruction.
*
* @param instruction Pointer to the IR instruction.
* @param arg_index The index of the argument to retrieve.
*
* @return The `a32_register_t` identifier.
* @pre `instruction` must not be NULL.
* @pre `arg_index` must be less than `MAX_IR_ARGS`.
* @pre The argument at `arg_index` must be of type `IR_TYPE_A32_REGISTER`.
*/
const pound::jit::a32_register_t instruction_get_arg_a32_register(const instruction_t *instruction, const size_t arg_index);
/*!
* @brief Gets the return type of an instruction based on its opcode.
*
* @param instruction Pointer to the IR instruction.
*
* @return The `type_t` that this instruction's opcode returns.
* @pre `instruction` must not be NULL.
* @pre `instruction->opcode` must be a valid opcode index (less than `NUM_OPCODE`).
*/
const type_t instruction_get_return_type (const instruction_t *instruction);
/*!
* @brief Gets the name of an instruction's opcode as a C-string.
*
* @param instruction Pointer to the IR instruction.
*
* @return A constant C-string containing the opcode's name.
* @pre `instruction` must not be NULL.
* @pre `instruction->opcode` must be a valid opcode index (less than `NUM_OPCODE`).
* @pre The global `g_opcodes` array must be initialized and accessible.
*/
const char* instruction_get_opcode_name(const instruction_t *instruction);
/*!
* @brief Appends an instruction to the tail of an instruction list.
*
* The instruction is added to the end of the list. If the list is empty,
* the instruction becomes both the head and the tail.
*
* @param list Pointer to the instruction list to modify.
* @param instruction Pointer to the `instruction_t` node to append.
*
* @pre `list` must not be NULL.
* @pre `instruction` must not be NULL.
*/
void instruction_list_append (instruction_list_t *list, instruction_t *instruction);
/*!
* @brief Removes an instruction from an instruction list.
*
* @param list Pointer to the instruction list to modify.
* @param instruction Pointer to the `instruction_t` node to remove.
*
* @pre `list` must not be NULL.
* @pre `instruction` must not be NULL.
* @pre `instruction` must be a member of `list`.
*/
void instruction_list_remove (instruction_list_t *list, instruction_t *instruction);
}
#endif // POUND_JIT_IR_INSTRUCTION_H

View file

@ -7,6 +7,8 @@
* by including "opcode.inc", which is processed using X-macros.
*/
#ifndef POUMD_JIT_IR_OPCODE_H
#define POUMD_JIT_IR_OPCODE_H
#include "type.h"
namespace pound::jit::ir {
@ -56,3 +58,4 @@ extern decoded_opcode_t g_opcodes[NUM_OPCODE];
void opcode_init(void);
}
#endif // POUMD_JIT_IR_OPCODE_H

View file

@ -7,7 +7,8 @@
* This header declares the `type_t ` enumeration, which forms the basis of
* type identification and checking within the JIT's IR.
*/
#ifndef POUND_JIT_IR_TYPE_H
#define POUND_JIT_IR_TYPE_H
namespace pound::jit::ir {
/*!
@ -60,3 +61,4 @@ typedef enum
*/
bool are_types_compatible(const type_t t1, const type_t t2);
} // namespace pound::jit::ir
#endif // POUND_JIT_IR_TYPE_H

View file

@ -17,7 +17,7 @@ value_init (value_t *p_value)
}
void
value_init_from_u64 (value_t *p_value, uint64_t u64)
value_init_from_u64 (value_t *p_value, const uint64_t u64)
{
PVM_ASSERT(nullptr != p_value);
p_value->type = IR_TYPE_U64;
@ -25,7 +25,7 @@ value_init_from_u64 (value_t *p_value, uint64_t u64)
}
void
value_init_from_u32 (value_t *p_value, uint32_t u32)
value_init_from_u32 (value_t *p_value, const uint32_t u32)
{
PVM_ASSERT(nullptr != p_value);
p_value->type = IR_TYPE_U32;
@ -33,7 +33,7 @@ value_init_from_u32 (value_t *p_value, uint32_t u32)
}
void
value_init_from_u8 (value_t *p_value, uint8_t u8)
value_init_from_u8 (value_t *p_value, const uint8_t u8)
{
PVM_ASSERT(nullptr != p_value);
p_value->type = IR_TYPE_U8;
@ -41,7 +41,7 @@ value_init_from_u8 (value_t *p_value, uint8_t u8)
}
void
value_init_from_u1 (value_t *p_value, bool u1)
value_init_from_u1 (value_t *p_value, const bool u1)
{
PVM_ASSERT(nullptr != p_value);
p_value->type = IR_TYPE_U1;
@ -49,7 +49,7 @@ value_init_from_u1 (value_t *p_value, bool u1)
}
void
value_init_from_a32_register (value_t *p_value, a32_register_t reg)
value_init_from_a32_register (value_t *p_value, const a32_register_t reg)
{
PVM_ASSERT(nullptr != p_value);
p_value->type = IR_TYPE_A32_REGISTER;
@ -62,36 +62,36 @@ value_init_from_a32_register (value_t *p_value, a32_register_t reg)
* ============================================================================
*/
uint64_t
value_get_u64 (value_t *p_value)
const uint64_t
value_get_u64 (const value_t *p_value)
{
PVM_ASSERT(IR_TYPE_U64 == p_value->type);
return p_value->inner.immediate_u64;
}
uint32_t
value_get_u32 (value_t *p_value)
const uint32_t
value_get_u32 (const value_t *p_value)
{
PVM_ASSERT(IR_TYPE_U32 == p_value->type);
return p_value->inner.immediate_u32;
}
uint8_t
value_get_u8 (value_t *p_value)
const uint8_t
value_get_u8 (const value_t *p_value)
{
PVM_ASSERT(IR_TYPE_U8 == p_value->type);
return p_value->inner.immediate_u8;
}
bool
value_get_u1 (value_t *p_value)
const bool
value_get_u1 (const value_t *p_value)
{
PVM_ASSERT(IR_TYPE_U1 == p_value->type);
return p_value->inner.immediate_u1;
}
pound::jit::a32_register_t
value_get_a32_register (value_t *p_value)
const pound::jit::a32_register_t
value_get_a32_register (const value_t *p_value)
{
PVM_ASSERT(IR_TYPE_A32_REGISTER == p_value->type);
return p_value->inner.immediate_a32_register;

View file

@ -10,6 +10,8 @@
* approach to store different types of data safely.
*/
#ifndef POUND_JIT_IR_VALUE_H
#define POUND_JIT_IR_VALUE_H
#include <stdint.h>
#include "type.h"
#include "jit/a32_types.h"
@ -58,7 +60,7 @@ void value_init (value_t *p_value);
* @post The `p_value`'s `type` will be set to `IR_TYPE_U64` and its
* `inner.immediate_u64` member will contain `u64`.
*/
void value_init_from_u64 (value_t *p_value, uint64_t u64);
void value_init_from_u64 (value_t *p_value, const uint64_t u64);
/*!
* @brief Initializes a `value_t` instance to hold an unsigned 32-bit immediate value.
@ -68,7 +70,7 @@ void value_init_from_u64 (value_t *p_value, uint64_t u64);
* @post The `p_value`'s `type` will be set to `IR_TYPE_U32` and its
* `inner.immediate_u32` member will contain `u32`.
*/
void value_init_from_u32 (value_t *p_value, uint32_t u32);
void value_init_from_u32 (value_t *p_value, const uint32_t u32);
/*!
* @brief Initializes a `value_t` instance to hold an unsigned 8-bit immediate value.
@ -78,7 +80,7 @@ void value_init_from_u32 (value_t *p_value, uint32_t u32);
* @post The `p_value`'s `type` will be set to `IR_TYPE_U8` and its
* `inner.immediate_u8` member will contain `u8`.
*/
void value_init_from_u8 (value_t *p_value, uint8_t u8);
void value_init_from_u8 (value_t *p_value, const uint8_t u8);
/*!
* @brief Initializes a `value_t` instance to hold a 1-bit boolean immediate value.
@ -88,7 +90,7 @@ void value_init_from_u8 (value_t *p_value, uint8_t u8);
* @post The `p_value`'s `type` will be set to `IR_TYPE_U1` and its
* `inner.immediate_u1` member will contain `u1`.
*/
void value_init_from_u1 (value_t *p_value, bool u1);
void value_init_from_u1 (value_t *p_value, const bool u1);
/*!
* @brief Initializes a `value_t` instance to hold an A32 register identifier.
@ -101,7 +103,7 @@ void value_init_from_u1 (value_t *p_value, bool u1);
* @post The `p_value`'s `type` will be set to `IR_TYPE_A32_REGISTER` and its
* `inner.immediate_a32_register` member will contain `reg`.
*/
void value_init_from_a32_register (value_t *p_value, a32_register_t reg);
void value_init_from_a32_register (value_t *p_value, const a32_register_t reg);
/*!
* @brief Retrieves an unsigned 64-bit immediate value from a `value_t`.
@ -112,7 +114,7 @@ void value_init_from_a32_register (value_t *p_value, a32_register_t reg);
* @warning Calling this function on a `value_t` not of type `IR_TYPE_U64`
* results in undefined behavior.
*/
uint64_t value_get_u64 (value_t *p_value);
const uint64_t value_get_u64 (const value_t *p_value);
/*!
@ -124,7 +126,7 @@ uint64_t value_get_u64 (value_t *p_value);
* @warning Calling this function on a `value_t` not of type `IR_TYPE_U32`
* results in undefined behavior.
*/
uint32_t value_get_u32 (value_t *p_value);
const uint32_t value_get_u32 (const value_t *p_value);
/*!
* @brief Retrieves an unsigned 8-bit immediate value from a `value_t`.
@ -135,7 +137,7 @@ uint32_t value_get_u32 (value_t *p_value);
* @warning Calling this function on a `value_t` not of type `IR_TYPE_U8`
* results in undefined behavior.
*/
uint8_t value_get_u8 (value_t *p_value);
const uint8_t value_get_u8 (const value_t *p_value);
/**
* @brief Retrieves an unsigned 1-bit immediate value from a `value_t`.
@ -146,7 +148,7 @@ uint8_t value_get_u8 (value_t *p_value);
* @warning Calling this function on a `value_t` not of type `IR_TYPE_U1`
* results in undefined behavior.
*/
bool value_get_u1 (value_t *p_value);
const bool value_get_u1 (const value_t *p_value);
/**
* @brief Retrieves an A32 register identifier from a `value_t`.
@ -157,5 +159,6 @@ bool value_get_u1 (value_t *p_value);
* @warning Calling this function on a `value_t` not of type `IR_TYPE_A32_REGISTER`
* results in undefined behavior.
*/
pound::jit::a32_register_t value_get_a32_register (value_t *p_value);
const pound::jit::a32_register_t value_get_a32_register (const value_t *p_value);
} // namespace pound:::jit::ir
#endif // POUND_JIT_IR_TYPE_H