diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 64592a8855..07e9ae7a8f 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -151,6 +151,17 @@ if (ENABLE_WEB_SERVICE) ) endif() +# unordered_dense +AddPackage( + NAME unordered_dense + REPO "Lizzie841/unordered_dense" + SHA e59d30b7b1 + HASH 71eff7bd9ba4b9226967bacd56a8ff000946f8813167cb5664bb01e96fb79e4e220684d824fe9c59c4d1cc98c606f13aff05b7940a1ed8ab3c95d6974ee34fa0 + FIND_PACKAGE_ARGUMENTS "CONFIG" + OPTIONS + "UNORDERED_DENSE_INSTALL OFF" +) + # FFMpeg if (YUZU_USE_BUNDLED_FFMPEG) add_subdirectory(ffmpeg) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index e9aed1d7af..cbe1d35fc5 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -262,13 +262,13 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") endif() if (BOOST_NO_HEADERS) - target_link_libraries(common PUBLIC Boost::algorithm Boost::icl Boost::pool) + target_link_libraries(common PUBLIC Boost::algorithm Boost::icl Boost::pool) else() - target_link_libraries(common PUBLIC Boost::headers) + target_link_libraries(common PUBLIC Boost::headers) endif() if (lz4_ADDED) - target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib) + target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib) endif() target_link_libraries(common PUBLIC fmt::fmt stb::headers Threads::Threads) @@ -280,6 +280,11 @@ else() target_link_libraries(common PRIVATE zstd) endif() +if (TARGET unordered_dense::unordered_dense) + # weird quirk of system installs + target_link_libraries(common PUBLIC unordered_dense::unordered_dense) +endif() + if(ANDROID) # For ASharedMemory_create target_link_libraries(common PRIVATE android) diff --git a/src/common/heap_tracker.cpp b/src/common/heap_tracker.cpp index 6832087959..2babc58817 100644 --- a/src/common/heap_tracker.cpp +++ b/src/common/heap_tracker.cpp @@ -1,3 +1,5 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -39,25 +41,18 @@ void HeapTracker::Map(size_t virtual_offset, size_t host_offset, size_t length, m_buffer.Map(virtual_offset, host_offset, length, perm, false); return; } - { - // We are mapping part of a separate heap. + // We are mapping part of a separate heap and insert into mappings. std::scoped_lock lk{m_lock}; - - auto* const map = new SeparateHeapMap{ - .vaddr = virtual_offset, + m_map_count++; + m_mappings.insert_or_assign(virtual_offset, SeparateHeapMap{ .paddr = host_offset, .size = length, .tick = m_tick++, .perm = perm, .is_resident = false, - }; - - // Insert into mappings. - m_map_count++; - m_mappings.insert(*map); + }); } - // Finally, map. this->DeferredMapSeparateHeap(virtual_offset); } @@ -66,36 +61,22 @@ void HeapTracker::Unmap(size_t virtual_offset, size_t size, bool is_separate_hea // If this is a separate heap... if (is_separate_heap) { std::scoped_lock lk{m_lock}; - - const SeparateHeapMap key{ - .vaddr = virtual_offset, - }; - // Split at the boundaries of the region we are removing. this->SplitHeapMapLocked(virtual_offset); this->SplitHeapMapLocked(virtual_offset + size); - // Erase all mappings in range. - auto it = m_mappings.find(key); - while (it != m_mappings.end() && it->vaddr < virtual_offset + size) { - // Get underlying item. - auto* const item = std::addressof(*it); - + auto it = m_mappings.find(virtual_offset); + while (it != m_mappings.end() && it->first < virtual_offset + size) { // If resident, erase from resident map. - if (item->is_resident) { + if (it->second.is_resident) { ASSERT(--m_resident_map_count >= 0); - m_resident_mappings.erase(m_resident_mappings.iterator_to(*item)); + m_resident_mappings.erase(m_resident_mappings.find(it->first)); } - // Erase from map. ASSERT(--m_map_count >= 0); it = m_mappings.erase(it); - - // Free the item. - delete item; } } - // Unmap pages. m_buffer.Unmap(virtual_offset, size, false); } @@ -117,68 +98,56 @@ void HeapTracker::Protect(size_t virtual_offset, size_t size, MemoryPermission p { std::scoped_lock lk2{m_lock}; - - const SeparateHeapMap key{ - .vaddr = next, - }; - // Try to get the next mapping corresponding to this address. - const auto it = m_mappings.nfind(key); - + const auto it = m_mappings.find(next); if (it == m_mappings.end()) { // There are no separate heap mappings remaining. next = end; should_protect = true; - } else if (it->vaddr == cur) { + } else if (it->first == cur) { // We are in range. // Update permission bits. - it->perm = perm; + it->second.perm = perm; // Determine next address and whether we should protect. - next = cur + it->size; - should_protect = it->is_resident; + next = cur + it->second.size; + should_protect = it->second.is_resident; } else /* if (it->vaddr > cur) */ { // We weren't in range, but there is a block coming up that will be. - next = it->vaddr; + next = it->first; should_protect = true; } } // Clamp to end. next = std::min(next, end); - // Reprotect, if we need to. - if (should_protect) { + if (should_protect) m_buffer.Protect(cur, next - cur, perm); - } - // Advance. cur = next; } } bool HeapTracker::DeferredMapSeparateHeap(u8* fault_address) { - if (m_buffer.IsInVirtualRange(fault_address)) { + if (m_buffer.IsInVirtualRange(fault_address)) return this->DeferredMapSeparateHeap(fault_address - m_buffer.VirtualBasePointer()); - } - return false; } bool HeapTracker::DeferredMapSeparateHeap(size_t virtual_offset) { bool rebuild_required = false; - { std::scoped_lock lk{m_lock}; // Check to ensure this was a non-resident separate heap mapping. const auto it = this->GetNearestHeapMapLocked(virtual_offset); - if (it == m_mappings.end() || it->is_resident) { + if (it == m_mappings.end() || it->second.is_resident) { return false; } // Update tick before possible rebuild. - it->tick = m_tick++; + it->second.tick = m_tick++; // Check if we need to rebuild. if (m_resident_map_count > m_max_resident_map_count) { @@ -186,41 +155,34 @@ bool HeapTracker::DeferredMapSeparateHeap(size_t virtual_offset) { } // Map the area. - m_buffer.Map(it->vaddr, it->paddr, it->size, it->perm, false); + m_buffer.Map(it->first, it->second.paddr, it->second.size, it->second.perm, false); // This map is now resident. - it->is_resident = true; + it->second.is_resident = true; m_resident_map_count++; m_resident_mappings.insert(*it); } - if (rebuild_required) { - // A rebuild was required, so perform it now. + // A rebuild was required, so perform it now. + if (rebuild_required) this->RebuildSeparateHeapAddressSpace(); - } - return true; } void HeapTracker::RebuildSeparateHeapAddressSpace() { std::scoped_lock lk{m_rebuild_lock, m_lock}; - ASSERT(!m_resident_mappings.empty()); - // Dump half of the mappings. - // // Despite being worse in theory, this has proven to be better in practice than more // regularly dumping a smaller amount, because it significantly reduces average case // lock contention. - const size_t desired_count = std::min(m_resident_map_count, m_max_resident_map_count) / 2; - const size_t evict_count = m_resident_map_count - desired_count; + std::size_t const desired_count = std::min(m_resident_map_count, m_max_resident_map_count) / 2; + std::size_t const evict_count = m_resident_map_count - desired_count; auto it = m_resident_mappings.begin(); - - for (size_t i = 0; i < evict_count && it != m_resident_mappings.end(); i++) { + for (std::size_t i = 0; i < evict_count && it != m_resident_mappings.end(); i++) { // Unmark and unmap. - it->is_resident = false; - m_buffer.Unmap(it->vaddr, it->size, false); - + it->second.is_resident = false; + m_buffer.Unmap(it->first, it->second.size, false); // Advance. ASSERT(--m_resident_map_count >= 0); it = m_resident_mappings.erase(it); @@ -229,53 +191,32 @@ void HeapTracker::RebuildSeparateHeapAddressSpace() { void HeapTracker::SplitHeapMap(VAddr offset, size_t size) { std::scoped_lock lk{m_lock}; - this->SplitHeapMapLocked(offset); this->SplitHeapMapLocked(offset + size); } void HeapTracker::SplitHeapMapLocked(VAddr offset) { - const auto it = this->GetNearestHeapMapLocked(offset); - if (it == m_mappings.end() || it->vaddr == offset) { - // Not contained or no split required. - return; + auto it = this->GetNearestHeapMapLocked(offset); + if (it != m_mappings.end() && it->first != offset) { + // Adjust left iterator + auto const orig_size = it->second.size; + auto const left_size = offset - it->first; + it->second.size = left_size; + // Insert the new right map. + auto const right = SeparateHeapMap{ + .paddr = it->second.paddr + left_size, + .size = orig_size - left_size, + .tick = it->second.tick, + .perm = it->second.perm, + .is_resident = it->second.is_resident, + }; + m_map_count++; + auto rit = m_mappings.insert_or_assign(it->first + left_size, right); + if (rit.first->second.is_resident) { + m_resident_map_count++; + m_resident_mappings.insert(*rit.first); + } } - - // Cache the original values. - auto* const left = std::addressof(*it); - const size_t orig_size = left->size; - - // Adjust the left map. - const size_t left_size = offset - left->vaddr; - left->size = left_size; - - // Create the new right map. - auto* const right = new SeparateHeapMap{ - .vaddr = left->vaddr + left_size, - .paddr = left->paddr + left_size, - .size = orig_size - left_size, - .tick = left->tick, - .perm = left->perm, - .is_resident = left->is_resident, - }; - - // Insert the new right map. - m_map_count++; - m_mappings.insert(*right); - - // If resident, also insert into resident map. - if (right->is_resident) { - m_resident_map_count++; - m_resident_mappings.insert(*right); - } -} - -HeapTracker::AddrTree::iterator HeapTracker::GetNearestHeapMapLocked(VAddr offset) { - const SeparateHeapMap key{ - .vaddr = offset, - }; - - return m_mappings.find(key); } } // namespace Common diff --git a/src/common/heap_tracker.h b/src/common/heap_tracker.h index ee5b0bf43a..18ec711d65 100644 --- a/src/common/heap_tracker.h +++ b/src/common/heap_tracker.h @@ -1,93 +1,57 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include #include -#include #include - +#include #include "common/host_memory.h" -#include "common/intrusive_red_black_tree.h" namespace Common { struct SeparateHeapMap { - Common::IntrusiveRedBlackTreeNode addr_node{}; - Common::IntrusiveRedBlackTreeNode tick_node{}; - VAddr vaddr{}; - PAddr paddr{}; - size_t size{}; - size_t tick{}; - MemoryPermission perm{}; - bool is_resident{}; -}; - -struct SeparateHeapMapAddrComparator { - static constexpr int Compare(const SeparateHeapMap& lhs, const SeparateHeapMap& rhs) { - if (lhs.vaddr < rhs.vaddr) { - return -1; - } else if (lhs.vaddr <= (rhs.vaddr + rhs.size - 1)) { - return 0; - } else { - return 1; - } - } -}; - -struct SeparateHeapMapTickComparator { - static constexpr int Compare(const SeparateHeapMap& lhs, const SeparateHeapMap& rhs) { - if (lhs.tick < rhs.tick) { - return -1; - } else if (lhs.tick > rhs.tick) { - return 1; - } else { - return SeparateHeapMapAddrComparator::Compare(lhs, rhs); - } - } + PAddr paddr{}; //8 + std::size_t size{}; //8 (16) + std::size_t tick{}; //8 (24) + // 4 bits needed, sync with host_memory.h if needed + MemoryPermission perm : 4 = MemoryPermission::Read; + bool is_resident : 1 = false; }; +static_assert(sizeof(SeparateHeapMap) == 32); //half a cache line! good for coherency class HeapTracker { public: explicit HeapTracker(Common::HostMemory& buffer); ~HeapTracker(); - - void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perm, - bool is_separate_heap); + void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perm, bool is_separate_heap); void Unmap(size_t virtual_offset, size_t size, bool is_separate_heap); void Protect(size_t virtual_offset, size_t length, MemoryPermission perm); - u8* VirtualBasePointer() { + inline u8* VirtualBasePointer() noexcept { return m_buffer.VirtualBasePointer(); } - bool DeferredMapSeparateHeap(u8* fault_address); bool DeferredMapSeparateHeap(size_t virtual_offset); - private: - using AddrTreeTraits = - Common::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&SeparateHeapMap::addr_node>; - using AddrTree = AddrTreeTraits::TreeType; - - using TickTreeTraits = - Common::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&SeparateHeapMap::tick_node>; - using TickTree = TickTreeTraits::TreeType; - - AddrTree m_mappings{}; - TickTree m_resident_mappings{}; - + // TODO: You may want to "fake-map" the first 2GB of 64-bit address space + // and dedicate it entirely to a recursive PTE mapping :) + // However Ankerl is way better than using an RB tree, in all senses + using AddrTree = ankerl::unordered_dense::map; + AddrTree m_mappings; + using TicksTree = ankerl::unordered_dense::map; + TicksTree m_resident_mappings; private: void SplitHeapMap(VAddr offset, size_t size); void SplitHeapMapLocked(VAddr offset); - - AddrTree::iterator GetNearestHeapMapLocked(VAddr offset); - void RebuildSeparateHeapAddressSpace(); - + inline HeapTracker::AddrTree::iterator GetNearestHeapMapLocked(VAddr offset) noexcept { + return m_mappings.find(offset); + } private: Common::HostMemory& m_buffer; const s64 m_max_resident_map_count; - std::shared_mutex m_rebuild_lock{}; std::mutex m_lock{}; s64 m_map_count{};