Compare commits

...

3 commits

Author SHA1 Message Date
Ronald Caesar
304aeed9f6
tests/jit: Add IR tests
Signed-off-by: Ronald Caesar <github43132@proton.me>
2025-11-08 16:37:41 -04:00
Ronald Caesar
a57ce183a1
extern/googletest: Add testing library
Need a testing framework to test the IR layer. GoogleTest is a popular
choice so it was chosen.

Signed-off-by: Ronald Caesar <github43132@proton.me>
2025-11-08 15:41:04 -04:00
Ronald Caesar
d832bb1151
jit/ir: Define value_t interface
Introduces jit/ir/value.h, which defines the value_t structure which is
a polymorphic container designed to hold various kinds of data that IR
instructions operate on or produce.

Signed-off-by: Ronald Caesar <github43132@proton.me>
2025-11-08 14:45:13 -04:00
8 changed files with 373 additions and 53 deletions

3
.gitmodules vendored
View file

@ -4,3 +4,6 @@
[submodule "3rd_Party/imgui"]
path = 3rd_Party/imgui
url = https://github.com/ocornut/imgui.git
[submodule "3rd_Party/googletest"]
path = 3rd_Party/googletest
url = https://github.com/google/googletest.git

View file

@ -29,3 +29,7 @@ target_include_directories(imgui PUBLIC
imgui
imgui/backends
)
# GoogleTest
add_subdirectory(googletest)
enable_testing()

View file

@ -32,3 +32,13 @@ This document tracks all pinned third-party submodules in the Pound project. Eac
- **Last Review**: 2025-09-20
- **Next Review**: 2026-03-20
### GoogleTest
- **Repository**: https://github.com/google/googletest
- **Version Tag**: v1.17.0
- **Commit Hash**: 52eb8108c5bdec04579160ae17225d66034bd723
- **License**: BSD-3-Clause
- **Purpose**: Provides the testing and mocking framework for Pound.
- **Pinning Date**: 2025-11-09
- **Pinning Reason**: Dependency added for the first time.
- **Last Review**: 2025-11-09
- **Next Review**: 2026-05-09

1
3rd_Party/googletest vendored Submodule

@ -0,0 +1 @@
Subproject commit 52eb8108c5bdec04579160ae17225d66034bd723

View file

@ -80,6 +80,8 @@ foreach(line ${submodule_lines})
verify_pinned_commit("imgui" "${submodule_path}" "${commit_hash}" "bf75bfec48fc00f532af8926130b70c0e26eb099")
elseif(submodule_path STREQUAL "3rd_Party/SDL3")
verify_pinned_commit("SDL3" "${submodule_path}" "${commit_hash}" "a96677bdf6b4acb84af4ec294e5f60a4e8cbbe03")
elseif(submodule_path STREQUAL "3rd_Party/googletest")
verify_pinned_commit("SDL3" "${submodule_path}" "${commit_hash}" "52eb8108c5bdec04579160ae17225d66034bd723")
endif()
message(STATUS "Verified submodule: ${submodule_path} (${commit_hash})")
@ -156,3 +158,19 @@ target_link_libraries(Pound PRIVATE
SDL3::SDL3
imgui
)
set(TEST_SRC
${CMAKE_CURRENT_SOURCE_DIR}/tests/jit/ir/test_value.cpp
)
add_executable(tests ${TEST_SRC})
target_link_libraries(tests PRIVATE
jit
gtest
gtest_main
)
target_include_directories(jit PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/..
)

View file

