From 6ad393775f94064c459b4f86bcb6645cc4aa8bb3 Mon Sep 17 00:00:00 2001 From: nmzik Date: Tue, 20 Oct 2026 15:53:23 +0200 Subject: [PATCH] memory: write GPU publishes to backed pages and skip reserved holes like sparse reads --- src/kernel/memoryAddressSpace.inc | 98 ++++++++++++++------------ tests/VirtualMemoryAllocationTests.cpp | 21 +++--- 2 files changed, 63 insertions(+), 56 deletions(-) diff --git a/src/kernel/memoryAddressSpace.inc b/src/kernel/memoryAddressSpace.inc index 69bb6eb74..946a8493c 100644 --- a/src/kernel/memoryAddressSpace.inc +++ b/src/kernel/memoryAddressSpace.inc @@ -28,9 +28,9 @@ static bool CoalescePlaceholders(uint64_t vaddr, uint64_t size) { } #endif -template -static void CopySparseRange(const Regions& regions, uint64_t vaddr, void* data, uint64_t size, - Source&& source, Gap&& gap) { +template +static void ForEachSparseRange(const Regions& regions, uint64_t vaddr, uint64_t size, + Region&& region, Gap&& gap) { const auto end = vaddr + size; auto mapping = regions.lower_bound(vaddr); if (mapping != regions.begin()) { @@ -40,23 +40,34 @@ static void CopySparseRange(const Regions& regions, uint64_t vaddr, void* data, } } - auto* destination = static_cast(data); - auto cursor = vaddr; + auto cursor = vaddr; for (; mapping != regions.end() && mapping->second.vaddr < end; ++mapping) { const auto& entry = mapping->second; const auto start = std::max(vaddr, entry.vaddr); const auto bytes = std::min(end, entry.vaddr + entry.size) - start; if (cursor < start) { - gap(cursor, destination + (cursor - vaddr), start - cursor); + gap(cursor, start - cursor); } - std::memcpy(destination + (start - vaddr), source(entry, start), bytes); + region(entry, start, bytes); cursor = start + bytes; } if (cursor < end) { - gap(cursor, destination + (cursor - vaddr), end - cursor); + gap(cursor, end - cursor); } } +template +static void CopySparseRange(const Regions& regions, uint64_t vaddr, void* data, uint64_t size, + Source&& source, Gap&& gap) { + auto* destination = static_cast(data); + ForEachSparseRange( + regions, vaddr, size, + [&](const auto& entry, uint64_t start, uint64_t bytes) { + std::memcpy(destination + (start - vaddr), source(entry, start), bytes); + }, + [&](uint64_t start, uint64_t bytes) { gap(start, destination + (start - vaddr), bytes); }); +} + class GuestBackingStore { public: enum class FailureReason { @@ -200,36 +211,16 @@ public: return false; } + // Skip unbacked gaps, mirroring sparse reads that zero-fill them. std::lock_guard lock(m_mutex); - const auto end = vaddr + size; - auto first = m_maps.upper_bound(vaddr); - if (first == m_maps.begin()) { - return false; - } - --first; - auto current = vaddr; - for (auto mapping = first; current < end; ++mapping) { - if (mapping == m_maps.end() || current < mapping->second.vaddr || - current >= mapping->second.vaddr + mapping->second.size) { - return false; - } - const auto& entry = mapping->second; - const auto bytes = std::min(end, entry.vaddr + entry.size) - current; - if (!RangeOk(entry.backing_offset + current - entry.vaddr, bytes)) { - return false; - } - current += bytes; - } - - const auto* source = static_cast(data); - current = vaddr; - for (auto mapping = first; current < end; ++mapping) { - const auto& entry = mapping->second; - const auto bytes = std::min(end, entry.vaddr + entry.size) - current; - const auto offset = entry.backing_offset + current - entry.vaddr; - std::memcpy(m_backing_base + offset, source + (current - vaddr), bytes); - current += bytes; - } + const auto* source = static_cast(data); + ForEachSparseRange( + m_maps, vaddr, size, + [&](const auto& entry, uint64_t start, uint64_t bytes) { + std::memcpy(m_backing_base + (entry.backing_offset + start - entry.vaddr), + source + (start - vaddr), bytes); + }, + [](uint64_t, uint64_t) {}); return true; } @@ -775,8 +766,14 @@ public: }); } + // Writes the backed bytes of a range that holds no private mappings. bool TryWriteBacking(uint64_t vaddr, const void* data, uint64_t size) { - return m_backing->TryWriteBacking(vaddr, data, size); + if (!Owns(vaddr, size)) { + return false; + } + std::shared_lock lock(m_mutex); + return !OverlapsPrivateUnlocked(vaddr, size) && + m_backing->TryWriteBacking(vaddr, data, size); } bool TryReadBacking(uint64_t vaddr, void* data, uint64_t size) { @@ -790,15 +787,8 @@ public: } std::shared_lock lock(m_mutex); if constexpr (!copy_private) { - auto mapping = m_mapped.upper_bound(vaddr); - if (mapping != m_mapped.begin()) { - --mapping; - } - for (; mapping != m_mapped.end() && mapping->second.vaddr < vaddr + size; ++mapping) { - const auto& region = mapping->second; - if (region.type == MappingType::Private && region.vaddr + region.size > vaddr) { - return false; - } + if (OverlapsPrivateUnlocked(vaddr, size)) { + return false; } } return m_backing->TryReadSparseBacking( @@ -1111,6 +1101,20 @@ private: return true; } + [[nodiscard]] bool OverlapsPrivateUnlocked(uint64_t vaddr, uint64_t size) const { + auto mapping = m_mapped.upper_bound(vaddr); + if (mapping != m_mapped.begin()) { + --mapping; + } + for (; mapping != m_mapped.end() && mapping->second.vaddr < vaddr + size; ++mapping) { + const auto& region = mapping->second; + if (region.type == MappingType::Private && region.vaddr + region.size > vaddr) { + return true; + } + } + return false; + } + bool AddMappedUnlocked(uint64_t vaddr, uint64_t size, MappingType type) { if (size == 0 || UINT64_MAX - vaddr < size) { return false; diff --git a/tests/VirtualMemoryAllocationTests.cpp b/tests/VirtualMemoryAllocationTests.cpp index 4631e1214..961ad9089 100644 --- a/tests/VirtualMemoryAllocationTests.cpp +++ b/tests/VirtualMemoryAllocationTests.cpp @@ -1596,20 +1596,23 @@ void TestDirectMapQueryOffsetAndPartialMunmap() { ExpectUnmapped(test, base + SceKernelPageSize); constexpr uint64_t transaction_sentinel = 0x5452414e53414354ull; // "TRANSACT" - constexpr uint64_t rejected_write = 0x4e4f504152544941ull; // "NOPARTIA" + constexpr uint64_t sparse_write = 0x4e4f504152544941ull; // "NOPARTIA" + constexpr uint64_t backed_prefix_mask = 0xffffffffull; const auto crossing_address = base + SceKernelPageSize - sizeof(uint32_t); std::memcpy(reinterpret_cast(alias_base + SceKernelPageSize - sizeof(uint32_t)), &transaction_sentinel, sizeof(transaction_sentinel)); Check(test, - !Libs::LibKernel::Memory::TryWriteBacking(crossing_address, &rejected_write, - sizeof(rejected_write)), - "TryWriteBacking should reject a range crossing an unmapped span"); - uint64_t backing_after_rejected_write = 0; - std::memcpy(&backing_after_rejected_write, + Libs::LibKernel::Memory::TryWriteBacking(crossing_address, &sparse_write, + sizeof(sparse_write)), + "TryWriteBacking should write a range crossing an unmapped span"); + uint64_t backing_after_sparse_write = 0; + std::memcpy(&backing_after_sparse_write, reinterpret_cast(alias_base + SceKernelPageSize - sizeof(uint32_t)), - sizeof(backing_after_rejected_write)); - Check(test, backing_after_rejected_write == transaction_sentinel, - "failed backing writes must not modify a validated prefix"); + sizeof(backing_after_sparse_write)); + Check(test, + backing_after_sparse_write == ((sparse_write & backed_prefix_mask) | + (transaction_sentinel & ~backed_prefix_mask)), + "sparse backing writes must fill the backed prefix and skip the unmapped span"); uint64_t rejected_read = transaction_sentinel; Check(test, !Libs::LibKernel::Memory::TryReadBacking(crossing_address, &rejected_read,