From d95634b706fce78bda54c13e4a37e673afe673da Mon Sep 17 00:00:00 2001 From: AlxcBTW <187763517+AlxcBTW@users.noreply.github.com> Date: Tue, 13 Oct 2026 08:37:17 +0200 Subject: [PATCH] perf(agc): bind the address space's BDA table for writes inside writable imports A build mapping only the cached address space but writing through V#s got a table keyed by its writes: a new key for nearly every set of written addresses, so each such build merged the space's imported ranges, stored them, then hashed and compared them in BdaResources. AddressRanges splits only ranges without write permission around the written intervals. When no written interval overlaps such a range of the space, its result is the space's own ranges, so the build binds the space's table by the space serial, as a build without writes does. A write overlapping a range without write permission keeps the per-writes table. Test: builds with one and two writes inside a writable import bind the serial and the ranges of the build without writes, equal to AddressRanges(), and keep the read-only import without write permission. Co-Authored-By: Claude Opus 5.5 --- .../Graphics/src/GuestBufferMemory.cpp | 9 ++++ .../libSceAgcDriver/tests/BdaResources.cpp | 54 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index f537ad995c..bd49d5c46a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -3451,6 +3451,15 @@ std::optional GuestBufferMemory::CachedAddressTa Require(uploaded && !committed, "guest GPU address ranges are not available"); if (writes.empty()) return CachedTable{space->serial, &space->ranges}; if (writeTableRanges != nullptr) return CachedTable{writeTableSerial, writeTableRanges.get()}; + const auto& spaceRanges = space->ranges; + const bool inWritable = std::all_of(writes.begin(), writes.end(), [&](const auto& written) { + auto at = std::partition_point(spaceRanges.begin(), spaceRanges.end(), [&](const auto& range) { return range.end <= written.first; }); + for (; at != spaceRanges.end() && at->begin < written.second; ++at) { + if ((at->permissions & ShaderRecompiler::BdaAbi::Write) == 0u) return false; + } + return true; + }); + if (inWritable) return CachedTable{space->serial, &space->ranges}; std::lock_guard lock(space->tableMutex); const auto found = space->writeTables.find(writes); if (found != space->writeTables.end()) { diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp index 5a1f81054e..a2fd8b445b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp @@ -119,6 +119,59 @@ void importSpanTests(const Context& context) { ::operator delete(block, std::align_val_t{piece}); } +void spaceWritableWritesTests(const Context& context) { + if (!AddressSpaceCounters().enabled) return; + constexpr std::size_t piece = 65536; + auto* writable = static_cast(::operator new(piece, std::align_val_t{piece})); + auto* readOnly = static_cast(::operator new(piece, std::align_val_t{piece})); + std::memset(writable, 0x11, piece); + std::memset(readOnly, 0x22, piece); + auto importing = context; + importing.hostImportAlignment = piece; + const auto change = [&](std::uint8_t* range, bool add, bool canWrite) { + auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + if (add) GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, range, piece, true, canWrite, true); + else GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, range); + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + }; + change(writable, true, true); + change(readOnly, true, false); + const auto same = [](const std::vector& left, const std::vector& right) { + return left.size() == right.size() && (left.empty() || std::memcmp(left.data(), right.data(), left.size() * sizeof(left.front())) == 0); + }; + struct Built { + std::uint64_t serial; + std::vector ranges; + }; + const auto build = [&](std::initializer_list> written, const char* step) { + GuestBufferMemory leased(importing); + leased.AcquireRegistered(); + for (const auto& [offset, bytes] : written) leased.AddWritable(reinterpret_cast(writable) + offset, bytes); + leased.Upload(true); + const auto cached = leased.CachedAddressTable(); + Require(cached.has_value() && cached->ranges != nullptr, std::string("a build of the space alone has no cached table: ") + step); + Built result{cached->serial, leased.AddressRanges()}; + Require(same(*cached->ranges, result.ranges), std::string("the cached table differs from AddressRanges(): ") + step); + leased.WriteBack(); + return result; + }; + const auto plain = build({}, "no writes"); + const auto once = build({{256, 512}}, "one write in a writable import"); + const auto twice = build({{4096, 64}, {8192, 128}}, "two writes in a writable import"); + Require(once.serial == plain.serial && twice.serial == plain.serial, "writes in a writable import did not bind the space's table"); + Require(same(once.ranges, plain.ranges) && same(twice.ranges, plain.ranges), "writes in a writable import changed the BDA table"); + const auto writableBegin = reinterpret_cast(writable); + const auto readOnlyBegin = reinterpret_cast(readOnly); + for (const auto& range : once.ranges) { + if (range.begin == writableBegin) Require((range.permissions & ShaderRecompiler::BdaAbi::Write) != 0u, "the writable import lost its write permission"); + if (range.begin == readOnlyBegin) Require((range.permissions & ShaderRecompiler::BdaAbi::Write) == 0u, "the read-only import gained write permission"); + } + change(writable, false, true); + change(readOnly, false, false); + ::operator delete(writable, std::align_val_t{piece}); + ::operator delete(readOnly, std::align_val_t{piece}); +} + void failedImportRemapTests(const Context& context, const BdaTestAccess& access) { constexpr std::size_t piece = 65536; auto* block = static_cast(::operator new(4 * piece, std::align_val_t{piece})); @@ -1239,6 +1292,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { importSpanTests(context); failedImportRemapTests(context, access); spaceImportRunTests(context); + spaceWritableWritesTests(context); importWindowTests(context); heapMirrorTests(context, access); importedHeapMirrorTests(context, access);