diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-29 05:46:38 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-29 20:49:47 -0400 |
| commit | 06f294c7e6b305d5d8904d284de70c268cc3f2e0 (patch) | |
| tree | 03181491fb0869fa12856ae8a6c8d6616ebc2f04 /src | |
| parent | 7fa88f0bb18ab0ae1896e6060a0c770e73446f51 (diff) | |
core: per-thread device memory translation cache
DeviceMemoryManager kept one four-entry page translation cache for every thread. Map
(on a guest service thread, e.g. nx2:gpu 211 job mappings) cleared it while the GPU
thread was between its null check of an entry and the copy from it: the GPU thread
then read from address 0 plus the page offset. Title 1 crashed this way in about half
of its 150 s test runs (BindHostGraphicsUniformBuffer -> ReadBlockUnsafe, fault at
0x600). Each thread now keeps its own cache, which Map and Unmap invalidate through a
generation counter. 0 crashes in 6 runs after.
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/device_memory_manager.h | 24 | ||||
| -rw-r--r-- | src/core/device_memory_manager.inc | 26 |
2 files changed, 36 insertions, 14 deletions
diff --git a/src/core/device_memory_manager.h b/src/core/device_memory_manager.h index 9ed0b6f5e5..46cda14e78 100644 --- a/src/core/device_memory_manager.h +++ b/src/core/device_memory_manager.h @@ -132,6 +132,27 @@ private: u8* host_ptr{}; }; + /// A thread's last four page translations of one manager. Several threads read through + /// them (the GPU thread, the fencing thread, service threads): one cache shared by all was + /// cleared by Map on a service thread between the GPU thread's null check of an entry and + /// its use, and the GPU thread copied from address 0 (title 1 crashed every few minutes). + struct TranslationCache { + const void* owner{}; + u64 generation{}; + std::array<TranslationEntry, 4> slots{}; + u32 cursor{}; + }; + static inline thread_local TranslationCache t_cache{}; + + /// This thread's translations, emptied when Map or Unmap ran since they were made. + TranslationCache& Translations() { + const u64 current = translation_generation.load(std::memory_order_acquire); + if (t_cache.owner != this || t_cache.generation != current) { + t_cache = TranslationCache{.owner = this, .generation = current}; + } + return t_cache; + } + // Internal helper that performs the update assuming the caller already holds the necessary lock. void UpdatePagesCachedCountNoLock(DAddr addr, size_t size, s32 delta); @@ -204,8 +225,7 @@ private: tracked_entries.GetUnchecked(page_index).cpu_backing_address = address | (asid.id << asid_start_bit); } - std::array<TranslationEntry, 4> t_slot{}; - u32 cache_cursor = 0; + std::atomic<u64> translation_generation{1}; ///< Bumped by Map and Unmap using CounterType = u8; using CounterAtomicType = std::atomic_uint8_t; static constexpr size_t subentries = 8 / sizeof(CounterType); diff --git a/src/core/device_memory_manager.inc b/src/core/device_memory_manager.inc index 97c2706abb..d70444ef8c 100644 --- a/src/core/device_memory_manager.inc +++ b/src/core/device_memory_manager.inc @@ -234,7 +234,7 @@ void DeviceMemoryManager<Traits>::Map(DAddr address, VAddr virtual_address, size } impl->multi_dev_address.Register(new_dev, start_id); } - t_slot = {}; + translation_generation.fetch_add(1, std::memory_order_release); if (track) { TrackContinuityImpl(address, virtual_address, size, asid); } @@ -268,7 +268,7 @@ void DeviceMemoryManager<Traits>::Unmap(DAddr address, size_t size) { base_dev = new_start | MULTI_FLAG; } } - t_slot = {}; + translation_generation.fetch_add(1, std::memory_order_release); } template <typename Traits> void DeviceMemoryManager<Traits>::TrackContinuityImpl(DAddr address, VAddr virtual_address, @@ -404,9 +404,10 @@ void DeviceMemoryManager<Traits>::ReadBlock(DAddr address, void* dest_pointer, s const std::size_t page_offset = address & Memory::YUZU_PAGEMASK; if (size <= Memory::YUZU_PAGESIZE - page_offset) { const DAddr guest_page = address & ~static_cast<DAddr>(Memory::YUZU_PAGEMASK); - for (size_t i = 0; i < 4; ++i) { - if (t_slot[i].guest_page == guest_page && t_slot[i].host_ptr != nullptr) { - std::memcpy(dest_pointer, t_slot[i].host_ptr + page_offset, size); + TranslationCache& cache = Translations(); + for (const TranslationEntry& entry : cache.slots) { + if (entry.guest_page == guest_page && entry.host_ptr != nullptr) { + std::memcpy(dest_pointer, entry.host_ptr + page_offset, size); return; } } @@ -415,8 +416,8 @@ void DeviceMemoryManager<Traits>::ReadBlock(DAddr address, void* dest_pointer, s const auto phys_addr = tracked_entries[page_index].compressed_physical_ptr; if (phys_addr != 0) { auto* const mem_ptr = GetPointerFromRaw<u8>((PAddr(phys_addr - 1) << Memory::YUZU_PAGEBITS)); - t_slot[cache_cursor % t_slot.size()] = TranslationEntry{.guest_page = guest_page, .host_ptr = mem_ptr}; - cache_cursor = (cache_cursor + 1) & 3U; + cache.slots[cache.cursor] = TranslationEntry{.guest_page = guest_page, .host_ptr = mem_ptr}; + cache.cursor = (cache.cursor + 1) & 3U; std::memcpy(dest_pointer, mem_ptr + page_offset, size); return; } @@ -462,9 +463,10 @@ void DeviceMemoryManager<Traits>::ReadBlockUnsafe(DAddr address, void* dest_poin const std::size_t page_offset = address & Memory::YUZU_PAGEMASK; if (size <= Memory::YUZU_PAGESIZE - page_offset) { const DAddr guest_page = address & ~static_cast<DAddr>(Memory::YUZU_PAGEMASK); - for (size_t i = 0; i < 4; ++i) { - if (t_slot[i].guest_page == guest_page && t_slot[i].host_ptr != nullptr) { - std::memcpy(dest_pointer, t_slot[i].host_ptr + page_offset, size); + TranslationCache& cache = Translations(); + for (const TranslationEntry& entry : cache.slots) { + if (entry.guest_page == guest_page && entry.host_ptr != nullptr) { + std::memcpy(dest_pointer, entry.host_ptr + page_offset, size); return; } } @@ -473,8 +475,8 @@ void DeviceMemoryManager<Traits>::ReadBlockUnsafe(DAddr address, void* dest_poin const auto phys_addr = tracked_entries[page_index].compressed_physical_ptr; if (phys_addr != 0) { auto* const mem_ptr = GetPointerFromRaw<u8>((PAddr(phys_addr - 1) << Memory::YUZU_PAGEBITS)); - t_slot[cache_cursor % t_slot.size()] = TranslationEntry{.guest_page = guest_page, .host_ptr = mem_ptr}; - cache_cursor = (cache_cursor + 1) & 3U; + cache.slots[cache.cursor] = TranslationEntry{.guest_page = guest_page, .host_ptr = mem_ptr}; + cache.cursor = (cache.cursor + 1) & 3U; std::memcpy(dest_pointer, mem_ptr + page_offset, size); return; } |