@ -1,22 +1,8 @@
#include "type.h"
#include "jit/a32_types.h"
#include "value.h"
#include "common/passert.h"
#include <stdint.h>
namespace pound::jit::ir {
typedef struct
{
type_t type;
union
{
uint64_t immediate_u64;
uint32_t immediate_u32;
pound::jit::a32_register_t immediate_a32_register;
uint8_t immediate_u8;
bool immediate_u1;
} inner;
} value_t;
/*
* ============================================================================
* Init Functions
@ -24,50 +10,50 @@ typedef struct
*/
void
value_init (value_t *value)
value_init (value_t *p_value)
{
PVM_ASSERT(nullptr != value);
value->type = IR_TYPE_VOID;
PVM_ASSERT(nullptr != p_value);
p_value->type = IR_TYPE_VOID;
}
void
value_init_from_u64 (value_t *value, uint64_t u64)
value_init_from_u64 (value_t *p_value, uint64_t u64)
{
PVM_ASSERT(nullptr != value);
value->type = IR_TYPE_U64;
value->inner.immediate_u64 = u64;
PVM_ASSERT(nullptr != p_value);
p_value->type = IR_TYPE_U64;
p_value->inner.immediate_u64 = u64;
}
void
value_init_from_u32 (value_t *value, uint32_t u32)
value_init_from_u32 (value_t *p_value, uint32_t u32)
{
PVM_ASSERT(nullptr != value);
value->type = IR_TYPE_U32;
value->inner.immediate_u32 = u32;
PVM_ASSERT(nullptr != p_value);
p_value->type = IR_TYPE_U32;
p_value->inner.immediate_u32 = u32;
}
void
value_init_from_u8 (value_t *value, uint8_t u8)
value_init_from_u8 (value_t *p_value, uint8_t u8)
{
PVM_ASSERT(nullptr != value);
value->type = IR_TYPE_U8;
value->inner.immediate_u8 = u8;
PVM_ASSERT(nullptr != p_value);
p_value->type = IR_TYPE_U8;
p_value->inner.immediate_u8 = u8;
}
void
value_init_from_u1 (value_t *value, bool u1)
value_init_from_u1 (value_t *p_value, bool u1)
{
PVM_ASSERT(nullptr != value);
value->type = IR_TYPE_U1;
value->inner.immediate_u1 = u1;
PVM_ASSERT(nullptr != p_value);
p_value->type = IR_TYPE_U1;
p_value->inner.immediate_u1 = u1;
}
void
value_init_from_a32_register (value_t *value, a32_register_t reg)
value_init_from_a32_register (value_t *p_value, a32_register_t reg)
{
PVM_ASSERT(nullptr != value);
value->type = IR_TYPE_A32_REGISTER;
value->inner.immediate_a32_register = reg;
PVM_ASSERT(nullptr != p_value);
p_value->type = IR_TYPE_A32_REGISTER;
p_value->inner.immediate_a32_register = reg;
}
/*
@ -77,37 +63,37 @@ value_init_from_a32_register (value_t *value, a32_register_t reg)
*/
uint64_t
value_get_u64 (value_t *value)
value_get_u64 (value_t *p_value)
{
PVM_ASSERT(IR_TYPE_U64 == value->type);
return value->inner.immediate_u64;
PVM_ASSERT(IR_TYPE_U64 == p_value->type);
return p_value->inner.immediate_u64;
}
uint32_t
value_get_u32 (value_t *value)
value_get_u32 (value_t *p_value)
{
PVM_ASSERT(IR_TYPE_U32 == value->type);
return value->inner.immediate_u32;
PVM_ASSERT(IR_TYPE_U32 == p_value->type);
return p_value->inner.immediate_u32;
}
uint8_t
value_get_u8 (value_t *value)
value_get_u8 (value_t *p_value)
{
PVM_ASSERT(IR_TYPE_U8 == value->type);
return value->inner.immediate_u8;
PVM_ASSERT(IR_TYPE_U8 == p_value->type);
return p_value->inner.immediate_u8;
}
bool
value_get_u1 (value_t *value)
value_get_u1 (value_t *p_value)
{
PVM_ASSERT(IR_TYPE_U1 == value->type);
return value->inner.immediate_u1;
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 *value)
value_get_a32_register (value_t *p_value)
{
PVM_ASSERT(IR_TYPE_A32_REGISTER == value->type);
return value->inner.immediate_a32_register;
PVM_ASSERT(IR_TYPE_A32_REGISTER == p_value->type);
return p_value->inner.immediate_a32_register;
}
}

161
src/jit/ir/value.h Normal file
View file

