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) include(TestBigEndian)
TEST_BIG_ENDIAN(WORDS_BIGENDIAN) 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}) foreach(TARGET ${POUND_PROJECT_TARGETS})
# Apply Endianness definitions to all our targets. # Apply Endianness definitions to all our targets.
if(WORDS_BIGENDIAN) 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/type.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ir/value.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ir/value.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ir/opcode.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ir/opcode.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ir/instruction.cpp
) )
target_link_libraries(jit PRIVATE common host) 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. * 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" #include "type.h"
namespace pound::jit::ir { namespace pound::jit::ir {
@ -56,3 +58,4 @@ extern decoded_opcode_t g_opcodes[NUM_OPCODE];
void opcode_init(void); 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 * This header declares the `type_t ` enumeration, which forms the basis of
* type identification and checking within the JIT's IR. * 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 { namespace pound::jit::ir {
/*! /*!
@ -60,3 +61,4 @@ typedef enum
*/ */
bool are_types_compatible(const type_t t1, const type_t t2); bool are_types_compatible(const type_t t1, const type_t t2);
} // namespace pound::jit::ir } // namespace pound::jit::ir
#endif // POUND_JIT_IR_TYPE_H

View file

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

View file

@ -10,6 +10,8 @@
* approach to store different types of data safely. * approach to store different types of data safely.
*/ */
#ifndef POUND_JIT_IR_VALUE_H
#define POUND_JIT_IR_VALUE_H
#include <stdint.h> #include <stdint.h>
#include "type.h" #include "type.h"
#include "jit/a32_types.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 * @post The `p_value`'s `type` will be set to `IR_TYPE_U64` and its
* `inner.immediate_u64` member will contain `u64`. * `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. * @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 * @post The `p_value`'s `type` will be set to `IR_TYPE_U32` and its
* `inner.immediate_u32` member will contain `u32`. * `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. * @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 * @post The `p_value`'s `type` will be set to `IR_TYPE_U8` and its
* `inner.immediate_u8` member will contain `u8`. * `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. * @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 * @post The `p_value`'s `type` will be set to `IR_TYPE_U1` and its
* `inner.immediate_u1` member will contain `u1`. * `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. * @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 * @post The `p_value`'s `type` will be set to `IR_TYPE_A32_REGISTER` and its
* `inner.immediate_a32_register` member will contain `reg`. * `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`. * @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` * @warning Calling this function on a `value_t` not of type `IR_TYPE_U64`
* results in undefined behavior. * 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` * @warning Calling this function on a `value_t` not of type `IR_TYPE_U32`
* results in undefined behavior. * 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`. * @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` * @warning Calling this function on a `value_t` not of type `IR_TYPE_U8`
* results in undefined behavior. * 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`. * @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` * @warning Calling this function on a `value_t` not of type `IR_TYPE_U1`
* results in undefined behavior. * 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`. * @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` * @warning Calling this function on a `value_t` not of type `IR_TYPE_A32_REGISTER`
* results in undefined behavior. * 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 } // namespace pound:::jit::ir
#endif // POUND_JIT_IR_TYPE_H