diff --git a/.0pdd.yml b/.0pdd.yml index b1507b4..d879988 100644 --- a/.0pdd.yml +++ b/.0pdd.yml @@ -6,5 +6,6 @@ alerts: - c71n93 - mmamayka - levBagryansky + - ivan-egorov42 tags: - pdd \ No newline at end of file diff --git a/include/besm-666/util/assotiative-cache.hpp b/include/besm-666/util/assotiative-cache.hpp new file mode 100644 index 0000000..5ecb639 --- /dev/null +++ b/include/besm-666/util/assotiative-cache.hpp @@ -0,0 +1,299 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace besm::util { + +// @todo #71:90m Implement TagFunction and HashFunction and get rid of keeping +// tag in CacheEntry +template class CacheEntry { +public: + CacheEntry(); + ~CacheEntry() = default; + + void setPayload(PayloadType const &payload, TagType tag) noexcept( + std::is_nothrow_copy_constructible_v); + void setPayload(PayloadType &&payload, TagType tag) noexcept( + std::is_nothrow_move_constructible_v); + + PayloadType const &getPayload() const noexcept; + PayloadType &getPayload() noexcept; + TagType const &getTag() const noexcept; + + void invalidate() noexcept; + bool valid() const noexcept; + +private: + PayloadType payload_; + bool valid_; + TagType tag_; +}; + +template +using TagFunction = TagType (*)(PayloadType const &); + +template +using HashFunction = HashType (*)(PayloadType const &); + +template TagFunc, + HashFunction HashFunc> +class Cache { +public: + Cache(size_t ways, size_t sets); + + Cache(Cache &&other) noexcept; + Cache &operator=(Cache &&other) noexcept; + + void add(PayloadType const &payload) noexcept( + std::is_nothrow_copy_constructible_v); + void add(PayloadType &&payload) noexcept( + std::is_nothrow_move_constructible_v); + + CacheEntry &find(TagType tag) noexcept; + CacheEntry const &find(TagType tag) const noexcept; + + void invalidate(TagType tag) noexcept; + void invalidate() noexcept; + size_t getSize() const noexcept; + size_t getWays() const noexcept; + size_t getSets() const noexcept; + size_t getCounter(size_t set) noexcept; + + template friend class CacheStateDumper; + +private: + size_t ways_; + size_t sets_; + size_t size_ = ways_ * sets_; + std::unique_ptr counters_; + +protected: + std::unique_ptr[]> cachedData_; +}; + +template class CacheStateDumper { +public: + CacheStateDumper(std::ostream &stream) : stream_(stream) {} + void dump(Cache &cache); + +private: + std::ostream &stream_; +}; + +//-------------CacheStateDumper------------------// +template void CacheStateDumper::dump(Cache &cache) { + + stream_ << "[Cache] State dump" << std::endl; + + for (int i = 0; i < cache.getSets(); i++) { + for (int j = 0; j < cache.getWays(); j++) { + if (cache.cachedData_[i * cache.getWays() + j].valid()) + stream_ + << cache.cachedData_[i * cache.getWays() + j].getPayload() + << " "; + else + stream_ << "inv "; + } + stream_ << std::endl; + } + + stream_ << "Size: " << cache.getSize() << std::endl; +} + +//-------------Cache------------------// + +template TagFunc, + HashFunction HashFunc> +Cache::Cache( + Cache &&other) noexcept + : ways_(other.ways_), sets_(other.sets_), size_(other.size_), + counters_(std::move(other.counters_)), + cachedData_(std::move(other.cachedData_)) {} + +template TagFunc, + HashFunction HashFunc> +Cache & +Cache::operator=( + Cache &&other) noexcept { + std::swap(ways_, other.ways_); + std::swap(sets_, other.sets_); + std::swap(size_, other.size_); + std::swap(counters_, other.counters_); + std::swap(cachedData_, other.cachedData_); +} + +template TagFunc, + HashFunction HashFunc> +Cache::Cache(size_t ways, + size_t sets) + : ways_(ways), sets_(sets) { + cachedData_ = + std::make_unique[]>(ways * sets); + counters_ = std::make_unique(sets); +} + +template TagFunc, + HashFunction HashFunc> +void Cache::add( + PayloadType const + &payload) noexcept(std::is_nothrow_copy_constructible_v) { + TagType tag = TagFunc(payload); + auto set = HashFunc(tag) % sets_; + + cachedData_[set * ways_ + counters_[set]].setPayload(payload, tag); + counters_[set] = (counters_[set] + 1) % ways_; +} + +template TagFunc, + HashFunction HashFunc> +void Cache::add( + PayloadType + &&payload) noexcept(std::is_nothrow_move_constructible_v) { + TagType tag = TagFunc(payload); + auto set = HashFunc(tag) % sets_; + + cachedData_[set * ways_ + counters_[set]].setPayload(std::move(payload), + tag); + counters_[set] = (counters_[set] + 1) % ways_; +} + +template TagFunc, + HashFunction HashFunc> +CacheEntry & +Cache::find( + TagType tag) noexcept { + auto set = HashFunc(tag) % sets_; + for (auto i = set * ways_; i < set * ways_ + ways_; i++) { + if (cachedData_[i].getTag() == tag && cachedData_[i].valid()) + return cachedData_[i]; + } + + return cachedData_[set * ways_ + counters_[set]]; +} + +template TagFunc, + HashFunction HashFunc> +CacheEntry const & +Cache::find( + TagType tag) const noexcept { + return const_cast const &>( + (const_cast *>( + this)) + ->find(tag)); +} + +template TagFunc, + HashFunction HashFunc> +void Cache::invalidate( + TagType tag) noexcept { + auto set = HashFunc(tag) % sets_; + for (auto i = set * ways_; i < set * ways_ + ways_; i++) { + if (cachedData_[i].getTag() == tag && cachedData_[i].valid()) + cachedData_[i].invalidate(); + } +} + +template TagFunc, + HashFunction HashFunc> +void Cache::invalidate() noexcept { + for (int i = 0; i < getSize(); i++) { + cachedData_[i].invalidate(); + } +} + +template TagFunc, + HashFunction HashFunc> +size_t Cache::getSize() + const noexcept { + return size_; +} + +template TagFunc, + HashFunction HashFunc> +size_t Cache::getWays() + const noexcept { + return ways_; +} + +template TagFunc, + HashFunction HashFunc> +size_t Cache::getSets() + const noexcept { + return sets_; +} + +template TagFunc, + HashFunction HashFunc> +size_t Cache::getCounter( + size_t set) noexcept { + return counters_[set]; +} + +//-------------CacheEntry------------------// +template +CacheEntry::CacheEntry() : valid_(false), tag_(0) {} + +template +void CacheEntry::setPayload( + PayloadType const &payload, + TagType tag) noexcept(std::is_nothrow_copy_constructible_v) { + payload_ = payload; + tag_ = tag; + valid_ = true; +} + +template +void CacheEntry::setPayload( + PayloadType &&payload, + TagType tag) noexcept(std::is_nothrow_move_constructible_v) { + payload_ = std::move(payload); + tag_ = tag; + valid_ = true; +} + +template +PayloadType const & +CacheEntry::getPayload() const noexcept { + return payload_; +} + +template +PayloadType &CacheEntry::getPayload() noexcept { + return payload_; +} + +template +void CacheEntry::invalidate() noexcept { + valid_ = false; +} + +template +bool CacheEntry::valid() const noexcept { + return valid_; +} + +template +TagType const &CacheEntry::getTag() const noexcept { + return tag_; +} +} // namespace besm::util diff --git a/unit_test/util/CMakeLists.txt b/unit_test/util/CMakeLists.txt index 19b1e15..12cdfda 100644 --- a/unit_test/util/CMakeLists.txt +++ b/unit_test/util/CMakeLists.txt @@ -1,4 +1,4 @@ add_subdirectory(elf) - besm666_test(./bit-magic-tests.cpp) -besm666_test(./range-test.cpp) +besm666_test(./assotiative-cache-tests.cpp) +besm666_test(./range-test.cpp) \ No newline at end of file diff --git a/unit_test/util/assotiative-cache-tests.cpp b/unit_test/util/assotiative-cache-tests.cpp new file mode 100644 index 0000000..67f12a9 --- /dev/null +++ b/unit_test/util/assotiative-cache-tests.cpp @@ -0,0 +1,101 @@ +#include "besm-666/riscv-types.hpp" +#include "besm-666/util/assotiative-cache.hpp" +#include + +using namespace besm::util; +template +HashType fhash(TagType const &tag) { + return std::hash{}(tag); +} + +template +TagType ftag(PayloadType const &payload) { + return 2 * payload; +} + +class CacheTest : public ::testing::Test { +protected: + void SetUp() override { + cache.add(5); + cache.add(5); + cache.add(5); + cache.add(25); + cache.add(42); + cache.add(5); + } + + Cache cache{2, 25}; +}; + +TEST_F(CacheTest, find) { + using namespace besm::util; + EXPECT_EQ(cache.find(ftag(5)).getPayload(), 5); + EXPECT_EQ(cache.find(ftag(25)).getPayload(), 25); + EXPECT_EQ(cache.find(ftag(42)).getPayload(), 42); +} + +TEST_F(CacheTest, valid) { + using namespace besm::util; + EXPECT_TRUE(cache.find(ftag(5)).valid()); + EXPECT_TRUE(cache.find(ftag(25)).valid()); + EXPECT_TRUE(cache.find(ftag(42)).valid()); +} + +TEST_F(CacheTest, invalid) { + using namespace besm::util; + EXPECT_FALSE(cache.find(228).valid()); + EXPECT_FALSE(cache.find(1488).valid()); + EXPECT_FALSE(cache.find(404).valid()); +} + +TEST_F(CacheTest, counters) { + using namespace besm::util; + EXPECT_EQ(cache.getCounter(5 * 2 % 25), 0); + EXPECT_EQ(cache.getCounter(25 * 2 % 25), 1); + EXPECT_EQ(cache.getCounter(42 * 2 % 25), 1); +} + +constexpr size_t TLBSets = 64; +constexpr size_t TLBWays = 4; + +struct TLBEntry { + // page id = page address / page size + besm::RV64Size virtualPageId; + besm::RV64Size physicalPageId; +}; + +template +TagType TLBTag(PayloadType const &entry) { + return entry.virtualPageId; +} + +template +HashType TLBHash(TagType const &tag) { + return tag & (TLBSets - 1); +} + +TEST(AssotiativeCache, ComplexPayload) { + using TLBCache = besm::util::Cache; + + TLBCache cache(TLBWays, TLBSets); + + // Everyone goes to the same way. 1:1 mapping + cache.add(TLBEntry{64 * 0, 64 * 0}); + cache.add(TLBEntry{64 * 1, 64 * 1}); + cache.add(TLBEntry{64 * 2, 64 * 2}); + cache.add(TLBEntry{64 * 3, 64 * 3}); + cache.add(TLBEntry{64 * 4, 64 * 4}); + + auto entry = cache.find(64 * 4); + EXPECT_TRUE(entry.valid()); + EXPECT_EQ(entry.getTag(), 64 * 4); + EXPECT_EQ(entry.getPayload().virtualPageId, 64 * 4); + EXPECT_EQ(entry.getPayload().physicalPageId, 64 * 4); + + // find() was changed and doesn't invalidate Entry in case of cache miss so + // this test isn't actual for now. + + // auto lostEntry = cache.find(64 * 0); + // EXPECT_FALSE(lostEntry.valid()); +}