From 2796d62954078c95692dd6b7187bd5fa506a1fa8 Mon Sep 17 00:00:00 2001 From: uslstenn Date: Sun, 12 Oct 2025 21:32:38 +0300 Subject: [PATCH 1/2] [memory] Page memory implementation was added --- cmake/utils.cmake | 19 +++- src/memory/CMakeLists.txt | 2 + src/memory/include/prot/page.hh | 80 +++++++++++++++++ src/memory/include/prot/page_memory.hh | 120 +++++++++++++++++++++++++ src/memory/tests/CMakeLists.txt | 8 ++ src/memory/tests/page_memory.cc | 92 +++++++++++++++++++ 6 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 src/memory/include/prot/page.hh create mode 100644 src/memory/include/prot/page_memory.hh create mode 100644 src/memory/tests/CMakeLists.txt create mode 100644 src/memory/tests/page_memory.cc diff --git a/cmake/utils.cmake b/cmake/utils.cmake index 2464d58..38a9b79 100644 --- a/cmake/utils.cmake +++ b/cmake/utils.cmake @@ -6,7 +6,7 @@ macro(prot_add_utest src_file) set(TEST_NAME "${src_file}_test") add_executable(${TEST_NAME} ${src_file}) - target_link_libraries(${TEST_NAME} PRIVATE GMock::gmock_main PROT::defaults) + target_link_libraries(${TEST_NAME} PRIVATE PROT::defaults GTest::gtest GTest::gtest_main GTest::gmock GTest::gmock_main) foreach(arg IN LISTS ARGN) target_link_libraries(${TEST_NAME} PRIVATE ${arg}) endforeach() @@ -15,3 +15,20 @@ macro(prot_add_utest src_file) EXTRA_ARGS --gtest_color=yes PROPERTIES LABELS unit) endmacro() + +function(prot_add_component_utest COMPONENT_NAME) + file(GLOB TESTLIST RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.cc") + find_package(GTest REQUIRED) + + foreach(TEST_SRC ${TESTLIST}) + set(TEST_NAME "${src_file}_test") + prot_add_utest(${TEST_SRC}) + target_include_directories(${TEST_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/src/${COMPONENT_NAME}/include) + + set_target_properties(${TEST_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/test/${COMPONENT_NAME}" + ) + + endforeach() +endfunction() diff --git a/src/memory/CMakeLists.txt b/src/memory/CMakeLists.txt index 02d640a..1aca73a 100644 --- a/src/memory/CMakeLists.txt +++ b/src/memory/CMakeLists.txt @@ -1,4 +1,6 @@ add_library(prot_mem STATIC memory.cc plain_memory.cc simple_page_mem.cc) +add_subdirectory(tests) + target_link_libraries( prot_mem PUBLIC PROT::isa diff --git a/src/memory/include/prot/page.hh b/src/memory/include/prot/page.hh new file mode 100644 index 0000000..92c48dc --- /dev/null +++ b/src/memory/include/prot/page.hh @@ -0,0 +1,80 @@ +#ifndef PROT_PAGE_HH_INCLUDED +#define PROT_PAGE_HH_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include "prot/isa.hh" + +namespace prot { +template class PageConfig { +public: + static constexpr std::uint64_t getPageBits() { return kPageBits; } + static constexpr std::uint64_t getPageSize() { return kPageSize; } + static constexpr std::uint64_t getOffsetMask() { return kOffsetMask; } + static constexpr std::uint64_t getIdMask() { return kIdMask; } + + static std::uint64_t getId(isa::Addr addr) { + return (addr & kIdMask) >> kPageBits; + } + + static isa::Addr getOffset(isa::Addr addr) { return addr & kOffsetMask; } + +private: + static constexpr std::uint64_t kPageBits{PageBits}; + static constexpr std::uint64_t kPageSize{1ULL << kPageBits}; + static constexpr std::uint64_t kOffsetMask{kPageSize - 1}; + static constexpr std::uint64_t kIdMask{~kOffsetMask}; +}; + +template +concept PageConfigConc = requires(T, isa::Addr addr) { + { T::getPageSize() } -> std::same_as; + { T::getId(addr) } -> std::same_as; + { T::getOffset(addr) } -> std::same_as; +}; + +class PageFault : public std::out_of_range { +public: + explicit PageFault(isa::Addr addr) + : std::out_of_range("Page fault at address: 0x" + std::to_string(addr)), + m_addr(addr) {} + + isa::Addr addr() const { return m_addr; } + +private: + isa::Addr m_addr; +}; + +template class Page final { +public: + Page() : m_data(PageConfigT::getPageSize()) {} + +public: + void writeBlock(const std::byte *src, isa::Addr offset, std::size_t size) { + if (offset >= PageConfigT::getPageSize()) + throw std::out_of_range{"Offset out of page"}; + + std::copy_n(src, size, m_data.data() + offset); + } + + void readBlock(isa::Addr offset, std::size_t size, std::byte *dst) const { + if (offset >= PageConfigT::getPageSize()) + throw std::out_of_range{"Offset out of page"}; + + std::copy_n(m_data.data() + offset, size, dst); + } + + auto at(isa::Addr addr) const { return m_data.at(addr); } + auto getSize() const { return PageConfigT::getPageSize(); } + +private: + std::vector m_data{}; +}; +} // namespace prot + +#endif // PROT_PAGE_HH_INCLUDED diff --git a/src/memory/include/prot/page_memory.hh b/src/memory/include/prot/page_memory.hh new file mode 100644 index 0000000..5c1cb47 --- /dev/null +++ b/src/memory/include/prot/page_memory.hh @@ -0,0 +1,120 @@ +#ifndef PROT_PAGE_MEMORY_HH_INCLUDED +#define PROT_PAGE_MEMORY_HH_INCLUDED + +#include "memory.hh" +#include "page.hh" + +#include +#include +#include +#include +#include + +namespace prot { + +template +bool memWalker(isa::Addr addr, std::size_t size, Func func) { + if (size == 0) + return true; + + isa::Addr currentAddr = addr; + std::size_t remainSize = size; + + while (remainSize > 0) { + std::uint64_t pageId = PageConfigT::getId(currentAddr); + isa::Addr offset = PageConfigT::getOffset(currentAddr); + std::size_t chunkSize = + std::min(remainSize, PageConfigT::getPageSize() - offset); + + if (!func(pageId, chunkSize, offset)) + return false; + + currentAddr += chunkSize; + remainSize -= chunkSize; + } + + return true; +} + +template +class PageMemConfig final { +public: + using PageConfigT = PageConfig; + using PageT = Page; + + static constexpr std::uint64_t getPagesNum() { + return kAddrSpace / PageConfigT::getPageSize(); + } + + static constexpr PageConfigT getPageConfig() { return kPageConf; } + +private: + static constexpr std::uint64_t kAddrBits{AddrBits}; + static constexpr std::uint64_t kAddrSpace{1ULL << AddrBits}; + static constexpr PageConfigT kPageConf{}; +}; + +template +concept PageMemConfigConc = requires(T) { + { T::getPagesNum() } -> std::same_as; + { T::getPageConfig() } -> std::same_as; +}; + +template class PageMemory : public Memory { +public: + using PageConfigT = typename PageMemConfigT::PageConfigT; + using PageT = typename PageMemConfigT::PageT; + + PageMemory() : m_config(PageMemConfigT::getPageConfig()) { + m_storage.reserve(PageMemConfigT::getPagesNum()); + } + + void writeBlock(std::span src, isa::Addr addr) override { + const auto *cur = src.data(); + + memWalker( + addr, src.size(), + [this, &cur](isa::Addr addr, size_t size, isa::Addr offset) { + auto &page = this->getPage(addr); + page.writeBlock(cur, offset, size); + cur += size; + return true; + }); + } + + void readBlock(isa::Addr addr, std::span dest) const override { + auto *cur = dest.data(); + + memWalker( + addr, dest.size(), + [this, &cur](isa::Addr addr, size_t size, isa::Addr offset) { + const auto &page = this->getPage(addr); + page.readBlock(offset, size, cur); + cur += size; + return true; + }); + } + +private: + PageT getPage(isa::Addr addr) const { + std::uint64_t pageId = m_config.getId(addr); + auto found = m_storage.find(pageId); + + if (found == m_storage.end()) + throw PageFault{addr}; + + return found->second; + } + + PageT &getPage(isa::Addr addr) { + std::uint64_t pageId = m_config.getId(addr); + auto [it, inserted] = m_storage.try_emplace(pageId); + return it->second; + } + + std::unordered_map m_storage{}; + PageConfigT m_config; +}; +} // namespace prot + +#endif // PROT_PAGE_MEMORY_HH_INCLUDED diff --git a/src/memory/tests/CMakeLists.txt b/src/memory/tests/CMakeLists.txt new file mode 100644 index 0000000..671b284 --- /dev/null +++ b/src/memory/tests/CMakeLists.txt @@ -0,0 +1,8 @@ +set(COMPONENT_NAME memory) + +if(BUILD_TESTS) + find_package(GTest REQUIRED) + + prot_add_component_utest(${COMPONENT_NAME}) + +endif() diff --git a/src/memory/tests/page_memory.cc b/src/memory/tests/page_memory.cc new file mode 100644 index 0000000..cba5af3 --- /dev/null +++ b/src/memory/tests/page_memory.cc @@ -0,0 +1,92 @@ +#include + +#include "prot/page_memory.hh" + +class MemTest : public ::testing::Test { +protected: + // RAM -- 64kb, Page -- 4KB + prot::PageMemory> m_mem{}; +}; + +TEST_F(MemTest, writeBlockTest) { + const std::byte test_data[] = {std::byte{0xde}, std::byte{0xad}, + std::byte{0xbe}, std::byte{0xef}}; + + std::byte read_data[sizeof(test_data)]; + + constexpr prot::isa::Addr addr = 42; + + m_mem.writeBlock(test_data, addr); + m_mem.readBlock(addr, read_data); + + EXPECT_TRUE(std::equal(std::begin(test_data), std::end(test_data), + std::begin(read_data))); +} + +TEST_F(MemTest, writeBlockTest2) { + const std::byte test_data[] = {std::byte{0xde}, std::byte{0xad}, + std::byte{0xbe}, std::byte{0xef}}; + + constexpr prot::isa::Addr addr = 42; + + m_mem.writeBlock(test_data, addr); + + EXPECT_EQ(m_mem.read(addr), 0xde); + EXPECT_EQ(m_mem.read(addr), 0xadde); + EXPECT_EQ(m_mem.read(addr), 0xefbeadde); +} + +TEST_F(MemTest, writeBlockTest3) { + constexpr prot::isa::Addr addr = 42; + + m_mem.write(addr + 0, 0xde); + m_mem.write(addr + 1, 0xad); + m_mem.write(addr + 2, 0xef); + m_mem.write(addr + 3, 0xef); + + EXPECT_EQ(m_mem.read(addr + 0), 0xde); + EXPECT_EQ(m_mem.read(addr + 1), 0xad); + EXPECT_EQ(m_mem.read(addr + 2), 0xef); + EXPECT_EQ(m_mem.read(addr + 3), 0xef); +} + +TEST_F(MemTest, crossPageBoundaryTest) { + constexpr prot::isa::Addr boundary_addr = 4096 - 2; + const std::byte test_data[] = {std::byte{0x12}, std::byte{0x34}, + std::byte{0x56}, std::byte{0x78}}; + + m_mem.writeBlock(test_data, boundary_addr); + + std::byte read_data[sizeof(test_data)]; + m_mem.readBlock(boundary_addr, read_data); + + EXPECT_TRUE(std::equal(std::begin(test_data), std::end(test_data), + std::begin(read_data))); +} + +TEST_F(MemTest, pageFaultTest) { + constexpr prot::isa::Addr unmapped_addr = 0x10000; + + std::byte buffer[4]; + + EXPECT_THROW(m_mem.readBlock(unmapped_addr, buffer), prot::PageFault); + + const std::byte test_data[] = {std::byte{0xaa}}; + m_mem.writeBlock(test_data, 0x100); + + std::byte read_byte[1]; + m_mem.readBlock(0x100, read_byte); + EXPECT_EQ(test_data[0], read_byte[0]); +} + +TEST_F(MemTest, edgeCasesTest) { + constexpr prot::isa::Addr end_addr = (1ULL << 16) - 4; + const std::byte end_data[] = {std::byte{0xfe}, std::byte{0xed}, + std::byte{0xfa}, std::byte{0xce}}; + m_mem.writeBlock(end_data, end_addr); + + std::byte read_end[4]; + m_mem.readBlock(end_addr, read_end); + EXPECT_TRUE(std::equal(std::begin(end_data), std::end(end_data), + std::begin(read_end))); +} From e1d43415c35f52cda719afb9d4cb3ac6b8fbee98 Mon Sep 17 00:00:00 2001 From: uslstenn Date: Mon, 13 Oct 2025 02:02:36 +0300 Subject: [PATCH 2/2] [memory] Upd --- cmake/utils.cmake | 4 +--- src/memory/CMakeLists.txt | 3 ++- src/memory/include/prot/page.hh | 13 ++++--------- src/memory/include/prot/page_memory.hh | 17 ++++++++++------- src/memory/tests/CMakeLists.txt | 5 +---- src/memory/tests/page_memory.cc | 21 ++++++++++++--------- 6 files changed, 30 insertions(+), 33 deletions(-) diff --git a/cmake/utils.cmake b/cmake/utils.cmake index 38a9b79..5cf1593 100644 --- a/cmake/utils.cmake +++ b/cmake/utils.cmake @@ -18,12 +18,10 @@ endmacro() function(prot_add_component_utest COMPONENT_NAME) file(GLOB TESTLIST RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.cc") - find_package(GTest REQUIRED) foreach(TEST_SRC ${TESTLIST}) set(TEST_NAME "${src_file}_test") - prot_add_utest(${TEST_SRC}) - target_include_directories(${TEST_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/src/${COMPONENT_NAME}/include) + prot_add_utest(${TEST_SRC} ARGN) set_target_properties(${TEST_NAME} PROPERTIES diff --git a/src/memory/CMakeLists.txt b/src/memory/CMakeLists.txt index 1aca73a..3446f7c 100644 --- a/src/memory/CMakeLists.txt +++ b/src/memory/CMakeLists.txt @@ -1,5 +1,4 @@ add_library(prot_mem STATIC memory.cc plain_memory.cc simple_page_mem.cc) -add_subdirectory(tests) target_link_libraries( prot_mem @@ -8,3 +7,5 @@ target_link_libraries( target_include_directories(prot_mem PUBLIC include) add_library(PROT::memory ALIAS prot_mem) + +add_subdirectory(tests) diff --git a/src/memory/include/prot/page.hh b/src/memory/include/prot/page.hh index 92c48dc..4f944f6 100644 --- a/src/memory/include/prot/page.hh +++ b/src/memory/include/prot/page.hh @@ -13,16 +13,11 @@ namespace prot { template class PageConfig { public: - static constexpr std::uint64_t getPageBits() { return kPageBits; } - static constexpr std::uint64_t getPageSize() { return kPageSize; } - static constexpr std::uint64_t getOffsetMask() { return kOffsetMask; } - static constexpr std::uint64_t getIdMask() { return kIdMask; } - - static std::uint64_t getId(isa::Addr addr) { - return (addr & kIdMask) >> kPageBits; - } + using PadeId = uint32_t; + static constexpr std::uint64_t getPageSize() { return kPageSize; } static isa::Addr getOffset(isa::Addr addr) { return addr & kOffsetMask; } + static PadeId getId(isa::Addr addr) { return (addr & kIdMask) >> kPageBits; } private: static constexpr std::uint64_t kPageBits{PageBits}; @@ -34,7 +29,7 @@ private: template concept PageConfigConc = requires(T, isa::Addr addr) { { T::getPageSize() } -> std::same_as; - { T::getId(addr) } -> std::same_as; + { T::getId(addr) } -> std::same_as; { T::getOffset(addr) } -> std::same_as; }; diff --git a/src/memory/include/prot/page_memory.hh b/src/memory/include/prot/page_memory.hh index 5c1cb47..e36fbc4 100644 --- a/src/memory/include/prot/page_memory.hh +++ b/src/memory/include/prot/page_memory.hh @@ -21,10 +21,9 @@ bool memWalker(isa::Addr addr, std::size_t size, Func func) { std::size_t remainSize = size; while (remainSize > 0) { - std::uint64_t pageId = PageConfigT::getId(currentAddr); - isa::Addr offset = PageConfigT::getOffset(currentAddr); - std::size_t chunkSize = - std::min(remainSize, PageConfigT::getPageSize() - offset); + auto pageId = PageConfigT::getId(currentAddr); + auto offset = PageConfigT::getOffset(currentAddr); + auto chunkSize = std::min(remainSize, PageConfigT::getPageSize() - offset); if (!func(pageId, chunkSize, offset)) return false; @@ -52,6 +51,9 @@ private: static constexpr std::uint64_t kAddrBits{AddrBits}; static constexpr std::uint64_t kAddrSpace{1ULL << AddrBits}; static constexpr PageConfigT kPageConf{}; + + static_assert(kAddrSpace >= PageConfigT::getPageSize(), + "Total virtual address space is smaller than single page size"); }; template @@ -64,6 +66,7 @@ template class PageMemory : public Memory { public: using PageConfigT = typename PageMemConfigT::PageConfigT; using PageT = typename PageMemConfigT::PageT; + using PageId = typename PageConfigT::PadeId; PageMemory() : m_config(PageMemConfigT::getPageConfig()) { m_storage.reserve(PageMemConfigT::getPagesNum()); @@ -97,7 +100,7 @@ public: private: PageT getPage(isa::Addr addr) const { - std::uint64_t pageId = m_config.getId(addr); + auto pageId = m_config.getId(addr); auto found = m_storage.find(pageId); if (found == m_storage.end()) @@ -107,12 +110,12 @@ private: } PageT &getPage(isa::Addr addr) { - std::uint64_t pageId = m_config.getId(addr); + auto pageId = m_config.getId(addr); auto [it, inserted] = m_storage.try_emplace(pageId); return it->second; } - std::unordered_map m_storage{}; + std::unordered_map m_storage{}; PageConfigT m_config; }; } // namespace prot diff --git a/src/memory/tests/CMakeLists.txt b/src/memory/tests/CMakeLists.txt index 671b284..cb87d22 100644 --- a/src/memory/tests/CMakeLists.txt +++ b/src/memory/tests/CMakeLists.txt @@ -1,8 +1,5 @@ set(COMPONENT_NAME memory) if(BUILD_TESTS) - find_package(GTest REQUIRED) - - prot_add_component_utest(${COMPONENT_NAME}) - + prot_add_component_utest(${COMPONENT_NAME} PROT::memory) endif() diff --git a/src/memory/tests/page_memory.cc b/src/memory/tests/page_memory.cc index cba5af3..e71c639 100644 --- a/src/memory/tests/page_memory.cc +++ b/src/memory/tests/page_memory.cc @@ -1,14 +1,17 @@ +#include "prot/page_memory.hh" #include -#include "prot/page_memory.hh" +class PageMemTest : public ::testing::Test { +public: + static constexpr std::uint64_t kPageBits = 12; + static constexpr std::uint64_t kAddrBits = 16; -class MemTest : public ::testing::Test { protected: // RAM -- 64kb, Page -- 4KB - prot::PageMemory> m_mem{}; + prot::PageMemory> m_mem{}; }; -TEST_F(MemTest, writeBlockTest) { +TEST_F(PageMemTest, writeBlockTest) { const std::byte test_data[] = {std::byte{0xde}, std::byte{0xad}, std::byte{0xbe}, std::byte{0xef}}; @@ -23,7 +26,7 @@ TEST_F(MemTest, writeBlockTest) { std::begin(read_data))); } -TEST_F(MemTest, writeBlockTest2) { +TEST_F(PageMemTest, writeBlockTest2) { const std::byte test_data[] = {std::byte{0xde}, std::byte{0xad}, std::byte{0xbe}, std::byte{0xef}}; @@ -36,7 +39,7 @@ TEST_F(MemTest, writeBlockTest2) { EXPECT_EQ(m_mem.read(addr), 0xefbeadde); } -TEST_F(MemTest, writeBlockTest3) { +TEST_F(PageMemTest, writeBlockTest3) { constexpr prot::isa::Addr addr = 42; m_mem.write(addr + 0, 0xde); @@ -50,7 +53,7 @@ TEST_F(MemTest, writeBlockTest3) { EXPECT_EQ(m_mem.read(addr + 3), 0xef); } -TEST_F(MemTest, crossPageBoundaryTest) { +TEST_F(PageMemTest, crossPageBoundaryTest) { constexpr prot::isa::Addr boundary_addr = 4096 - 2; const std::byte test_data[] = {std::byte{0x12}, std::byte{0x34}, std::byte{0x56}, std::byte{0x78}}; @@ -64,7 +67,7 @@ TEST_F(MemTest, crossPageBoundaryTest) { std::begin(read_data))); } -TEST_F(MemTest, pageFaultTest) { +TEST_F(PageMemTest, pageFaultTest) { constexpr prot::isa::Addr unmapped_addr = 0x10000; std::byte buffer[4]; @@ -79,7 +82,7 @@ TEST_F(MemTest, pageFaultTest) { EXPECT_EQ(test_data[0], read_byte[0]); } -TEST_F(MemTest, edgeCasesTest) { +TEST_F(PageMemTest, edgeCasesTest) { constexpr prot::isa::Addr end_addr = (1ULL << 16) - 4; const std::byte end_data[] = {std::byte{0xfe}, std::byte{0xed}, std::byte{0xfa}, std::byte{0xce}};