From 9f061763346787806990aabb57b1a1bfb69860ce Mon Sep 17 00:00:00 2001 From: Swastik-51 Date: Fri, 9 Oct 2026 01:27:29 +0530 Subject: [PATCH 1/3] fix(agc): cache Linux describePages results --- .../Execution/src/GuestMemory.cpp | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp index de2fb32eb..77e3ac3e9 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -423,6 +423,17 @@ struct PageStates { arena.forget(address, bytes); image.forget(address, bytes); } + + void record(std::uintptr_t base, std::uintptr_t regionEnd, bool readable, bool writable, std::uint64_t generation) { + for (PageSpan* span : {&arena, &image}) { + if (!readable || span->size == 0 || regionEnd <= span->base || base >= span->base + span->size) continue; + const std::uint8_t value = PageReadable | (writable ? PageWritable : 0u); + const auto first = std::max(base, span->base); + const auto last = std::min(regionEnd, span->base + span->size); + for (auto at = first; at < last; at += PageBytes) span->store(at, value); + if (GuestAllocations::GuestAllocationsGeneration_nid_postfix() != generation) span->forget(first, static_cast(last - first)); + } + } }; PageStates& Pages() { @@ -533,18 +544,12 @@ bool describePages(std::uintptr_t address, std::size_t bytes, Emit&& emit) { const bool committed = memory.State == MEM_COMMIT && (memory.Protect & (PAGE_GUARD | PAGE_NOACCESS)) == 0; const bool readable = committed && readableProtection(protection); const bool writable = readable && writableProtection(protection); - for (PageSpan* span : {&pages.arena, &pages.image}) { - if (!readable || span->size == 0 || regionEnd <= span->base || base >= span->base + span->size) continue; - const std::uint8_t value = PageReadable | (writable ? PageWritable : 0u); - const auto first = std::max(base, span->base); - const auto last = std::min(regionEnd, span->base + span->size); - for (auto at = first; at < last; at += PageBytes) span->store(at, value); - if (GuestAllocations::GuestAllocationsGeneration_nid_postfix() != generation) span->forget(first, static_cast(last - first)); - } + pages.record(base, regionEnd, readable, writable, generation); const auto next = std::min(end, regionEnd); if (!emit(PageRun{cursor, next, readable, writable})) return true; cursor = next; #else + const auto generation = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); if (const int fd = HostMapping::QueryFd(); fd >= 0) { procmap_query query{}; query.size = sizeof(query); @@ -558,9 +563,13 @@ bool describePages(std::uintptr_t address, std::size_t bytes, Emit&& emit) { cursor = gapEnd; continue; } + const auto base = static_cast(query.vma_start); + const auto regionEnd = static_cast(query.vma_end); const bool readable = (query.vma_flags & PROCMAP_QUERY_VMA_READABLE) != 0; + const bool writable = readable && (query.vma_flags & PROCMAP_QUERY_VMA_WRITABLE) != 0; + pages.record(base, regionEnd, readable, writable, generation); const auto next = std::min(end, query.vma_end); - if (!emit(PageRun{cursor, next, readable, readable && (query.vma_flags & PROCMAP_QUERY_VMA_WRITABLE) != 0})) return true; + if (!emit(PageRun{cursor, next, readable, writable})) return true; cursor = next; continue; } @@ -589,8 +598,11 @@ bool describePages(std::uintptr_t address, std::size_t bytes, Emit&& emit) { found = true; break; } + const bool readable = permissions[0] == 'r'; + const bool writable = readable && permissions[1] == 'w'; + pages.record(first, last, readable, writable, generation); const auto next = std::min(end, last); - if (!emit(PageRun{cursor, next, permissions[0] == 'r', permissions[0] == 'r' && permissions[1] == 'w'})) return true; + if (!emit(PageRun{cursor, next, readable, writable})) return true; cursor = next; found = true; break; From 49e9298706212d0972197d09d7434f0113224b06 Mon Sep 17 00:00:00 2001 From: Ahir <191682168+ahir061@users.noreply.github.com> Date: Fri, 9 Oct 2026 02:00:05 +0530 Subject: [PATCH 2/3] fix(agc): store only the requested pages of a Linux page query describePages stored the whole VMA that PROCMAP_QUERY or /proc/self/maps returned. Linux merges adjacent anonymous mappings with equal flags, so an arena VMA can span GiBs, and the first table miss inside it after an invalidation rewrote every page of it. Only the pages of the request, rounded out to whole pages, are stored now, as #1791 describes. When the guest arena is available, RunUnmappedGapTests checks that a check stores no pages outside its range, and that protecting and unmapping through the registry clears the stored pages. The registry checks follow the test on the perf/linux-page-cache branch linked from #1791. Co-Authored-By: Claude Opus 5.5 --- .../Execution/src/GuestMemory.cpp | 10 ++++--- .../tests/GuestAllocations.cpp | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp index 77e3ac3e9..b884243b9 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -550,6 +550,10 @@ bool describePages(std::uintptr_t address, std::size_t bytes, Emit&& emit) { cursor = next; #else const auto generation = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + const auto recordRun = [&](std::uintptr_t next, bool readable, bool writable) { + constexpr auto mask = static_cast(PageBytes - 1); + pages.record(cursor & ~mask, (next + mask) & ~mask, readable, writable, generation); + }; if (const int fd = HostMapping::QueryFd(); fd >= 0) { procmap_query query{}; query.size = sizeof(query); @@ -563,12 +567,10 @@ bool describePages(std::uintptr_t address, std::size_t bytes, Emit&& emit) { cursor = gapEnd; continue; } - const auto base = static_cast(query.vma_start); - const auto regionEnd = static_cast(query.vma_end); const bool readable = (query.vma_flags & PROCMAP_QUERY_VMA_READABLE) != 0; const bool writable = readable && (query.vma_flags & PROCMAP_QUERY_VMA_WRITABLE) != 0; - pages.record(base, regionEnd, readable, writable, generation); const auto next = std::min(end, query.vma_end); + recordRun(next, readable, writable); if (!emit(PageRun{cursor, next, readable, writable})) return true; cursor = next; continue; @@ -600,8 +602,8 @@ bool describePages(std::uintptr_t address, std::size_t bytes, Emit&& emit) { } const bool readable = permissions[0] == 'r'; const bool writable = readable && permissions[1] == 'w'; - pages.record(first, last, readable, writable, generation); const auto next = std::min(end, last); + recordRun(next, readable, writable); if (!emit(PageRun{cursor, next, readable, writable})) return true; cursor = next; found = true; diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp index f4f02db6a..de79f6a57 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp @@ -1,5 +1,6 @@ #include "BdaTests.hpp" #include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/GuestHeap.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" @@ -187,5 +188,33 @@ void RunUnmappedGapTests() { Require(!GuestMemory::Accessible(reinterpret_cast(std::uintptr_t{0x18}), 4), "a near-null address counts as accessible"); munmap(block, page); munmap(block + 2 * page, page); + if (!GuestArena::GuestArenaAvailable_nid_postfix()) return; + auto* guest = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(2 * page, page)); + Require(mmap(guest, 2 * page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) == guest, "cannot map the arena page test pages"); + { + GuestAllocations::Mutation mutation; + mutation.Add(guest, 2 * page, true, true); + } + Require(GuestMemory::Accessible(guest, page, true), "a mapped arena page is not writable"); + Require(mprotect(guest + page, page, PROT_READ) == 0, "cannot protect the arena test's second page"); + Require(!GuestMemory::Accessible(guest + page, page, true), "a check stored arena pages outside its range"); + { + GuestAllocations::Mutation mutation; + mutation.Protect(guest + page, page, true, true, [&] { Require(mprotect(guest + page, page, PROT_READ | PROT_WRITE) == 0, "cannot restore the arena test's second page"); }); + } + Require(GuestMemory::Accessible(guest, 2 * page, true), "mapped arena pages are not writable"); + { + GuestAllocations::Mutation mutation; + mutation.Protect(guest, page, true, false, [&] { Require(mprotect(guest, page, PROT_READ) == 0, "cannot protect the arena test page"); }); + } + Require(!GuestMemory::Accessible(guest, page, true) && GuestMemory::Accessible(guest, page) && GuestMemory::Accessible(guest + page, page, true), "an arena page protected through the registry kept its old access"); + { + GuestAllocations::Mutation mutation; + const auto release = [](const void* pointer, std::size_t bytes, const void*, bool) { Require(munmap(const_cast(pointer), bytes) == 0, "cannot unmap an arena test page"); }; + mutation.Unmap(guest, page, release); + mutation.Unmap(guest + page, page, release); + } + Require(!GuestMemory::Accessible(guest, page) && !GuestMemory::Accessible(guest + page, page), "unmapped arena pages are still accessible"); + GuestArena::GuestArenaRelease_nid_postfix(guest, 2 * page); #endif } From d635d1896761fc71b2dedfdf2df047d1bca8a5c1 Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Sat, 10 Oct 2026 23:00:41 +1300 Subject: [PATCH 3/3] test(agc): check that a Linux page query is answered from the page table Revoke a checked arena page with mprotect behind the registry and check that the stored answer comes back, so the test fails without the cache. Pass the gpu argument that GuestAllocations::Mutation::Add and Protect now take. --- core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp index de79f6a57..ad2314a04 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp @@ -193,19 +193,22 @@ void RunUnmappedGapTests() { Require(mmap(guest, 2 * page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) == guest, "cannot map the arena page test pages"); { GuestAllocations::Mutation mutation; - mutation.Add(guest, 2 * page, true, true); + mutation.Add(guest, 2 * page, true, true, true); } Require(GuestMemory::Accessible(guest, page, true), "a mapped arena page is not writable"); + Require(mprotect(guest, page, PROT_NONE) == 0, "cannot revoke the arena test page behind the registry"); + Require(GuestMemory::Accessible(guest, page, true), "a checked arena page was queried again instead of read from the page table"); + Require(mprotect(guest, page, PROT_READ | PROT_WRITE) == 0, "cannot restore the arena test page behind the registry"); Require(mprotect(guest + page, page, PROT_READ) == 0, "cannot protect the arena test's second page"); Require(!GuestMemory::Accessible(guest + page, page, true), "a check stored arena pages outside its range"); { GuestAllocations::Mutation mutation; - mutation.Protect(guest + page, page, true, true, [&] { Require(mprotect(guest + page, page, PROT_READ | PROT_WRITE) == 0, "cannot restore the arena test's second page"); }); + mutation.Protect(guest + page, page, true, true, true, [&] { Require(mprotect(guest + page, page, PROT_READ | PROT_WRITE) == 0, "cannot restore the arena test's second page"); }); } Require(GuestMemory::Accessible(guest, 2 * page, true), "mapped arena pages are not writable"); { GuestAllocations::Mutation mutation; - mutation.Protect(guest, page, true, false, [&] { Require(mprotect(guest, page, PROT_READ) == 0, "cannot protect the arena test page"); }); + mutation.Protect(guest, page, true, false, true, [&] { Require(mprotect(guest, page, PROT_READ) == 0, "cannot protect the arena test page"); }); } Require(!GuestMemory::Accessible(guest, page, true) && GuestMemory::Accessible(guest, page) && GuestMemory::Accessible(guest + page, page, true), "an arena page protected through the registry kept its old access"); {