aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 05:46:38 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 20:49:47 -0400
commit06f294c7e6b305d5d8904d284de70c268cc3f2e0 (patch)
tree03181491fb0869fa12856ae8a6c8d6616ebc2f04 /src
parent7fa88f0bb18ab0ae1896e6060a0c770e73446f51 (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.h24
-rw-r--r--src/core/device_memory_manager.inc26
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;
}