@ -0,0 +1,161 @@
/**
* @file value.h
*
* @brief Defines the `value_t` structure and associated manipulation functions
* for the Pound JIT Intermediate Representation (IR).
*
* The `value_t` structure is designed to be a lightweight object
* that can be efficiently created, copied, and passed around
* during IR construction and optimization phases. It uses a tagged union
* approach to store different types of data safely.
*/
#include <stdint.h>
#include "type.h"
#include "jit/a32_types.h"
namespace pound::jit::ir {
/*!
* @brief A polymorphic container for values in the JIT's Intermediate Representation.
*
* The `value_t` struct represents a single value within the IR. It can hold
* various types of data, identified by the `type` member. The actual data
* is stored within the `inner` union.
*
* The `type` member must always correspond to the actual type of data
* stored in the `inner` union. Using a union member that does not match
* the `type` tag leads to undefined behavior.
*/
typedef struct
{
type_t type;
union
{
uint64_t immediate_u64;
uint32_t immediate_u32;
pound::jit::a32_register_t immediate_a32_register;
uint8_t immediate_u8;
bool immediate_u1;
} inner;
} value_t;
/*!
* @brief Initializes a `value_t` instance to a default/void state.
*
* @param p_value Pointer to the `value_t` instance to initialize.
* @post The `p_value`'s `type` will be set to `IR_TYPE_VOID`.
* The contents of its `inner` union are considered undefined
* for a void value.
*/
void value_init (value_t *p_value);
/*!
* @brief Initializes a `value_t` instance to hold an unsigned 64-bit immediate value.
*
* @param p_value Pointer to the `value_t` instance to initialize.
* @param u64 The 64-bit unsigned immediate value to store.
* @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);
/*!
* @brief Initializes a `value_t` instance to hold an unsigned 32-bit immediate value.
*
* @param p_value Pointer to the `value_t` instance to initialize.
* @param u32 The 32-bit unsigned immediate value to store.
* @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);
/*!
* @brief Initializes a `value_t` instance to hold an unsigned 8-bit immediate value.
*
* @param p_value Pointer to the `value_t` instance to initialize.
* @param u8 The 8-bit unsigned immediate value to store.
* @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);
/*!
* @brief Initializes a `value_t` instance to hold a 1-bit boolean immediate value.
*
* @param p_value Pointer to the `value_t` instance to initialize.
* @param u1 The boolean (1-bit) immediate value to store.
* @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);
/*!
* @brief Initializes a `value_t` instance to hold an A32 register identifier.
*
* This function stores the *identity* of an A32 register (e.g., R0, SP, PC)
* within the `value_t`. It does not store the *content* of that register.
*
* @param p_value Pointer to the `value_t` instance to initialize.
* @param reg The A32 register identifier (of type `a32_register_t`) to store.
* @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);
/*!
* @brief Retrieves an unsigned 64-bit immediate value from a `value_t`.
*
* @pre The `p_value` must be of type `IR_TYPE_U64`.
* @param p_value Pointer to the `value_t` instance.
* @retval uint64_t The 64-bit unsigned immediate value stored in `p_value`.
* @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);
/*!
* @brief Retrieves an unsigned 32-bit immediate value from a `value_t`.
*
* @pre The `p_value` must be of type `IR_TYPE_U32`.
* @param p_value Pointer to the `value_t` instance.
* @retval uint32_t The 32-bit unsigned immediate value stored in `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);
/*!
* @brief Retrieves an unsigned 8-bit immediate value from a `value_t`.
*
* @pre The `p_value` must be of type `IR_TYPE_U8`.
* @param p_value Pointer to the `value_t` instance.
* @retval uint8_t The 8-bit unsigned immediate value stored in `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);
/**
* @brief Retrieves an unsigned 1-bit immediate value from a `value_t`.
*
* @pre The `p_value` must be of type `IR_TYPE_U1`.
* @param p_value Pointer to the `value_t` instance.
* @retval bool The 1-bit unsigned immediate value stored in `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);
/**
* @brief Retrieves an A32 register identifier from a `value_t`.
*
* @pre The `p_value` must be of type `IR_TYPE_A32_REGISTER`.
* @param p_value Pointer to the `value_t` instance.
* @retval pound::jit::a32_register_t The A32 register identifier stored in `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);
} // namespace pound:::jit::ir

137
tests/jit/ir/test_value.cpp Normal file
View file

@ -0,0 +1,137 @@
#include "gtest/gtest.h"
#include "jit/ir/value.h"
namespace pound::jit::ir {
// Test fixture for value_t tests
class ValueTest : public ::testing::Test {
protected:
value_t val;
void SetUp() override {
value_init(&val);
}
void TearDown() override {
}
};
// Test value_init
TEST_F(ValueTest, InitializesToVoid) {
EXPECT_EQ(val.type, IR_TYPE_VOID);
}
// Test U1 type
TEST_F(ValueTest, U1InitializationAndRetrieval) {
value_init_from_u1(&val, true);
EXPECT_EQ(val.type, IR_TYPE_U1);
EXPECT_TRUE(value_get_u1(&val));
value_init_from_u1(&val, false);
EXPECT_EQ(val.type, IR_TYPE_U1);
EXPECT_FALSE(value_get_u1(&val));
}
// Test U8 type
TEST_F(ValueTest, U8InitializationAndRetrieval) {
uint8_t test_val = 0xAB;
value_init_from_u8(&val, test_val);
EXPECT_EQ(val.type, IR_TYPE_U8);
EXPECT_EQ(value_get_u8(&val), test_val);
value_init_from_u8(&val, 0x00);
EXPECT_EQ(val.type, IR_TYPE_U8);
EXPECT_EQ(value_get_u8(&val), 0x00);
value_init_from_u8(&val, 0xFF);
EXPECT_EQ(val.type, IR_TYPE_U8);
EXPECT_EQ(value_get_u8(&val), 0xFF);
}
// Test U32 type
TEST_F(ValueTest, U32InitializationAndRetrieval) {
uint32_t test_val = 0xABCDEF12;
value_init_from_u32(&val, test_val);
EXPECT_EQ(val.type, IR_TYPE_U32);
EXPECT_EQ(value_get_u32(&val), test_val);
value_init_from_u32(&val, 0x00000000);
EXPECT_EQ(val.type, IR_TYPE_U32);
EXPECT_EQ(value_get_u32(&val), 0x00000000);
value_init_from_u32(&val, 0xFFFFFFFF);
EXPECT_EQ(val.type, IR_TYPE_U32);
EXPECT_EQ(value_get_u32(&val), 0xFFFFFFFF);
}
// Test U64 type
TEST_F(ValueTest, U64InitializationAndRetrieval) {
uint64_t test_val = 0x123456789ABCDEF0ULL;
value_init_from_u64(&val, test_val);
EXPECT_EQ(val.type, IR_TYPE_U64);
EXPECT_EQ(value_get_u64(&val), test_val);
value_init_from_u64(&val, 0x0000000000000000ULL);
EXPECT_EQ(val.type, IR_TYPE_U64);
EXPECT_EQ(value_get_u64(&val), 0x0000000000000000ULL);
value_init_from_u64(&val, 0xFFFFFFFFFFFFFFFFULL);
EXPECT_EQ(val.type, IR_TYPE_U64);
EXPECT_EQ(value_get_u64(&val), 0xFFFFFFFFFFFFFFFFULL);
}
// Test A32 Register type
TEST_F(ValueTest, A32RegisterInitializationAndRetrieval) {
value_init_from_a32_register(&val, A32_REGISTER_R0);
EXPECT_EQ(val.type, IR_TYPE_A32_REGISTER);
EXPECT_EQ(value_get_a32_register(&val), A32_REGISTER_R0);
value_init_from_a32_register(&val, A32_REGISTER_SP);
EXPECT_EQ(val.type, IR_TYPE_A32_REGISTER);
EXPECT_EQ(value_get_a32_register(&val), A32_REGISTER_SP);
value_init_from_a32_register(&val, A32_REGISTER_PC);
EXPECT_EQ(val.type, IR_TYPE_A32_REGISTER);
EXPECT_EQ(value_get_a32_register(&val), A32_REGISTER_PC);
value_init_from_a32_register(&val, A32_REGISTER_R10);
EXPECT_EQ(val.type, IR_TYPE_A32_REGISTER);
EXPECT_EQ(value_get_a32_register(&val), A32_REGISTER_R10);
}
// --- Death Tests for Type Mismatches ---
// These tests assume that value_get_* functions use PVM_ASSERT
// to check the type and that PVM_ASSERT aborts the program,
// allowing GTest's EXPECT_DEATH to catch it.
TEST(ValueDeathTest, GetU1FromVoid) {
value_t v;
value_init(&v);
EXPECT_DEATH(value_get_u1(&v), "ASSERTION FAILURE");
}
TEST(ValueDeathTest, GetU8FromU32) {
value_t v;
value_init_from_u32(&v, 123);
EXPECT_DEATH(value_get_u8(&v), "ASSERTION FAILURE");
}
TEST(ValueDeathTest, GetU32FromU64) {
value_t v;
value_init_from_u64(&v, 123456789);
EXPECT_DEATH(value_get_u32(&v), "ASSERTION FAILURE");
}
TEST(ValueDeathTest, GetU64FromA32Register) {
value_t v;
value_init_from_a32_register(&v, A32_REGISTER_R5);
EXPECT_DEATH(value_get_u64(&v), "ASSERTION FAILURE");
}
TEST(ValueDeathTest, GetA32RegisterFromU1) {
value_t v;
value_init_from_u1(&v, true);
EXPECT_DEATH(value_get_a32_register(&v), "ASSERTION FAILURE");
}
} // namespace pound::jit::ir