diff --git a/.gitmodules b/.gitmodules index 4825855..82a9e4c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/3rd_Party/CMakeLists.txt b/3rd_Party/CMakeLists.txt index 1f2c1ff..6bf847e 100644 --- a/3rd_Party/CMakeLists.txt +++ b/3rd_Party/CMakeLists.txt @@ -29,3 +29,7 @@ target_include_directories(imgui PUBLIC imgui imgui/backends ) + +# GoogleTest +add_subdirectory(googletest) +enable_testing() diff --git a/3rd_Party/PINNED_DEPENDENCIES.md b/3rd_Party/PINNED_DEPENDENCIES.md index 130e103..5a45c82 100644 --- a/3rd_Party/PINNED_DEPENDENCIES.md +++ b/3rd_Party/PINNED_DEPENDENCIES.md @@ -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 diff --git a/3rd_Party/googletest b/3rd_Party/googletest new file mode 160000 index 0000000..52eb810 --- /dev/null +++ b/3rd_Party/googletest @@ -0,0 +1 @@ +Subproject commit 52eb8108c5bdec04579160ae17225d66034bd723 diff --git a/CMakeLists.txt b/CMakeLists.txt index 08fb491..85907bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}/.. +) diff --git a/src/jit/ir/value.cpp b/src/jit/ir/value.cpp index c12a174..ce17ecb 100644 --- a/src/jit/ir/value.cpp +++ b/src/jit/ir/value.cpp @@ -1,22 +1,8 @@ -#include "type.h" -#include "jit/a32_types.h" +#include "value.h" #include "common/passert.h" #include 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; } } diff --git a/src/jit/ir/value.h b/src/jit/ir/value.h new file mode 100644 index 0000000..53b24e5 --- /dev/null +++ b/src/jit/ir/value.h @@ -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 +#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 diff --git a/tests/jit/ir/test_value.cpp b/tests/jit/ir/test_value.cpp new file mode 100644 index 0000000..ea83364 --- /dev/null +++ b/tests/jit/ir/test_value.cpp @@ -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